/**
 * DispositivoGps Service
 * ERP Transportistas
 *
 * Business logic for GPS device management.
 * Adapted from erp-mecanicas-diesel MMD-014 GPS Integration
 * Module: MAI-006 Tracking
 */

import { Repository, DataSource } from 'typeorm';
import { DispositivoGps, PlataformaGps, TipoUnidadGps } from '../entities/dispositivo-gps.entity';

// DTOs
export interface CreateDispositivoGpsDto {
  unidadId: string;
  tipoUnidad?: TipoUnidadGps;
  externalDeviceId: string;
  plataforma?: PlataformaGps;
  imei?: string;
  numeroSerie?: string;
  telefono?: string;
  modelo?: string;
  fabricante?: string;
  intervaloPosicionSegundos?: number;
  metadata?: Record<string, any>;
  createdBy?: string;
}

export interface UpdateDispositivoGpsDto {
  externalDeviceId?: string;
  plataforma?: PlataformaGps;
  imei?: string;
  numeroSerie?: string;
  telefono?: string;
  modelo?: string;
  fabricante?: string;
  intervaloPosicionSegundos?: number;
  activo?: boolean;
  metadata?: Record<string, any>;
}

export interface DispositivoGpsFilters {
  unidadId?: string;
  tipoUnidad?: TipoUnidadGps;
  plataforma?: PlataformaGps;
  activo?: boolean;
  search?: string;
}

export interface UltimaPosicion {
  latitud: number;
  longitud: number;
  timestamp: Date;
}

export class DispositivoGpsService {
  private dispositivoRepository: Repository<DispositivoGps>;

  constructor(dataSource: DataSource) {
    this.dispositivoRepository = dataSource.getRepository(DispositivoGps);
  }

  /**
   * Register a new GPS device
   */
  async create(tenantId: string, dto: CreateDispositivoGpsDto): Promise<DispositivoGps> {
    // Check for duplicate external device ID
    const existing = await this.dispositivoRepository.findOne({
      where: { tenantId, externalDeviceId: dto.externalDeviceId },
    });

    if (existing) {
      throw new Error(`Dispositivo con ID externo ${dto.externalDeviceId} ya existe`);
    }

    // Check if unit already has a device
    const unidadDispositivo = await this.dispositivoRepository.findOne({
      where: { tenantId, unidadId: dto.unidadId, activo: true },
    });

    if (unidadDispositivo) {
      throw new Error(`La unidad ${dto.unidadId} ya tiene un dispositivo GPS activo`);
    }

    const dispositivo = this.dispositivoRepository.create({
      tenantId,
      unidadId: dto.unidadId,
      tipoUnidad: dto.tipoUnidad || TipoUnidadGps.TRACTORA,
      externalDeviceId: dto.externalDeviceId,
      plataforma: dto.plataforma || PlataformaGps.TRACCAR,
      imei: dto.imei,
      numeroSerie: dto.numeroSerie,
      telefono: dto.telefono,
      modelo: dto.modelo,
      fabricante: dto.fabricante,
      intervaloPosicionSegundos: dto.intervaloPosicionSegundos || 30,
      metadata: dto.metadata || {},
      createdBy: dto.createdBy,
      activo: true,
    });

    return this.dispositivoRepository.save(dispositivo);
  }

