import {
  Injectable,
  Logger,
  NotFoundException,
  BadRequestException,
  OnModuleInit,
} from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { PrismaService } from '../prisma/prisma.service';
import { SignalService } from '../signal/signal.service';
import { TraccarClientService } from './traccar-client.service';
import {
  DriverLocationDto,
  DriverStatsDto,
  GpsStatusDto,
  LocationDataDto,
} from './dto/location-response.dto';
import { UpdateGpsSettingsDto } from './dto/update-gps-settings.dto';
import { GpsSettings, User } from '@prisma/client';
import * as crypto from 'crypto';

@Injectable()
export class GpsService implements OnModuleInit {
  private readonly logger = new Logger(GpsService.name);

  constructor(
    private prisma: PrismaService,
    private traccarClient: TraccarClientService,
    private signalService: SignalService,
  ) {}

  async onModuleInit() {
    // Ensure GPS settings exist and load Traccar credentials
    const settings = await this.getSettings();

    // Set Traccar credentials from database settings
    if (settings.traccarAdminUser && settings.traccarAdminPassword) {
      this.traccarClient.setCredentials(
        settings.traccarAdminUser,
        settings.traccarAdminPassword,
      );
      this.logger.log(`Loaded Traccar credentials for user: ${settings.traccarAdminUser}`);
    }
  }

  /**
   * Get or create GPS settings (singleton pattern)
   */
  async getSettings(): Promise<GpsSettings> {
    let settings = await this.prisma.gpsSettings.findFirst();

    if (!settings) {
      this.logger.log('Creating default GPS settings');
      settings = await this.prisma.gpsSettings.create({
        data: {
          updateIntervalSeconds: 60,
          shiftStartHour: 4,
          shiftStartMinute: 0,
          shiftEndHour: 1,
          shiftEndMinute: 0,
          retentionDays: 30,
          traccarAdminUser: 'admin',
          traccarAdminPassword: 'admin', // Default - should be changed!
        },
      });
    }

    return settings;
  }

  /**
   * Update GPS settings
   */
  async updateSettings(dto: UpdateGpsSettingsDto): Promise<GpsSettings> {
    const settings = await this.getSettings();

    const updated = await this.prisma.gpsSettings.update({
      where: { id: settings.id },
      data: dto,
    });

    // Update Traccar client credentials if changed
    if (dto.traccarAdminUser || dto.traccarAdminPassword) {
      this.traccarClient.setCredentials(
        dto.traccarAdminUser || settings.traccarAdminUser,
        dto.traccarAdminPassword || settings.traccarAdminPassword || 'admin',
      );
    }

    return updated;
  }

  /**
   * Get GPS system status
   */
  async getStatus(): Promise<GpsStatusDto> {
    const settings = await this.getSettings();
    const traccarAvailable = await this.traccarClient.isAvailable();

    let traccarVersion: string | null = null;
    if (traccarAvailable) {
      try {
        const serverInfo = await this.traccarClient.getServerInfo();
        traccarVersion = serverInfo.version;
      } catch {
        // Ignore
      }
    }

    const enrolledDrivers = await this.prisma.gpsDevice.count();
    const activeDrivers = await this.prisma.gpsDevice.count({
      where: {
        isActive: true,
        lastActive: {
          gte: new Date(Date.now() - 5 * 60 * 1000), // Active in last 5 minutes
        },
      },
    });

    return {
      traccarAvailable,
      traccarVersion,
      enrolledDrivers,
      activeDrivers,
      settings: {
        updateIntervalSeconds: settings.updateIntervalSeconds,
        shiftStartTime: `${settings.shiftStartHour.toString().padStart(2, '0')}:${settings.shiftStartMinute.toString().padStart(2, '0')}`,
        shiftEndTime: `${settings.shiftEndHour.toString().padStart(2, '0')}:${settings.shiftEndMinute.toString().padStart(2, '0')}`,
        retentionDays: settings.retentionDays,
      },
    };
  }

