import { Repository, Between, LessThan, LessThanOrEqual, MoreThan, In } from 'typeorm';
import {
  DocumentoFlota,
  TipoDocumento,
  TipoEntidadDocumento,
} from '../entities/documento-flota.entity';
import { Unidad, EstadoUnidad } from '../entities/unidad.entity';
import { Operador, EstadoOperador } from '../entities/operador.entity';

// ============================================================================
// INTERFACES
// ============================================================================

export interface CreateDocumentoFlotaDto {
  entidadTipo: TipoEntidadDocumento;
  entidadId: string;
  tipoDocumento: TipoDocumento;
  nombre: string;
  numeroDocumento?: string;
  descripcion?: string;
  fechaEmision?: Date;
  fechaVencimiento?: Date;
  diasAlertaVencimiento?: number;
  archivoUrl?: string;
  archivoNombre?: string;
  archivoTipo?: string;
  archivoTamanoBytes?: number;
}

export interface UpdateDocumentoFlotaDto {
  nombre?: string;
  numeroDocumento?: string;
  descripcion?: string;
  fechaEmision?: Date;
  fechaVencimiento?: Date;
  diasAlertaVencimiento?: number;
  archivoUrl?: string;
  archivoNombre?: string;
  archivoTipo?: string;
  archivoTamanoBytes?: number;
  verificado?: boolean;
}

export interface DocumentoVencimiento {
  documento: DocumentoFlota;
  entidadTipo: TipoEntidadDocumento;
  entidadId: string;
  entidadNombre: string;
  diasParaVencer: number;
  esCritico: boolean;
}

export interface ResumenVencimientos {
  totalDocumentos: number;
  vencidos: number;
  porVencer7Dias: number;
  porVencer15Dias: number;
  porVencer30Dias: number;
  porCategoria: {
    unidades: { vencidos: number; porVencer: number };
    remolques: { vencidos: number; porVencer: number };
    operadores: { vencidos: number; porVencer: number };
  };
  documentosCriticosVencidos: DocumentoVencimiento[];
}

export interface EntidadBloqueada {
  entidadTipo: TipoEntidadDocumento;
  entidadId: string;
  entidadNombre: string;
  documentosVencidos: DocumentoFlota[];
  motivoBloqueo: string[];
}

// Documentos criticos que bloquean operacion
const DOCUMENTOS_CRITICOS_UNIDAD: TipoDocumento[] = [
  TipoDocumento.TARJETA_CIRCULACION,
  TipoDocumento.POLIZA_SEGURO,
  TipoDocumento.VERIFICACION,
  TipoDocumento.PERMISO_SCT,
];

const DOCUMENTOS_CRITICOS_OPERADOR: TipoDocumento[] = [
  TipoDocumento.LICENCIA,
  TipoDocumento.CERTIFICADO_FISICO,
  TipoDocumento.ANTIDOPING,
];

// ============================================================================
// SERVICE
// ============================================================================

export class DocumentoFlotaService {
  constructor(
    private readonly documentoRepository: Repository<DocumentoFlota>,
    private readonly unidadRepository: Repository<Unidad>,
    private readonly operadorRepository: Repository<Operador>
  ) {}

  // --------------------------------------------------------------------------
  // CRUD OPERATIONS
  // --------------------------------------------------------------------------

  /**
   * Crear documento de flota (soporta UNIDAD, REMOLQUE, OPERADOR)
   */
  async create(
    tenantId: string,
    dto: CreateDocumentoFlotaDto,
    createdById: string
  ): Promise<DocumentoFlota> {
    // Validar que la entidad existe
    await this.validateEntidadExists(tenantId, dto.entidadTipo, dto.entidadId);

    // Verificar si ya existe un documento del mismo tipo para la entidad
    const existingDoc = await this.documentoRepository.findOne({
      where: {
        tenantId,
        entidadTipo: dto.entidadTipo,
        entidadId: dto.entidadId,
        tipoDocumento: dto.tipoDocumento,
        activo: true,
      },
    });

    if (existingDoc) {
      throw new Error(
        `Ya existe un documento de tipo ${dto.tipoDocumento} para esta entidad. ` +
        `Use el metodo renovar() para actualizarlo.`
      );
    }

    const documento = this.documentoRepository.create({
      ...dto,
      tenantId,
      diasAlertaVencimiento: dto.diasAlertaVencimiento || 30,
      activo: true,
      createdById,
    });

    return this.documentoRepository.save(documento);
  }

