import { Repository, IsNull, Not } from 'typeorm';
import { Asignacion } from '../entities/asignacion.entity';
import { Unidad, EstadoUnidad } from '../entities/unidad.entity';
import { Operador, EstadoOperador } from '../entities/operador.entity';
import { DocumentoFlota, TipoDocumento, TipoEntidadDocumento } from '../entities/documento-flota.entity';

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

export interface CreateAsignacionDto {
  remolqueId?: string;
  motivo?: string;
  fechaInicio?: Date;
}

export interface AsignacionHistorial {
  asignacion: Asignacion;
  unidad: Unidad;
  operador: Operador;
  duracionDias: number;
}

export interface DisponibilidadResult {
  unidadDisponible: boolean;
  operadorDisponible: boolean;
  unidadMotivo?: string;
  operadorMotivo?: string;
  documentosVencidosUnidad: string[];
  documentosVencidosOperador: string[];
  puedeAsignar: boolean;
}

// Documentos criticos que bloquean asignacion
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 AsignacionService {
  constructor(
    private readonly asignacionRepository: Repository<Asignacion>,
    private readonly unidadRepository: Repository<Unidad>,
    private readonly operadorRepository: Repository<Operador>,
    private readonly documentoRepository: Repository<DocumentoFlota>
  ) {}

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

  /**
   * Crear asignacion de unidad a operador
   */
  async create(
    tenantId: string,
    unidadId: string,
    operadorId: string,
    dto: CreateAsignacionDto,
    createdById: string
  ): Promise<Asignacion> {
    // Validar disponibilidad de ambos
    const disponibilidad = await this.validateDisponibilidad(tenantId, unidadId, operadorId);

    if (!disponibilidad.puedeAsignar) {
      const motivos: string[] = [];
      if (!disponibilidad.unidadDisponible) {
        motivos.push(`Unidad: ${disponibilidad.unidadMotivo}`);
      }
      if (!disponibilidad.operadorDisponible) {
        motivos.push(`Operador: ${disponibilidad.operadorMotivo}`);
      }
      if (disponibilidad.documentosVencidosUnidad.length > 0) {
        motivos.push(`Documentos vencidos de unidad: ${disponibilidad.documentosVencidosUnidad.join(', ')}`);
      }
      if (disponibilidad.documentosVencidosOperador.length > 0) {
        motivos.push(`Documentos vencidos de operador: ${disponibilidad.documentosVencidosOperador.join(', ')}`);
      }
      throw new Error(`No se puede crear la asignacion: ${motivos.join('; ')}`);
    }

    // Verificar que no haya asignacion activa para la unidad
    const asignacionExistenteUnidad = await this.asignacionRepository.findOne({
      where: { tenantId, unidadId, activa: true },
    });
    if (asignacionExistenteUnidad) {
      throw new Error('La unidad ya tiene un operador asignado. Termine la asignacion actual primero.');
    }

    // Verificar que el operador no tenga asignacion activa
    const asignacionExistenteOperador = await this.asignacionRepository.findOne({
      where: { tenantId, operadorId, activa: true },
    });
    if (asignacionExistenteOperador) {
      throw new Error('El operador ya tiene una unidad asignada. Termine la asignacion actual primero.');
    }

    // Obtener unidad y operador
    const unidad = await this.unidadRepository.findOne({ where: { tenantId, id: unidadId } });
    const operador = await this.operadorRepository.findOne({ where: { tenantId, id: operadorId } });

    if (!unidad) throw new Error(`Unidad con ID ${unidadId} no encontrada`);
    if (!operador) throw new Error(`Operador con ID ${operadorId} no encontrado`);

    // Crear asignacion
    const asignacion = this.asignacionRepository.create({
      tenantId,
      unidadId,
      operadorId,
      remolqueId: dto.remolqueId,
      fechaInicio: dto.fechaInicio || new Date(),
      activa: true,
      motivo: dto.motivo,
      createdById,
    });

    const savedAsignacion = await this.asignacionRepository.save(asignacion);

    // Actualizar unidad asignada en operador
    operador.unidadAsignadaId = unidadId;
    await this.operadorRepository.save(operador);

    return savedAsignacion;
  }

  /**
   * Buscar asignacion por ID
   */
  async findById(tenantId: string, id: string): Promise<Asignacion | null> {
    return this.asignacionRepository.findOne({
      where: { tenantId, id },
      relations: ['unidad', 'operador'],
    });
  }

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

  /**
   * Buscar historial de asignaciones de una unidad
   */
  async findByUnidad(tenantId: string, unidadId: string): Promise<Asignacion[]> {
    return this.asignacionRepository.find({
      where: { tenantId, unidadId },
      relations: ['operador'],
      order: { fechaInicio: 'DESC' },
    });
  }

  /**
   * Buscar historial de asignaciones de un operador
   */
  async findByOperador(tenantId: string, operadorId: string): Promise<Asignacion[]> {
    return this.asignacionRepository.find({
      where: { tenantId, operadorId },
      relations: ['unidad'],
      order: { fechaInicio: 'DESC' },
    });
  }

  /**
   * Obtener todas las asignaciones activas
   */
  async findActivas(tenantId: string): Promise<Asignacion[]> {
    return this.asignacionRepository.find({
      where: { tenantId, activa: true },
      relations: ['unidad', 'operador'],
      order: { fechaInicio: 'DESC' },
    });
  }

  /**
   * Obtener asignacion actual de una unidad
   */
  async getAsignacionActual(tenantId: string, unidadId: string): Promise<Asignacion | null> {
    return this.asignacionRepository.findOne({
      where: { tenantId, unidadId, activa: true },
      relations: ['operador'],
    });
  }

  /**
   * Terminar asignacion
   */
  async terminar(
    tenantId: string,
    id: string,
    motivo: string,
    terminadoPorId: string
  ): Promise<Asignacion> {
    const asignacion = await this.findByIdOrFail(tenantId, id);

    if (!asignacion.activa) {
      throw new Error('La asignacion ya esta terminada');
    }

    // Verificar que la unidad no este en viaje
    const unidad = await this.unidadRepository.findOne({
      where: { tenantId, id: asignacion.unidadId },
    });

    if (unidad && (unidad.estado === EstadoUnidad.EN_VIAJE || unidad.estado === EstadoUnidad.EN_RUTA)) {
      throw new Error('No se puede terminar la asignacion de una unidad en viaje');
    }

    // Terminar asignacion
    asignacion.activa = false;
    asignacion.fechaFin = new Date();
    asignacion.motivo = motivo;

    await this.asignacionRepository.save(asignacion);

    // Actualizar operador
    const operador = await this.operadorRepository.findOne({
      where: { tenantId, id: asignacion.operadorId },
    });

    if (operador) {
      operador.unidadAsignadaId = undefined as unknown as string;
      await this.operadorRepository.save(operador);
    }

    return asignacion;
  }

  /**
   * Transferir unidad a nuevo operador
   */
  async transferir(
    tenantId: string,
    unidadId: string,
    nuevoOperadorId: string,
    motivo: string,
    transferidoPorId: string
  ): Promise<Asignacion> {
    // 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`);
    }

    // Verificar que la unidad no este en viaje
    if (unidad.estado === EstadoUnidad.EN_VIAJE || unidad.estado === EstadoUnidad.EN_RUTA) {
      throw new Error('No se puede transferir una unidad en viaje');
    }

    // Terminar asignacion actual si existe
    const asignacionActual = await this.getAsignacionActual(tenantId, unidadId);
    if (asignacionActual) {
      await this.terminar(tenantId, asignacionActual.id, `Transferencia: ${motivo}`, transferidoPorId);
    }

    // Crear nueva asignacion
    return this.create(
      tenantId,
      unidadId,
      nuevoOperadorId,
      { motivo: `Transferencia: ${motivo}` },
      transferidoPorId
    );
  }

  /**
   * Obtener historial completo de asignaciones de una unidad
   */
  async getHistorial(tenantId: string, unidadId: string): Promise<AsignacionHistorial[]> {
    const asignaciones = await this.asignacionRepository.find({
      where: { tenantId, unidadId },
      relations: ['unidad', 'operador'],
      order: { fechaInicio: 'DESC' },
    });

    const historial: AsignacionHistorial[] = [];

    for (const asignacion of asignaciones) {
      const fechaFin = asignacion.fechaFin || new Date();
      const duracionMs = fechaFin.getTime() - asignacion.fechaInicio.getTime();
      const duracionDias = Math.ceil(duracionMs / (1000 * 60 * 60 * 24));

      historial.push({
        asignacion,
        unidad: asignacion.unidad,
        operador: asignacion.operador,
        duracionDias,
      });
    }

    return historial;
  }

  /**
   * Validar disponibilidad de unidad y operador para asignacion
   */
  async validateDisponibilidad(
    tenantId: string,
    unidadId: string,
    operadorId: string
  ): Promise<DisponibilidadResult> {
    const result: DisponibilidadResult = {
      unidadDisponible: true,
      operadorDisponible: true,
      documentosVencidosUnidad: [],
      documentosVencidosOperador: [],
      puedeAsignar: true,
    };

    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    // Verificar unidad
    const unidad = await this.unidadRepository.findOne({
      where: { tenantId, id: unidadId },
    });

    if (!unidad) {
      result.unidadDisponible = false;
      result.unidadMotivo = 'Unidad no encontrada';
      result.puedeAsignar = false;
    } else if (!unidad.activo) {
      result.unidadDisponible = false;
      result.unidadMotivo = 'Unidad no activa';
      result.puedeAsignar = false;
    } else if (unidad.estado !== EstadoUnidad.DISPONIBLE) {
      result.unidadDisponible = false;
      result.unidadMotivo = `Unidad en estado ${unidad.estado}`;
      result.puedeAsignar = false;
    }

    // Verificar operador
    const operador = await this.operadorRepository.findOne({
      where: { tenantId, id: operadorId },
    });

    if (!operador) {
      result.operadorDisponible = false;
      result.operadorMotivo = 'Operador no encontrado';
      result.puedeAsignar = false;
    } else if (!operador.activo) {
      result.operadorDisponible = false;
      result.operadorMotivo = 'Operador no activo';
      result.puedeAsignar = false;
    } else if (
      operador.estado !== EstadoOperador.DISPONIBLE &&
      operador.estado !== EstadoOperador.ACTIVO
    ) {
      result.operadorDisponible = false;
      result.operadorMotivo = `Operador en estado ${operador.estado}`;
      result.puedeAsignar = false;
    }

    // Verificar documentos criticos de la unidad
    if (unidad) {
      const docsUnidad = await this.documentoRepository.find({
        where: {
          tenantId,
          entidadId: unidadId,
          entidadTipo: TipoEntidadDocumento.UNIDAD,
          activo: true,
        },
      });

      for (const tipoDoc of DOCUMENTOS_CRITICOS_UNIDAD) {
        const doc = docsUnidad.find(d => d.tipoDocumento === tipoDoc);
        if (doc && doc.fechaVencimiento && doc.fechaVencimiento < hoy) {
          result.documentosVencidosUnidad.push(tipoDoc);
          result.puedeAsignar = false;
        }
      }

      // Verificar fechas en la entidad unidad
      if (unidad.fechaPolizaVencimiento && unidad.fechaPolizaVencimiento < hoy) {
        if (!result.documentosVencidosUnidad.includes(TipoDocumento.POLIZA_SEGURO)) {
          result.documentosVencidosUnidad.push(TipoDocumento.POLIZA_SEGURO);
          result.puedeAsignar = false;
        }
      }
      if (unidad.fechaVerificacionProxima && unidad.fechaVerificacionProxima < hoy) {
        if (!result.documentosVencidosUnidad.includes(TipoDocumento.VERIFICACION)) {
          result.documentosVencidosUnidad.push(TipoDocumento.VERIFICACION);
          result.puedeAsignar = false;
        }
      }
      if (unidad.fechaPermisoVencimiento && unidad.fechaPermisoVencimiento < hoy) {
        if (!result.documentosVencidosUnidad.includes(TipoDocumento.PERMISO_SCT)) {
          result.documentosVencidosUnidad.push(TipoDocumento.PERMISO_SCT);
          result.puedeAsignar = false;
        }
      }
    }

    // Verificar documentos criticos del operador
    if (operador) {
      const docsOperador = await this.documentoRepository.find({
        where: {
          tenantId,
          entidadId: operadorId,
          entidadTipo: TipoEntidadDocumento.OPERADOR,
          activo: true,
        },
      });

      for (const tipoDoc of DOCUMENTOS_CRITICOS_OPERADOR) {
        const doc = docsOperador.find(d => d.tipoDocumento === tipoDoc);
        if (doc && doc.fechaVencimiento && doc.fechaVencimiento < hoy) {
          result.documentosVencidosOperador.push(tipoDoc);
          result.puedeAsignar = false;
        }
      }

      // Verificar fechas en la entidad operador
      if (operador.licenciaVigencia && operador.licenciaVigencia < hoy) {
        if (!result.documentosVencidosOperador.includes(TipoDocumento.LICENCIA)) {
          result.documentosVencidosOperador.push(TipoDocumento.LICENCIA);
          result.puedeAsignar = false;
        }
      }
      if (operador.certificadoFisicoVigencia && operador.certificadoFisicoVigencia < hoy) {
        if (!result.documentosVencidosOperador.includes(TipoDocumento.CERTIFICADO_FISICO)) {
          result.documentosVencidosOperador.push(TipoDocumento.CERTIFICADO_FISICO);
          result.puedeAsignar = false;
        }
      }
      if (operador.antidopingVigencia && operador.antidopingVigencia < hoy) {
        if (!result.documentosVencidosOperador.includes(TipoDocumento.ANTIDOPING)) {
          result.documentosVencidosOperador.push(TipoDocumento.ANTIDOPING);
          result.puedeAsignar = false;
        }
      }
    }

    return result;
  }
}