  /**
   * Enroll a driver for GPS tracking
   */
  async enrollDriver(
    driverId: string,
    sendSignalMessage: boolean = true,
  ): Promise<{
    success: boolean;
    deviceIdentifier: string;
    serverUrl: string;
    instructions: string;
    signalMessageSent?: boolean;
  }> {
    // Check if driver exists
    const driver = await this.prisma.driver.findUnique({
      where: { id: driverId },
      include: { gpsDevice: true },
    });

    if (!driver) {
      throw new NotFoundException('Driver not found');
    }

    if (driver.deletedAt) {
      throw new BadRequestException('Cannot enroll deleted driver');
    }

    if (driver.gpsDevice) {
      throw new BadRequestException('Driver is already enrolled for GPS tracking');
    }

    // Generate unique device identifier (no special characters for better compatibility)
    const deviceIdentifier = `vipdriver${driverId.replace(/-/g, '').slice(0, 8)}`;

    // Create device in Traccar
    const traccarDevice = await this.traccarClient.createDevice(
      driver.name,
      deviceIdentifier,
      driver.phone || undefined,
    );

    // Create GPS device record (consent pre-approved by HR at hiring)
    await this.prisma.gpsDevice.create({
      data: {
        driverId,
        traccarDeviceId: traccarDevice.id,
        deviceIdentifier,
        consentGiven: true,
        consentGivenAt: new Date(),
      },
    });

    const serverUrl = this.traccarClient.getDeviceServerUrl();
    const settings = await this.getSettings();

    const instructions = `
GPS Tracking Setup Instructions for ${driver.name}:

1. Download "Traccar Client" app:
   - iOS: https://apps.apple.com/app/traccar-client/id843156974
   - Android: https://play.google.com/store/apps/details?id=org.traccar.client

2. Open the app and configure:
   - Device identifier: ${deviceIdentifier}
   - Server URL: ${serverUrl}
   - Frequency: ${settings.updateIntervalSeconds} seconds
   - Location accuracy: High

3. Tap "Service Status" to start tracking.

Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}:00 - ${settings.shiftEndHour}:00).
    `.trim();

    let signalMessageSent = false;

    // Send Signal message if requested and driver has phone
    if (sendSignalMessage && driver.phone) {
      try {
        const linkedNumber = await this.signalService.getLinkedNumber();
        if (linkedNumber) {
          const formattedPhone = this.signalService.formatPhoneNumber(driver.phone);
          const result = await this.signalService.sendMessage(
            linkedNumber,
            formattedPhone,
            instructions,
          );
          signalMessageSent = result.success;
        }
      } catch (error) {
        this.logger.warn(`Failed to send Signal message to driver: ${error}`);
      }
    }

    return {
      success: true,
      deviceIdentifier,
      serverUrl,
      instructions,
      signalMessageSent,
    };
  }

  /**
   * Unenroll a driver from GPS tracking
   */
  async unenrollDriver(driverId: string): Promise<{ success: boolean; message: string }> {
    const gpsDevice = await this.prisma.gpsDevice.findUnique({
      where: { driverId },
    });

    if (!gpsDevice) {
      throw new NotFoundException('Driver is not enrolled for GPS tracking');
    }

    // Delete from Traccar
    try {
      await this.traccarClient.deleteDevice(gpsDevice.traccarDeviceId);
    } catch (error) {
      this.logger.warn(`Failed to delete device from Traccar: ${error}`);
    }

    // Delete location history
    await this.prisma.gpsLocationHistory.deleteMany({
      where: { deviceId: gpsDevice.id },
    });

    // Delete GPS device record
    await this.prisma.gpsDevice.delete({
      where: { id: gpsDevice.id },
    });

    return {
      success: true,
      message: 'Driver unenrolled from GPS tracking. All location history has been deleted.',
    };
  }

  /**
   * Confirm driver consent for GPS tracking
   */
  async confirmConsent(driverId: string, consentGiven: boolean): Promise<void> {
    const gpsDevice = await this.prisma.gpsDevice.findUnique({
      where: { driverId },
    });

    if (!gpsDevice) {
      throw new NotFoundException('Driver is not enrolled for GPS tracking');
    }

    await this.prisma.gpsDevice.update({
      where: { id: gpsDevice.id },
      data: {
        consentGiven,
        consentGivenAt: consentGiven ? new Date() : null,
      },
    });
  }

