import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios, { AxiosInstance } from 'axios';

export interface TraccarDevice {
  id: number;
  name: string;
  uniqueId: string;
  status: string;
  disabled: boolean;
  lastUpdate: string | null;
  positionId: number | null;
  groupId: number | null;
  phone: string | null;
  model: string | null;
  contact: string | null;
  category: string | null;
  attributes: Record<string, any>;
}

export interface TraccarPosition {
  id: number;
  deviceId: number;
  protocol: string;
  deviceTime: string;
  fixTime: string;
  serverTime: string;
  outdated: boolean;
  valid: boolean;
  latitude: number;
  longitude: number;
  altitude: number;
  speed: number; // knots
  course: number;
  address: string | null;
  accuracy: number;
  network: any;
  attributes: {
    batteryLevel?: number;
    distance?: number;
    totalDistance?: number;
    motion?: boolean;
  };
}

export interface TraccarSession {
  cookie: string;
}

@Injectable()
export class TraccarClientService implements OnModuleInit {
  private readonly logger = new Logger(TraccarClientService.name);
  private client: AxiosInstance;
  private readonly baseUrl: string;
  private sessionCookie: string | null = null;
  private adminUser: string = 'admin';
  private adminPassword: string = 'admin';

  constructor(private configService: ConfigService) {
    this.baseUrl = this.configService.get<string>('TRACCAR_API_URL') || 'http://localhost:8082';
    this.client = axios.create({
      baseURL: this.baseUrl,
      timeout: 30000,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }

  async onModuleInit() {
    // Don't authenticate on startup - wait for GpsService to load credentials from database
    // The first request will trigger authentication with the correct credentials
    this.logger.log('Traccar client initialized - waiting for credentials from database');
  }

  /**
   * Set admin credentials (called from GpsService after loading from DB)
   */
  setCredentials(username: string, password: string) {
    this.adminUser = username;
    this.adminPassword = password;
    this.sessionCookie = null; // Force re-authentication
  }

  /**
   * Authenticate with Traccar and get session cookie
   */
  async authenticate(): Promise<boolean> {
    try {
      const response = await this.client.post(
        '/api/session',
        new URLSearchParams({
          email: this.adminUser,
          password: this.adminPassword,
        }).toString(),
        {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        },
      );

      // Extract session cookie
      const setCookie = response.headers['set-cookie'];
      if (setCookie && setCookie.length > 0) {
        this.sessionCookie = setCookie[0].split(';')[0];
        this.logger.log('Traccar authentication successful');
        return true;
      }

      return false;
    } catch (error: any) {
      this.logger.error('Traccar authentication failed:', error.message);
      throw error;
    }
  }

  /**
   * Make authenticated request to Traccar API
   */
  private async request<T>(
    method: 'get' | 'post' | 'put' | 'delete',
    path: string,
    data?: any,
  ): Promise<T> {
    // Ensure we have a session
    if (!this.sessionCookie) {
      await this.authenticate();
    }

    try {
      const response = await this.client.request({
        method,
        url: path,
        data,
        headers: {
          Cookie: this.sessionCookie,
        },
      });
      return response.data;
    } catch (error: any) {
      // If unauthorized, try to re-authenticate once
      if (error.response?.status === 401) {
        this.sessionCookie = null;
        await this.authenticate();
        const response = await this.client.request({
          method,
          url: path,
          data,
          headers: {
            Cookie: this.sessionCookie,
          },
        });
        return response.data;
      }
      throw error;
    }
  }

  /**
   * Check if Traccar is available
   */
  async isAvailable(): Promise<boolean> {
    try {
      await this.client.get('/api/server');
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Get server info
   */
  async getServerInfo(): Promise<any> {
    return this.request('get', '/api/server');
  }

  /**
   * Create a new device in Traccar
   */
  async createDevice(name: string, uniqueId: string, phone?: string): Promise<TraccarDevice> {
    const device = await this.request<TraccarDevice>('post', '/api/devices', {
      name,
      uniqueId,
      phone,
      category: 'person',
    });

    // Link device to all admin users so they can see it
    await this.linkDeviceToAllAdmins(device.id);

    return device;
  }

  /**
   * Link a device to a specific user
   */
  async linkDeviceToUser(deviceId: number, userId: number): Promise<boolean> {
    try {
      await this.request('post', '/api/permissions', {
        userId,
        deviceId,
      });
      return true;
    } catch (error: any) {
      // 400 means permission already exists, which is fine
      if (error.response?.status === 400) {
        return true;
      }
      this.logger.warn(`Failed to link device ${deviceId} to user ${userId}: ${error.message}`);
      return false;
    }
  }

  /**
   * Link a device to all admin users
   */
  async linkDeviceToAllAdmins(deviceId: number): Promise<void> {
    try {
      const users = await this.getAllUsers();
      const admins = users.filter(u => u.administrator);

      for (const admin of admins) {
        await this.linkDeviceToUser(deviceId, admin.id);
      }

      this.logger.log(`Linked device ${deviceId} to ${admins.length} admin users`);
    } catch (error: any) {
      this.logger.warn(`Failed to link device to admins: ${error.message}`);
    }
  }

  /**
   * Get device by unique ID
   */
  async getDeviceByUniqueId(uniqueId: string): Promise<TraccarDevice | null> {
    try {
      const devices = await this.request<TraccarDevice[]>('get', `/api/devices?uniqueId=${uniqueId}`);
      return devices.length > 0 ? devices[0] : null;
    } catch {
      return null;
    }
  }

  /**
   * Get device by Traccar ID
   */
  async getDevice(deviceId: number): Promise<TraccarDevice | null> {
    try {
      const devices = await this.request<TraccarDevice[]>('get', `/api/devices?id=${deviceId}`);
      return devices.length > 0 ? devices[0] : null;
    } catch {
      return null;
    }
  }

  /**
   * Get all devices
   */
  async getAllDevices(): Promise<TraccarDevice[]> {
    return this.request('get', '/api/devices');
  }

  /**
   * Delete a device
   */
  async deleteDevice(deviceId: number): Promise<void> {
    await this.request('delete', `/api/devices/${deviceId}`);
  }

  /**
   * Get latest position for a device
   */
  async getDevicePosition(deviceId: number): Promise<TraccarPosition | null> {
    try {
      const positions = await this.request<TraccarPosition[]>('get', `/api/positions?deviceId=${deviceId}`);
      return positions.length > 0 ? positions[0] : null;
    } catch {
      return null;
    }
  }

  /**
   * Get all current positions
   */
  async getAllPositions(): Promise<TraccarPosition[]> {
    return this.request('get', '/api/positions');
  }

  /**
   * Get position history for a device
   */
  async getPositionHistory(
    deviceId: number,
    from: Date,
    to: Date,
  ): Promise<TraccarPosition[]> {
    const fromStr = from.toISOString();
    const toStr = to.toISOString();
    return this.request('get', `/api/positions?deviceId=${deviceId}&from=${fromStr}&to=${toStr}`);
  }

  /**
   * Get trip report (includes distance traveled)
   */
  async getTripReport(
    deviceId: number,
    from: Date,
    to: Date,
  ): Promise<any[]> {
    const fromStr = from.toISOString();
    const toStr = to.toISOString();
    return this.request('get', `/api/reports/trips?deviceId=${deviceId}&from=${fromStr}&to=${toStr}`);
  }

  /**
   * Get summary report (includes total distance, max speed, etc.)
   */
  async getSummaryReport(
    deviceId: number,
    from: Date,
    to: Date,
  ): Promise<any[]> {
    const fromStr = from.toISOString();
    const toStr = to.toISOString();
    return this.request('get', `/api/reports/summary?deviceId=${deviceId}&from=${fromStr}&to=${toStr}`);
  }

  /**
   * Convert speed from knots to km/h
   */
  knotsToKmh(knots: number): number {
    return knots * 1.852;
  }

  /**
   * Convert speed from knots to mph
   */
  knotsToMph(knots: number): number {
    return knots * 1.15078;
  }

  /**
   * Get the device server URL for mobile app configuration
   * Returns the Traccar URL (nginx handles SSL termination and proxies to Traccar)
   */
  getDeviceServerUrl(): string {
    // Return the base Traccar URL - nginx handles routing OsmAnd protocol
    return this.getTraccarUrl();
  }

  // ============================================
  // User Management (for VIP Admin sync)
  // ============================================

  /**
   * Create or update a Traccar user
   */
  async createOrUpdateUser(
    email: string,
    name: string,
    password: string,
    isAdmin: boolean = false,
    token?: string,
  ): Promise<TraccarUser> {
    // Check if user exists
    const existingUser = await this.getUserByEmail(email);

    if (existingUser) {
      // Update existing user
      return this.request('put', `/api/users/${existingUser.id}`, {
        ...existingUser,
        name,
        password: password || undefined, // Only update if provided
        administrator: isAdmin,
        token: token || existingUser.token, // Preserve or update token
      });
    }

    // Create new user with token for auto-login
    return this.request('post', '/api/users', {
      email,
      name,
      password,
      administrator: isAdmin,
      token: token || undefined,
    });
  }

  /**
   * Get or generate a token for a user (for auto-login)
   */
  async ensureUserToken(email: string, token: string): Promise<string | null> {
    const user = await this.getUserByEmail(email);
    if (!user) return null;

    // If user already has a token, return it
    if (user.token) {
      return user.token;
    }

    // Set the token on the user
    const updatedUser = await this.request<TraccarUser>('put', `/api/users/${user.id}`, {
      ...user,
      token,
    });

    return updatedUser.token;
  }

  /**
   * Get user's token if they have one
   */
  async getUserToken(email: string): Promise<string | null> {
    const user = await this.getUserByEmail(email);
    return user?.token || null;
  }

  /**
   * Get user by email
   */
  async getUserByEmail(email: string): Promise<TraccarUser | null> {
    try {
      const users = await this.request<TraccarUser[]>('get', '/api/users');
      return users.find(u => u.email.toLowerCase() === email.toLowerCase()) || null;
    } catch {
      return null;
    }
  }

  /**
   * Get all users
   */
  async getAllUsers(): Promise<TraccarUser[]> {
    return this.request('get', '/api/users');
  }

  /**
   * Delete a user
   */
  async deleteUser(userId: number): Promise<void> {
    await this.request('delete', `/api/users/${userId}`);
  }

  /**
   * Create a session for a user (for auto-login)
   * Returns session token that can be used for authentication
   */
  async createUserSession(email: string, password: string): Promise<{ token: string; cookie: string } | null> {
    try {
      const response = await this.client.post(
        '/api/session',
        new URLSearchParams({
          email,
          password,
        }).toString(),
        {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        },
      );

      const setCookie = response.headers['set-cookie'];
      if (setCookie && setCookie.length > 0) {
        const cookie = setCookie[0].split(';')[0];
        // Extract token if available
        const token = response.data?.token || null;
        return { token, cookie };
      }
      return null;
    } catch (error) {
      this.logger.error('Failed to create user session:', error);
      return null;
    }
  }

  /**
   * Get Traccar base URL for frontend redirect
   * Returns the public URL that users can access via the subdomain
   */
  getTraccarUrl(): string {
    // Check for explicit Traccar public URL first
    const traccarPublicUrl = this.configService.get<string>('TRACCAR_PUBLIC_URL');
    if (traccarPublicUrl) {
      return traccarPublicUrl;
    }

    // Default: derive from frontend URL using traccar subdomain
    const frontendUrl = this.configService.get<string>('FRONTEND_URL') || 'http://localhost:5173';
    try {
      const url = new URL(frontendUrl);
      // Replace the subdomain/hostname to use traccar subdomain
      // e.g., vip.madeamess.online -> traccar.vip.madeamess.online
      return `${url.protocol}//traccar.${url.hostname}`;
    } catch {
      return 'http://localhost:8082';
    }
  }

  /**
   * Check if initial setup is needed (no users exist)
   */
  async needsInitialSetup(): Promise<boolean> {
    try {
      // Try to access without auth - if it works, setup is needed
      const response = await this.client.get('/api/server', {
        validateStatus: (status) => status < 500,
      });

      // Traccar 6.x uses newServer=true to indicate first-time setup needed
      // Also check registration=true for older versions
      return response.data?.newServer === true || response.data?.registration === true;
    } catch {
      return true;
    }
  }

  /**
   * Perform initial setup - create first admin user
   */
  async performInitialSetup(email: string, password: string): Promise<boolean> {
    try {
      // Register first user (becomes admin automatically)
      const response = await this.client.post('/api/users', {
        email,
        password,
        name: 'VIP Coordinator Admin',
      });

      if (response.status === 200) {
        // Authenticate with the new credentials
        this.adminUser = email;
        this.adminPassword = password;
        await this.authenticate();
        return true;
      }
      return false;
    } catch (error: any) {
      this.logger.error('Initial setup failed:', error.message);
      return false;
    }
  }
}

export interface TraccarUser {
  id: number;
  name: string;
  email: string;
  administrator: boolean;
  disabled: boolean;
  readonly: boolean;
  token: string | null;
  attributes: Record<string, any>;
}
