import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';

import { GpsDeviceRepository, GpsPositionRepository } from './gps-tracking.repository';
import { GpsDevice } from './entities/gps-device.entity';
import { Gt06Gps, Gt06Status } from './gt06/gt06.codec';

/** A device is considered ONLINE if seen within this window. */
const ONLINE_WINDOW_MS = 5 * 60 * 1000;

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

  constructor(
    private readonly devices: GpsDeviceRepository,
    private readonly positions: GpsPositionRepository,
  ) {}

  private isOnline(d: GpsDevice): boolean {
    return Boolean(d.lastSeenAt && Date.now() - new Date(d.lastSeenAt).getTime() < ONLINE_WINDOW_MS);
  }

  /** Find the device for an IMEI, auto-registering it on first contact. */
  private async ensureDevice(imei: string): Promise<GpsDevice> {
    const existing = await this.devices.findByImei(imei);
    if (existing) return existing;
    this.logger.log(`Auto-registering new GPS tracker ${imei}`);
    return this.devices.create({ imei, status: 'REGISTERED', lastSeenAt: new Date() });
  }

  // ── Ingestion (called by the TCP server) ──

  async handleLogin(imei: string): Promise<void> {
    const device = await this.ensureDevice(imei);
    await this.devices.update(device.id, { lastSeenAt: new Date(), status: 'ONLINE' });
  }

  async handleHeartbeat(imei: string, status: Gt06Status): Promise<void> {
    const device = await this.ensureDevice(imei);
    await this.devices.update(device.id, {
      lastSeenAt: new Date(),
      status: 'ONLINE',
      voltageLevel: status.voltageLevel,
      gsmLevel: status.gsmLevel,
    });
  }

  async handleFix(imei: string, gps: Gt06Gps, alarm = 0, status?: Gt06Status): Promise<void> {
    const device = await this.ensureDevice(imei);
    const now = new Date();
    await this.devices.update(device.id, {
      lastSeenAt: now,
      status: 'ONLINE',
      lastLat: gps.latitude,
      lastLng: gps.longitude,
      lastSpeed: gps.speed,
      lastCourse: gps.course,
      lastFixAt: new Date(gps.time),
      ...(status ? { voltageLevel: status.voltageLevel, gsmLevel: status.gsmLevel } : {}),
    });
    await this.positions.create({
      deviceId: device.id,
      imei,
      vehicleId: device.vehicleId ?? null,
      lat: gps.latitude,
      lng: gps.longitude,
      speed: gps.speed,
      course: gps.course,
      satellites: gps.satellites,
      positioned: gps.positioned,
      gpsTime: new Date(gps.time),
      alarm,
    });
  }

  // ── Queries / management (REST) ──

  private decorate(d: GpsDevice) {
    return { ...d, online: this.isOnline(d) };
  }

  async listDevices() {
    const rows = await this.devices.findAll({ relations: { vehicle: true }, order: { createdAt: 'DESC' } });
    return rows.map((d) => this.decorate(d));
  }

  /** Live map feed — devices that have at least one fix. */
  async latest() {
    const rows = await this.devices.findAll({ relations: { vehicle: true } });
    return rows.filter((d) => d.lastLat != null && d.lastLng != null).map((d) => this.decorate(d));
  }

  async history(vehicleId: string, limit = 200) {
    return this.positions.findAll({
      where: { vehicleId },
      order: { gpsTime: 'DESC' },
      take: Math.min(limit, 1000),
    });
  }

  async registerDevice(dto: { imei: string; name?: string; vehicleId?: string | null }) {
    const existing = await this.devices.findByImei(dto.imei);
    if (existing) throw new BadRequestException(`A device with IMEI ${dto.imei} already exists`);
    return this.devices.create({
      imei: dto.imei,
      name: dto.name ?? null,
      vehicleId: dto.vehicleId ?? null,
      status: 'REGISTERED',
    });
  }

  async updateDevice(id: string, dto: { name?: string; vehicleId?: string | null }) {
    const updated = await this.devices.update(id, {
      ...(dto.name !== undefined ? { name: dto.name } : {}),
      ...(dto.vehicleId !== undefined ? { vehicleId: dto.vehicleId } : {}),
    });
    if (!updated) throw new NotFoundException(`GPS device ${id} not found`);
    return updated;
  }

  async removeDevice(id: string): Promise<void> {
    await this.devices.softDelete(id);
  }
}