  /**
   * Get all enrolled devices
   */
  async getEnrolledDevices(): Promise<any[]> {
    return this.prisma.gpsDevice.findMany({
      include: {
        driver: {
          select: {
            id: true,
            name: true,
            phone: true,
          },
        },
      },
      orderBy: { enrolledAt: 'desc' },
    });
  }

  /**
   * Get all active driver locations (Admin only)
   */
  async getActiveDriverLocations(): Promise<DriverLocationDto[]> {
    const devices = await this.prisma.gpsDevice.findMany({
      where: {
        isActive: true,
        driver: {
          deletedAt: null,
        },
      },
      include: {
        driver: {
          select: {
            id: true,
            name: true,
            phone: true,
          },
        },
      },
    });

    // Get all positions from Traccar
    let positions: any[] = [];
    try {
      positions = await this.traccarClient.getAllPositions();
    } catch (error) {
      this.logger.warn(`Failed to fetch positions from Traccar: ${error}`);
    }

    return devices.map((device) => {
      const position = positions.find((p) => p.deviceId === device.traccarDeviceId);

      return {
        driverId: device.driverId,
        driverName: device.driver.name,
        driverPhone: device.driver.phone,
        deviceIdentifier: device.deviceIdentifier,
        isActive: device.isActive,
        lastActive: device.lastActive,
        location: position
          ? {
              latitude: position.latitude,
              longitude: position.longitude,
              altitude: position.altitude || null,
              speed: this.traccarClient.knotsToMph(position.speed || 0),
              course: position.course || null,
              accuracy: position.accuracy || null,
              battery: position.attributes?.batteryLevel || null,
              timestamp: new Date(position.deviceTime),
            }
          : null,
      };
    });
  }

  /**
   * Get a specific driver's location
   */
  async getDriverLocation(driverId: string): Promise<DriverLocationDto | null> {
    const device = await this.prisma.gpsDevice.findUnique({
      where: { driverId },
      include: {
        driver: {
          select: {
            id: true,
            name: true,
            phone: true,
          },
        },
      },
    });

    if (!device) {
      return null;
    }

    let position = null;
    try {
      position = await this.traccarClient.getDevicePosition(device.traccarDeviceId);
    } catch (error) {
      this.logger.warn(`Failed to fetch position for driver ${driverId}: ${error}`);
    }

    return {
      driverId: device.driverId,
      driverName: device.driver.name,
      driverPhone: device.driver.phone,
      deviceIdentifier: device.deviceIdentifier,
      isActive: device.isActive,
      lastActive: device.lastActive,
      location: position
        ? {
            latitude: position.latitude,
            longitude: position.longitude,
            altitude: position.altitude || null,
            speed: this.traccarClient.knotsToMph(position.speed || 0),
            course: position.course || null,
            accuracy: position.accuracy || null,
            battery: position.attributes?.batteryLevel || null,
            timestamp: new Date(position.deviceTime),
          }
        : null,
    };
  }