  /**
   * Find device by ID
   */
  async findById(tenantId: string, id: string): Promise<DispositivoGps | null> {
    return this.dispositivoRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Find device by external ID
   */
  async findByExternalId(tenantId: string, externalDeviceId: string): Promise<DispositivoGps | null> {
    return this.dispositivoRepository.findOne({
      where: { tenantId, externalDeviceId },
    });
  }

  /**
   * Find device by unit ID
   */
  async findByUnidadId(tenantId: string, unidadId: string): Promise<DispositivoGps | null> {
    return this.dispositivoRepository.findOne({
      where: { tenantId, unidadId, activo: true },
    });
  }

  /**
   * List devices with filters
   */
  async findAll(
    tenantId: string,
    filters: DispositivoGpsFilters = {},
    pagination = { page: 1, limit: 20 }
  ) {
    const queryBuilder = this.dispositivoRepository.createQueryBuilder('dispositivo')
      .where('dispositivo.tenant_id = :tenantId', { tenantId });

    if (filters.unidadId) {
      queryBuilder.andWhere('dispositivo.unidad_id = :unidadId', { unidadId: filters.unidadId });
    }
    if (filters.tipoUnidad) {
      queryBuilder.andWhere('dispositivo.tipo_unidad = :tipoUnidad', { tipoUnidad: filters.tipoUnidad });
    }
    if (filters.plataforma) {
      queryBuilder.andWhere('dispositivo.plataforma = :plataforma', { plataforma: filters.plataforma });
    }
    if (filters.activo !== undefined) {
      queryBuilder.andWhere('dispositivo.activo = :activo', { activo: filters.activo });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(dispositivo.external_device_id ILIKE :search OR dispositivo.imei ILIKE :search OR dispositivo.numero_serie ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .orderBy('dispositivo.created_at', 'DESC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Update device
   */
  async update(tenantId: string, id: string, dto: UpdateDispositivoGpsDto): Promise<DispositivoGps | null> {
    const dispositivo = await this.findById(tenantId, id);
    if (!dispositivo) return null;

    // Check external ID uniqueness if changing
    if (dto.externalDeviceId && dto.externalDeviceId !== dispositivo.externalDeviceId) {
      const existing = await this.findByExternalId(tenantId, dto.externalDeviceId);
      if (existing) {
        throw new Error(`Dispositivo con ID externo ${dto.externalDeviceId} ya existe`);
      }
    }

    Object.assign(dispositivo, dto);
    return this.dispositivoRepository.save(dispositivo);
  }

  /**
   * Update last position (called when new position is received)
   */
  async updateUltimaPosicion(
    tenantId: string,
    id: string,
    posicion: UltimaPosicion
  ): Promise<DispositivoGps | null> {
    const dispositivo = await this.findById(tenantId, id);
    if (!dispositivo) return null;

    dispositivo.ultimaPosicionLat = posicion.latitud;
    dispositivo.ultimaPosicionLng = posicion.longitud;
    dispositivo.ultimaPosicionAt = posicion.timestamp;

    return this.dispositivoRepository.save(dispositivo);
  }

  /**
   * Deactivate device
   */
  async deactivate(tenantId: string, id: string): Promise<boolean> {
    const dispositivo = await this.findById(tenantId, id);
    if (!dispositivo) return false;

    dispositivo.activo = false;
    await this.dispositivoRepository.save(dispositivo);
    return true;
  }

  /**
   * Get devices with stale positions (no update in X minutes)
   */
  async findDispositivosInactivos(tenantId: string, umbralMinutos: number = 10): Promise<DispositivoGps[]> {
    const threshold = new Date(Date.now() - umbralMinutos * 60 * 1000);

    return this.dispositivoRepository
      .createQueryBuilder('dispositivo')
      .where('dispositivo.tenant_id = :tenantId', { tenantId })
      .andWhere('dispositivo.activo = :activo', { activo: true })
      .andWhere('(dispositivo.ultima_posicion_at IS NULL OR dispositivo.ultima_posicion_at < :threshold)', { threshold })
      .getMany();
  }

  /**
   * Get all active devices with last position
   */
  async findActivosConPosicion(tenantId: string): Promise<DispositivoGps[]> {
    return this.dispositivoRepository.find({
      where: { tenantId, activo: true },
      order: { ultimaPosicionAt: 'DESC' },
    });
  }

  /**
   * Get device statistics
   */
  async getEstadisticas(tenantId: string): Promise<{
    total: number;
    activos: number;
    porPlataforma: Record<PlataformaGps, number>;
    porTipoUnidad: Record<TipoUnidadGps, number>;
    enLinea: number;
    fueraDeLinea: number;
  }> {
    const umbralMinutos = 10;
    const threshold = new Date(Date.now() - umbralMinutos * 60 * 1000);

    const [total, activos, plataformaCounts, tipoUnidadCounts, enLinea] = await Promise.all([
      this.dispositivoRepository.count({ where: { tenantId } }),
      this.dispositivoRepository.count({ where: { tenantId, activo: true } }),
      this.dispositivoRepository
        .createQueryBuilder('dispositivo')
        .select('dispositivo.plataforma', 'plataforma')
        .addSelect('COUNT(*)', 'count')
        .where('dispositivo.tenant_id = :tenantId', { tenantId })
        .groupBy('dispositivo.plataforma')
        .getRawMany(),
      this.dispositivoRepository
        .createQueryBuilder('dispositivo')
        .select('dispositivo.tipo_unidad', 'tipoUnidad')
        .addSelect('COUNT(*)', 'count')
        .where('dispositivo.tenant_id = :tenantId', { tenantId })
        .groupBy('dispositivo.tipo_unidad')
        .getRawMany(),
      this.dispositivoRepository
        .createQueryBuilder('dispositivo')
        .where('dispositivo.tenant_id = :tenantId', { tenantId })
        .andWhere('dispositivo.activo = :activo', { activo: true })
        .andWhere('dispositivo.ultima_posicion_at >= :threshold', { threshold })
        .getCount(),
    ]);

    const porPlataforma: Record<PlataformaGps, number> = {
      [PlataformaGps.TRACCAR]: 0,
      [PlataformaGps.WIALON]: 0,
      [PlataformaGps.SAMSARA]: 0,
      [PlataformaGps.GEOTAB]: 0,
      [PlataformaGps.MANUAL]: 0,
    };

    const porTipoUnidad: Record<TipoUnidadGps, number> = {
      [TipoUnidadGps.TRACTORA]: 0,
      [TipoUnidadGps.REMOLQUE]: 0,
      [TipoUnidadGps.CAJA]: 0,
      [TipoUnidadGps.EQUIPO]: 0,
      [TipoUnidadGps.OPERADOR]: 0,
    };

    for (const row of plataformaCounts) {
      if (row.plataforma) {
        porPlataforma[row.plataforma as PlataformaGps] = parseInt(row.count, 10);
      }
    }

    for (const row of tipoUnidadCounts) {
      if (row.tipoUnidad) {
        porTipoUnidad[row.tipoUnidad as TipoUnidadGps] = parseInt(row.count, 10);
      }
    }

    return {
      total,
      activos,
      porPlataforma,
      porTipoUnidad,
      enLinea,
      fueraDeLinea: activos - enLinea,
    };
  }
}