  /**
   * Buscar documento por ID
   */
  async findById(tenantId: string, id: string): Promise<DocumentoFlota | null> {
    return this.documentoRepository.findOne({
      where: { tenantId, id, activo: true },
    });
  }

  /**
   * Buscar documento por ID o lanzar error
   */
  async findByIdOrFail(tenantId: string, id: string): Promise<DocumentoFlota> {
    const documento = await this.findById(tenantId, id);
    if (!documento) {
      throw new Error(`Documento con ID ${id} no encontrado`);
    }
    return documento;
  }

  /**
   * Buscar documentos de una unidad
   */
  async findByUnidad(tenantId: string, unidadId: string): Promise<DocumentoFlota[]> {
    // Verificar que la unidad existe
    const unidad = await this.unidadRepository.findOne({
      where: { tenantId, id: unidadId },
    });
    if (!unidad) {
      throw new Error(`Unidad con ID ${unidadId} no encontrada`);
    }

    return this.documentoRepository.find({
      where: {
        tenantId,
        entidadTipo: In([TipoEntidadDocumento.UNIDAD, TipoEntidadDocumento.REMOLQUE]),
        entidadId: unidadId,
        activo: true,
      },
      order: { fechaVencimiento: 'ASC' },
    });
  }

  /**
   * Buscar documentos de un operador
   */
  async findByOperador(tenantId: string, operadorId: string): Promise<DocumentoFlota[]> {
    // Verificar que el operador existe
    const operador = await this.operadorRepository.findOne({
      where: { tenantId, id: operadorId },
    });
    if (!operador) {
      throw new Error(`Operador con ID ${operadorId} no encontrado`);
    }

    return this.documentoRepository.find({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.OPERADOR,
        entidadId: operadorId,
        activo: true,
      },
      order: { fechaVencimiento: 'ASC' },
    });
  }

  /**
   * Buscar documentos por tipo
   */
  async findByTipo(tenantId: string, tipo: TipoDocumento): Promise<DocumentoFlota[]> {
    return this.documentoRepository.find({
      where: {
        tenantId,
        tipoDocumento: tipo,
        activo: true,
      },
      order: { fechaVencimiento: 'ASC' },
    });
  }

  /**
   * Obtener documentos vencidos
   */
  async findVencidos(tenantId: string): Promise<DocumentoFlota[]> {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    return this.documentoRepository.find({
      where: {
        tenantId,
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
      order: { fechaVencimiento: 'ASC' },
    });
  }

  /**
   * Obtener documentos por vencer en X dias
   */
  async findPorVencer(tenantId: string, dias: number): Promise<DocumentoFlota[]> {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    const fechaLimite = new Date(hoy);
    fechaLimite.setDate(fechaLimite.getDate() + dias);

    return this.documentoRepository.find({
      where: {
        tenantId,
        fechaVencimiento: Between(hoy, fechaLimite),
        activo: true,
      },
      order: { fechaVencimiento: 'ASC' },
    });
  }

  /**
   * Actualizar documento
   */
  async update(
    tenantId: string,
    id: string,
    dto: UpdateDocumentoFlotaDto,
    updatedById: string
  ): Promise<DocumentoFlota> {
    const documento = await this.findByIdOrFail(tenantId, id);

    // Si se marca como verificado, registrar quien y cuando
    if (dto.verificado && !documento.verificado) {
      documento.verificado = true;
      documento.verificadoPor = updatedById;
      documento.verificadoFecha = new Date();
    } else if (dto.verificado !== undefined) {
      documento.verificado = dto.verificado;
      if (!dto.verificado) {
        documento.verificadoPor = undefined as unknown as string;
        documento.verificadoFecha = undefined as unknown as Date;
      }
    }

    // Actualizar otros campos
    if (dto.nombre !== undefined) documento.nombre = dto.nombre;
    if (dto.numeroDocumento !== undefined) documento.numeroDocumento = dto.numeroDocumento;
    if (dto.descripcion !== undefined) documento.descripcion = dto.descripcion;
    if (dto.fechaEmision !== undefined) documento.fechaEmision = dto.fechaEmision;
    if (dto.fechaVencimiento !== undefined) documento.fechaVencimiento = dto.fechaVencimiento;
    if (dto.diasAlertaVencimiento !== undefined) documento.diasAlertaVencimiento = dto.diasAlertaVencimiento;
    if (dto.archivoUrl !== undefined) documento.archivoUrl = dto.archivoUrl;
    if (dto.archivoNombre !== undefined) documento.archivoNombre = dto.archivoNombre;
    if (dto.archivoTipo !== undefined) documento.archivoTipo = dto.archivoTipo;
    if (dto.archivoTamanoBytes !== undefined) documento.archivoTamanoBytes = dto.archivoTamanoBytes;

    return this.documentoRepository.save(documento);
  }

  /**
   * Renovar documento (actualiza fecha de vencimiento)
   */
  async renovar(
    tenantId: string,
    id: string,
    nuevaFechaVencimiento: Date,
    updatedById: string
  ): Promise<DocumentoFlota> {
    const documento = await this.findByIdOrFail(tenantId, id);

    // Validar que la nueva fecha sea futura
    const hoy = new Date();
    if (nuevaFechaVencimiento <= hoy) {
      throw new Error('La nueva fecha de vencimiento debe ser una fecha futura');
    }

    // Guardar la fecha anterior para historial (podria extenderse a tabla de historial)
    const fechaAnterior = documento.fechaVencimiento;

    documento.fechaVencimiento = nuevaFechaVencimiento;
    documento.fechaEmision = new Date(); // Fecha de renovacion
    documento.verificado = false; // Requiere nueva verificacion
    documento.verificadoPor = undefined as unknown as string;
    documento.verificadoFecha = undefined as unknown as Date;

    // Agregar nota en descripcion sobre renovacion
    const notaRenovacion = `\n[Renovado el ${new Date().toISOString().split('T')[0]} - Vigencia anterior: ${fechaAnterior?.toISOString().split('T')[0] || 'N/A'}]`;
    documento.descripcion = (documento.descripcion || '') + notaRenovacion;

    return this.documentoRepository.save(documento);
  }

  /**
   * Eliminar documento (soft delete)
   */
  async delete(tenantId: string, id: string): Promise<void> {
    const documento = await this.findByIdOrFail(tenantId, id);
    documento.activo = false;
    await this.documentoRepository.save(documento);
  }

  // --------------------------------------------------------------------------
  // REPORTS & ALERTS
  // --------------------------------------------------------------------------

  /**
   * Obtener resumen de vencimientos por categoria
   */
  async getResumenVencimientos(tenantId: string): Promise<ResumenVencimientos> {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    const fecha7Dias = new Date(hoy);
    fecha7Dias.setDate(fecha7Dias.getDate() + 7);

    const fecha15Dias = new Date(hoy);
    fecha15Dias.setDate(fecha15Dias.getDate() + 15);

    const fecha30Dias = new Date(hoy);
    fecha30Dias.setDate(fecha30Dias.getDate() + 30);

    // Total documentos activos
    const totalDocumentos = await this.documentoRepository.count({
      where: { tenantId, activo: true },
    });

    // Vencidos
    const vencidos = await this.documentoRepository.count({
      where: {
        tenantId,
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
    });

    // Por vencer en 7 dias
    const porVencer7Dias = await this.documentoRepository.count({
      where: {
        tenantId,
        fechaVencimiento: Between(hoy, fecha7Dias),
        activo: true,
      },
    });

    // Por vencer en 15 dias
    const porVencer15Dias = await this.documentoRepository.count({
      where: {
        tenantId,
        fechaVencimiento: Between(hoy, fecha15Dias),
        activo: true,
      },
    });

    // Por vencer en 30 dias
    const porVencer30Dias = await this.documentoRepository.count({
      where: {
        tenantId,
        fechaVencimiento: Between(hoy, fecha30Dias),
        activo: true,
      },
    });

    // Por categoria - Unidades
    const unidadesVencidos = await this.documentoRepository.count({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.UNIDAD,
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
    });
    const unidadesPorVencer = await this.documentoRepository.count({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.UNIDAD,
        fechaVencimiento: Between(hoy, fecha30Dias),
        activo: true,
      },
    });

    // Por categoria - Remolques
    const remolquesVencidos = await this.documentoRepository.count({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.REMOLQUE,
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
    });
    const remolquesPorVencer = await this.documentoRepository.count({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.REMOLQUE,
        fechaVencimiento: Between(hoy, fecha30Dias),
        activo: true,
      },
    });

    // Por categoria - Operadores
    const operadoresVencidos = await this.documentoRepository.count({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.OPERADOR,
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
    });
    const operadoresPorVencer = await this.documentoRepository.count({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.OPERADOR,
        fechaVencimiento: Between(hoy, fecha30Dias),
        activo: true,
      },
    });

    // Documentos criticos vencidos
    const documentosCriticosVencidos = await this.getDocumentosCriticosVencidos(tenantId);

    return {
      totalDocumentos,
      vencidos,
      porVencer7Dias,
      porVencer15Dias,
      porVencer30Dias,
      porCategoria: {
        unidades: { vencidos: unidadesVencidos, porVencer: unidadesPorVencer },
        remolques: { vencidos: remolquesVencidos, porVencer: remolquesPorVencer },
        operadores: { vencidos: operadoresVencidos, porVencer: operadoresPorVencer },
      },
      documentosCriticosVencidos,
    };
  }

  /**
   * Obtener unidades/operadores con documentos criticos vencidos (bloqueo operativo)
   */
  async bloquearPorDocumentosVencidos(tenantId: string): Promise<EntidadBloqueada[]> {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    const entidadesBloqueadas: EntidadBloqueada[] = [];

    // Obtener documentos criticos vencidos de unidades
    const docsUnidades = await this.documentoRepository.find({
      where: {
        tenantId,
        entidadTipo: In([TipoEntidadDocumento.UNIDAD, TipoEntidadDocumento.REMOLQUE]),
        tipoDocumento: In(DOCUMENTOS_CRITICOS_UNIDAD),
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
    });

    // Agrupar por entidad
    const unidadesMap = new Map<string, DocumentoFlota[]>();
    for (const doc of docsUnidades) {
      const docs = unidadesMap.get(doc.entidadId) || [];
      docs.push(doc);
      unidadesMap.set(doc.entidadId, docs);
    }

    // Procesar unidades
    for (const [entidadId, docs] of unidadesMap) {
      const unidad = await this.unidadRepository.findOne({
        where: { tenantId, id: entidadId },
      });

      if (unidad && unidad.activo) {
        entidadesBloqueadas.push({
          entidadTipo: docs[0].entidadTipo,
          entidadId,
          entidadNombre: `${unidad.numeroEconomico} - ${unidad.marca || ''} ${unidad.modelo || ''}`.trim(),
          documentosVencidos: docs,
          motivoBloqueo: docs.map(d => `${d.tipoDocumento}: vencido`),
        });
      }
    }

    // Obtener documentos criticos vencidos de operadores
    const docsOperadores = await this.documentoRepository.find({
      where: {
        tenantId,
        entidadTipo: TipoEntidadDocumento.OPERADOR,
        tipoDocumento: In(DOCUMENTOS_CRITICOS_OPERADOR),
        fechaVencimiento: LessThan(hoy),
        activo: true,
      },
    });

    // Agrupar por operador
    const operadoresMap = new Map<string, DocumentoFlota[]>();
    for (const doc of docsOperadores) {
      const docs = operadoresMap.get(doc.entidadId) || [];
      docs.push(doc);
      operadoresMap.set(doc.entidadId, docs);
    }

    // Procesar operadores
    for (const [entidadId, docs] of operadoresMap) {
      const operador = await this.operadorRepository.findOne({
        where: { tenantId, id: entidadId },
      });

      if (operador && operador.activo) {
        entidadesBloqueadas.push({
          entidadTipo: TipoEntidadDocumento.OPERADOR,
          entidadId,
          entidadNombre: `${operador.numeroEmpleado} - ${operador.nombre} ${operador.apellidoPaterno}`,
          documentosVencidos: docs,
          motivoBloqueo: docs.map(d => `${d.tipoDocumento}: vencido`),
        });
      }
    }

    return entidadesBloqueadas;
  }

  /**
   * Enviar alertas de vencimiento (retorna lista de alertas a enviar)
   */
  async sendAlertasVencimiento(
    tenantId: string,
    diasAnticipacion: number = 30
  ): Promise<DocumentoVencimiento[]> {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    const fechaLimite = new Date(hoy);
    fechaLimite.setDate(fechaLimite.getDate() + diasAnticipacion);

    // Obtener documentos que vencen dentro del rango
    const documentos = await this.documentoRepository.find({
      where: {
        tenantId,
        fechaVencimiento: Between(hoy, fechaLimite),
        activo: true,
      },
      order: { fechaVencimiento: 'ASC' },
    });

    const alertas: DocumentoVencimiento[] = [];

    for (const doc of documentos) {
      const diasParaVencer = Math.ceil(
        (doc.fechaVencimiento.getTime() - hoy.getTime()) / (1000 * 60 * 60 * 24)
      );

      // Solo alertar si esta dentro del periodo de alerta configurado del documento
      if (diasParaVencer <= doc.diasAlertaVencimiento) {
        const entidadNombre = await this.getEntidadNombre(tenantId, doc.entidadTipo, doc.entidadId);
        const esCritico = this.esDocumentoCritico(doc.entidadTipo, doc.tipoDocumento);

        alertas.push({
          documento: doc,
          entidadTipo: doc.entidadTipo,
          entidadId: doc.entidadId,
          entidadNombre,
          diasParaVencer,
          esCritico,
        });
      }
    }

    // Ordenar: criticos primero, luego por dias para vencer
    alertas.sort((a, b) => {
      if (a.esCritico !== b.esCritico) {
        return a.esCritico ? -1 : 1;
      }
      return a.diasParaVencer - b.diasParaVencer;
    });

    return alertas;
  }

  // --------------------------------------------------------------------------
  // HELPER METHODS
  // --------------------------------------------------------------------------

  /**
   * Validar que la entidad existe
   */
  private async validateEntidadExists(
    tenantId: string,
    entidadTipo: TipoEntidadDocumento,
    entidadId: string
  ): Promise<void> {
    if (entidadTipo === TipoEntidadDocumento.UNIDAD || entidadTipo === TipoEntidadDocumento.REMOLQUE) {
      const unidad = await this.unidadRepository.findOne({
        where: { tenantId, id: entidadId },
      });
      if (!unidad) {
        throw new Error(`Unidad/Remolque con ID ${entidadId} no encontrada`);
      }
    } else if (entidadTipo === TipoEntidadDocumento.OPERADOR) {
      const operador = await this.operadorRepository.findOne({
        where: { tenantId, id: entidadId },
      });
      if (!operador) {
        throw new Error(`Operador con ID ${entidadId} no encontrado`);
      }
    }
  }

  /**
   * Obtener nombre de la entidad para mostrar
   */
  private async getEntidadNombre(
    tenantId: string,
    entidadTipo: TipoEntidadDocumento,
    entidadId: string
  ): Promise<string> {
    if (entidadTipo === TipoEntidadDocumento.UNIDAD || entidadTipo === TipoEntidadDocumento.REMOLQUE) {
      const unidad = await this.unidadRepository.findOne({
        where: { tenantId, id: entidadId },
      });
      if (unidad) {
        return `${unidad.numeroEconomico} - ${unidad.marca || ''} ${unidad.modelo || ''}`.trim();
      }
    } else if (entidadTipo === TipoEntidadDocumento.OPERADOR) {
      const operador = await this.operadorRepository.findOne({
        where: { tenantId, id: entidadId },
      });
      if (operador) {
        return `${operador.numeroEmpleado} - ${operador.nombre} ${operador.apellidoPaterno}`;
      }
    }
    return 'Entidad no encontrada';
  }

  /**
   * Determinar si un documento es critico
   */
  private esDocumentoCritico(entidadTipo: TipoEntidadDocumento, tipoDocumento: TipoDocumento): boolean {
    if (entidadTipo === TipoEntidadDocumento.UNIDAD || entidadTipo === TipoEntidadDocumento.REMOLQUE) {
      return DOCUMENTOS_CRITICOS_UNIDAD.includes(tipoDocumento);
    } else if (entidadTipo === TipoEntidadDocumento.OPERADOR) {
      return DOCUMENTOS_CRITICOS_OPERADOR.includes(tipoDocumento);
    }
    return false;
  }

  /**
   * Obtener documentos criticos vencidos con detalles
   */
  private async getDocumentosCriticosVencidos(tenantId: string): Promise<DocumentoVencimiento[]> {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    // Documentos criticos vencidos
    const documentos = await this.documentoRepository.find({
      where: [
        {
          tenantId,
          entidadTipo: In([TipoEntidadDocumento.UNIDAD, TipoEntidadDocumento.REMOLQUE]),
          tipoDocumento: In(DOCUMENTOS_CRITICOS_UNIDAD),
          fechaVencimiento: LessThan(hoy),
          activo: true,
        },
        {
          tenantId,
          entidadTipo: TipoEntidadDocumento.OPERADOR,
          tipoDocumento: In(DOCUMENTOS_CRITICOS_OPERADOR),
          fechaVencimiento: LessThan(hoy),
          activo: true,
        },
      ],
    });

    const result: DocumentoVencimiento[] = [];

    for (const doc of documentos) {
      const diasParaVencer = Math.ceil(
        (doc.fechaVencimiento.getTime() - hoy.getTime()) / (1000 * 60 * 60 * 24)
      );
      const entidadNombre = await this.getEntidadNombre(tenantId, doc.entidadTipo, doc.entidadId);

      result.push({
        documento: doc,
        entidadTipo: doc.entidadTipo,
        entidadId: doc.entidadId,
        entidadNombre,
        diasParaVencer, // Sera negativo si ya vencio
        esCritico: true,
      });
    }

    return result;
  }
}