  /**
   * Get driver's own stats (for driver self-view)
   */
  async getDriverStats(
    driverId: string,
    fromDate?: Date,
    toDate?: Date,
  ): Promise<DriverStatsDto> {
    const device = await this.prisma.gpsDevice.findUnique({
      where: { driverId },
      include: {
        driver: {
          select: {
            id: true,
            name: true,
          },
        },
      },
    });

    if (!device) {
      throw new NotFoundException('Driver is not enrolled for GPS tracking');
    }

    // Default to last 7 days if no date range specified
    const to = toDate || new Date();
    const from = fromDate || new Date(to.getTime() - 7 * 24 * 60 * 60 * 1000);

    // Get summary from Traccar
    let totalMiles = 0;
    let topSpeedMph = 0;
    let topSpeedTimestamp: Date | null = null;
    let totalTrips = 0;
    let totalDrivingMinutes = 0;

    try {
      const summary = await this.traccarClient.getSummaryReport(
        device.traccarDeviceId,
        from,
        to,
      );

      if (summary.length > 0) {
        const report = summary[0];
        // Distance is in meters, convert to miles
        totalMiles = (report.distance || 0) / 1609.344;
        topSpeedMph = this.traccarClient.knotsToMph(report.maxSpeed || 0);
        // Engine hours in milliseconds, convert to minutes
        totalDrivingMinutes = Math.round((report.engineHours || 0) / 60000);
      }

      // Get trips for additional stats
      const trips = await this.traccarClient.getTripReport(
        device.traccarDeviceId,
        from,
        to,
      );
      totalTrips = trips.length;

      // Find top speed timestamp from positions
      const positions = await this.traccarClient.getPositionHistory(
        device.traccarDeviceId,
        from,
        to,
      );

      let maxSpeed = 0;
      for (const pos of positions) {
        const speedMph = this.traccarClient.knotsToMph(pos.speed || 0);
        if (speedMph > maxSpeed) {
          maxSpeed = speedMph;
          topSpeedTimestamp = new Date(pos.deviceTime);
        }
      }
      topSpeedMph = maxSpeed;
    } catch (error) {
      this.logger.warn(`Failed to fetch stats from Traccar: ${error}`);
    }

    // Get recent locations from our database
    const recentLocations = await this.prisma.gpsLocationHistory.findMany({
      where: {
        deviceId: device.id,
        timestamp: {
          gte: from,
          lte: to,
        },
      },
      orderBy: { timestamp: 'desc' },
      take: 100,
    });

    return {
      driverId,
      driverName: device.driver.name,
      period: {
        from,
        to,
      },
      stats: {
        totalMiles: Math.round(totalMiles * 10) / 10,
        topSpeedMph: Math.round(topSpeedMph),
        topSpeedTimestamp,
        averageSpeedMph: totalDrivingMinutes > 0
          ? Math.round((totalMiles / (totalDrivingMinutes / 60)) * 10) / 10
          : 0,
        totalTrips,
        totalDrivingMinutes,
      },
      recentLocations: recentLocations.map((loc) => ({
        latitude: loc.latitude,
        longitude: loc.longitude,
        altitude: loc.altitude,
        speed: loc.speed,
        course: loc.course,
        accuracy: loc.accuracy,
        battery: loc.battery,
        timestamp: loc.timestamp,
      })),
    };
  }

  /**
   * Sync positions from Traccar to our database (for history/stats)
   * Called periodically via cron job
   */
  @Cron(CronExpression.EVERY_MINUTE)
  async syncPositions(): Promise<void> {
    const devices = await this.prisma.gpsDevice.findMany({
      where: {
        isActive: true,
      },
    });

    if (devices.length === 0) return;

    try {
      const positions = await this.traccarClient.getAllPositions();

      for (const device of devices) {
        const position = positions.find((p) => p.deviceId === device.traccarDeviceId);
        if (!position) continue;

        // Update last active timestamp
        await this.prisma.gpsDevice.update({
          where: { id: device.id },
          data: { lastActive: new Date(position.deviceTime) },
        });

        // Store in history
        await this.prisma.gpsLocationHistory.create({
          data: {
            deviceId: device.id,
            latitude: position.latitude,
            longitude: position.longitude,
            altitude: position.altitude || null,
            speed: this.traccarClient.knotsToMph(position.speed || 0),
            course: position.course || null,
            accuracy: position.accuracy || null,
            battery: position.attributes?.batteryLevel || null,
            timestamp: new Date(position.deviceTime),
          },
        });
      }
    } catch (error) {
      this.logger.error(`Failed to sync positions: ${error}`);
    }
  }

  /**
   * Clean up old location history (runs daily at 2 AM)
   */
  @Cron('0 2 * * *')
  async cleanupOldLocations(): Promise<void> {
    const settings = await this.getSettings();
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - settings.retentionDays);

    const result = await this.prisma.gpsLocationHistory.deleteMany({
      where: {
        timestamp: { lt: cutoffDate },
      },
    });

    if (result.count > 0) {
      this.logger.log(`Cleaned up ${result.count} old GPS location records`);
    }
  }

  // ============================================
  // Traccar User Sync (VIP Admin -> Traccar Admin)
  // ============================================

  /**
   * Generate a secure password for Traccar user
   */
  private generateTraccarPassword(userId: string): string {
    // Generate deterministic but secure password based on user ID + secret
    const secret = process.env.JWT_SECRET || 'vip-coordinator-traccar-sync';
    return crypto
      .createHmac('sha256', secret)
      .update(userId)
      .digest('hex')
      .substring(0, 24);
  }

  /**
   * Generate a secure token for Traccar auto-login
   */
  private generateTraccarToken(userId: string): string {
    // Generate deterministic token for auto-login
    const secret = process.env.JWT_SECRET || 'vip-coordinator-traccar-token';
    return crypto
      .createHmac('sha256', secret + '-token')
      .update(userId)
      .digest('hex')
      .substring(0, 32);
  }

  /**
   * Sync a VIP user to Traccar
   */
  async syncUserToTraccar(user: User): Promise<boolean> {
    if (!user.email) return false;

    try {
      const isAdmin = user.role === 'ADMINISTRATOR';
      const password = this.generateTraccarPassword(user.id);
      const token = this.generateTraccarToken(user.id);

      await this.traccarClient.createOrUpdateUser(
        user.email,
        user.name || user.email,
        password,
        isAdmin,
        token, // Include token for auto-login
      );

      this.logger.log(`Synced user ${user.email} to Traccar (admin: ${isAdmin})`);
      return true;
    } catch (error) {
      this.logger.error(`Failed to sync user ${user.email} to Traccar:`, error);
      return false;
    }
  }

  /**
   * Sync all VIP admins to Traccar
   */
  async syncAllAdminsToTraccar(): Promise<{ synced: number; failed: number }> {
    const admins = await this.prisma.user.findMany({
      where: {
        role: 'ADMINISTRATOR',
        isApproved: true,
        deletedAt: null,
      },
    });

    let synced = 0;
    let failed = 0;

    for (const admin of admins) {
      const success = await this.syncUserToTraccar(admin);
      if (success) synced++;
      else failed++;
    }

    this.logger.log(`Admin sync complete: ${synced} synced, ${failed} failed`);
    return { synced, failed };
  }

  /**
   * Get Traccar admin URL (Auth0 SSO handles authentication)
   * User's Auth0 role determines admin status in Traccar
   */
  async getTraccarAutoLoginUrl(user: User): Promise<{
    url: string;
    directAccess: boolean;
  }> {
    if (user.role !== 'ADMINISTRATOR') {
      throw new BadRequestException('Only administrators can access Traccar admin');
    }

    // Just return the Traccar URL - Auth0 SSO handles authentication
    // User must have ADMINISTRATOR role in Auth0 to get admin access in Traccar
    const baseUrl = this.traccarClient.getTraccarUrl();

    return {
      url: baseUrl,
      directAccess: true,
    };
  }

  /**
   * Get Traccar session cookie for a user (for proxy/iframe auth)
   * Note: With Auth0 SSO (openid.force=true), this won't work.
   * Use getTraccarAutoLoginUrl() instead for direct redirect.
   */
  async getTraccarSessionForUser(user: User): Promise<string | null> {
    if (user.role !== 'ADMINISTRATOR') {
      return null;
    }

    // With Auth0 SSO, session creation via password is disabled
    // Return null to indicate direct access via URL is needed
    return null;
  }

  /**
   * Check if Traccar needs initial setup
   */
  async checkTraccarSetup(): Promise<{
    needsSetup: boolean;
    isAvailable: boolean;
  }> {
    const isAvailable = await this.traccarClient.isAvailable();
    if (!isAvailable) {
      return { needsSetup: false, isAvailable: false };
    }

    const needsSetup = await this.traccarClient.needsInitialSetup();
    return { needsSetup, isAvailable };
  }

  /**
   * Perform initial Traccar setup
   */
  async performTraccarSetup(adminEmail: string): Promise<boolean> {
    // Generate a secure password for the service account
    const servicePassword = crypto.randomBytes(16).toString('hex');

    const success = await this.traccarClient.performInitialSetup(
      adminEmail,
      servicePassword,
    );

    if (success) {
      // Save the service account credentials to settings
      await this.updateSettings({
        traccarAdminUser: adminEmail,
        traccarAdminPassword: servicePassword,
      });

      this.logger.log('Traccar initial setup complete');
      return true;
    }

    return false;
  }
}
