/**
 * Turno Service
 * ERP Transportistas
 *
 * Business logic for operator shift management.
 * Module: MAI-005 Despacho
 * Adapted from: erp-mecanicas-diesel MMD-011 ShiftService
 */

import { Repository, DataSource } from 'typeorm';
import { TurnoOperador, TipoTurno } from '../entities/turno-operador.entity';

// DTOs
export interface CreateTurnoDto {
  operadorId: string;
  fechaTurno: Date;
  tipoTurno: TipoTurno;
  horaInicio: string;
  horaFin: string;
  enGuardia?: boolean;
  prioridadGuardia?: number;
  unidadAsignadaId?: string;
  notas?: string;
  createdBy?: string;
}

export interface UpdateTurnoDto {
  tipoTurno?: TipoTurno;
  horaInicio?: string;
  horaFin?: string;
  enGuardia?: boolean;
  prioridadGuardia?: number;
  unidadAsignadaId?: string;
  ausente?: boolean;
  motivoAusencia?: string;
  notas?: string;
}

export interface FiltrosTurno {
  operadorId?: string;
  tipoTurno?: TipoTurno;
  fechaDesde?: Date;
  fechaHasta?: Date;
  enGuardia?: boolean;
  unidadAsignadaId?: string;
  ausente?: boolean;
}

export interface DisponibilidadOperador {
  operadorId: string;
  turno?: TurnoOperador;
  disponible: boolean;
  enGuardia: boolean;
  razon?: string;
}

export class TurnoService {
  private turnoRepository: Repository<TurnoOperador>;

  constructor(dataSource: DataSource) {
    this.turnoRepository = dataSource.getRepository(TurnoOperador);
  }

  /**
   * Create a new shift
   */
  async crearTurno(tenantId: string, dto: CreateTurnoDto): Promise<TurnoOperador> {
    // Check for overlapping shifts
    const existente = await this.turnoRepository.findOne({
      where: {
        tenantId,
        operadorId: dto.operadorId,
        fechaTurno: dto.fechaTurno,
      },
    });

    if (existente) {
      throw new Error(
        `Operador ${dto.operadorId} ya tiene turno el ${dto.fechaTurno}`
      );
    }

    const turno = this.turnoRepository.create({
      tenantId,
      ...dto,
    });

    return this.turnoRepository.save(turno);
  }

  /**
   * Get shift by ID
   */
  async getById(tenantId: string, id: string): Promise<TurnoOperador | null> {
    return this.turnoRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Update shift
   */
  async actualizarTurno(
    tenantId: string,
    id: string,
    dto: UpdateTurnoDto
  ): Promise<TurnoOperador | null> {
    const turno = await this.getById(tenantId, id);
    if (!turno) return null;

    Object.assign(turno, dto);
    return this.turnoRepository.save(turno);
  }

  /**
   * Delete shift
   */
  async eliminarTurno(tenantId: string, id: string): Promise<boolean> {
    const result = await this.turnoRepository.delete({ id, tenantId });
    return (result.affected || 0) > 0;
  }

  /**
   * Get shifts for a specific date
   */
  async getTurnosPorFecha(
    tenantId: string,
    fecha: Date,
    excluirAusentes: boolean = true
  ): Promise<TurnoOperador[]> {
    const qb = this.turnoRepository
      .createQueryBuilder('turno')
      .where('turno.tenant_id = :tenantId', { tenantId })
      .andWhere('turno.fecha_turno = :fecha', { fecha });

    if (excluirAusentes) {
      qb.andWhere('turno.ausente = :ausente', { ausente: false });
    }

    return qb.orderBy('turno.hora_inicio', 'ASC').getMany();
  }

  /**
   * Get operator's shifts
   */
  async getTurnosOperador(
    tenantId: string,
    operadorId: string,
    fechaDesde?: Date,
    fechaHasta?: Date
  ): Promise<TurnoOperador[]> {
    const qb = this.turnoRepository
      .createQueryBuilder('turno')
      .where('turno.tenant_id = :tenantId', { tenantId })
      .andWhere('turno.operador_id = :operadorId', { operadorId });

    if (fechaDesde) {
      qb.andWhere('turno.fecha_turno >= :fechaDesde', { fechaDesde });
    }
    if (fechaHasta) {
      qb.andWhere('turno.fecha_turno <= :fechaHasta', { fechaHasta });
    }

    return qb.orderBy('turno.fecha_turno', 'ASC').getMany();
  }

  /**
   * Get available operators for a specific date and time
   */
  async getOperadoresDisponibles(
    tenantId: string,
    fecha: Date,
    hora?: string
  ): Promise<DisponibilidadOperador[]> {
    const turnos = await this.getTurnosPorFecha(tenantId, fecha, true);

    return turnos
      .filter((turno) => {
        if (hora) {
          // Check if time is within shift hours
          return hora >= turno.horaInicio && hora <= turno.horaFin;
        }
        return true;
      })
      .map((turno) => ({
        operadorId: turno.operadorId,
        turno,
        disponible: !turno.ausente,
        enGuardia: turno.enGuardia,
      }));
  }

  /**
   * Get on-call operators for a date
   */
  async getOperadoresEnGuardia(
    tenantId: string,
    fecha: Date
  ): Promise<TurnoOperador[]> {
    return this.turnoRepository.find({
      where: {
        tenantId,
        fechaTurno: fecha,
        enGuardia: true,
        ausente: false,
      },
      order: { prioridadGuardia: 'ASC' },
    });
  }

  /**
   * Mark operator as started shift
   */
  async iniciarTurno(tenantId: string, id: string): Promise<TurnoOperador | null> {
    const turno = await this.getById(tenantId, id);
    if (!turno) return null;

    turno.horaInicioReal = new Date();
    return this.turnoRepository.save(turno);
  }

  /**
   * Mark operator as ended shift
   */
  async finalizarTurno(tenantId: string, id: string): Promise<TurnoOperador | null> {
    const turno = await this.getById(tenantId, id);
    if (!turno) return null;

    turno.horaFinReal = new Date();
    return this.turnoRepository.save(turno);
  }

  /**
   * Mark operator as absent
   */
  async marcarAusente(
    tenantId: string,
    id: string,
    motivo: string
  ): Promise<TurnoOperador | null> {
    const turno = await this.getById(tenantId, id);
    if (!turno) return null;

    turno.ausente = true;
    turno.motivoAusencia = motivo;
    return this.turnoRepository.save(turno);
  }

  /**
   * Assign unit to shift
   */
  async asignarUnidadATurno(
    tenantId: string,
    id: string,
    unidadId: string
  ): Promise<TurnoOperador | null> {
    const turno = await this.getById(tenantId, id);
    if (!turno) return null;

    turno.unidadAsignadaId = unidadId;
    return this.turnoRepository.save(turno);
  }

  /**
   * Get shifts by unit
   */
  async getTurnosPorUnidad(
    tenantId: string,
    unidadId: string,
    fecha: Date
  ): Promise<TurnoOperador[]> {
    return this.turnoRepository.find({
      where: {
        tenantId,
        unidadAsignadaId: unidadId,
        fechaTurno: fecha,
        ausente: false,
      },
      order: { horaInicio: 'ASC' },
    });
  }

  /**
   * Generate bulk shifts for a week
   */
  async generarTurnosSemanales(
    tenantId: string,
    operadorId: string,
    fechaInicioSemana: Date,
    tipoTurno: TipoTurno,
    horaInicio: string,
    horaFin: string,
    diasSemana: number[], // 0 = Sunday, 1 = Monday, etc.
    createdBy?: string
  ): Promise<TurnoOperador[]> {
    const turnos: TurnoOperador[] = [];

    for (let i = 0; i < 7; i++) {
      const fecha = new Date(fechaInicioSemana);
      fecha.setDate(fecha.getDate() + i);
      const diaSemana = fecha.getDay();

      if (diasSemana.includes(diaSemana)) {
        try {
          const turno = await this.crearTurno(tenantId, {
            operadorId,
            fechaTurno: fecha,
            tipoTurno,
            horaInicio,
            horaFin,
            createdBy,
          });
          turnos.push(turno);
        } catch {
          // Skip if shift already exists
        }
      }
    }

    return turnos;
  }

  /**
   * List shifts with filters
   */
  async listar(
    tenantId: string,
    filtros: FiltrosTurno = {},
    paginacion = { pagina: 1, limite: 20 }
  ) {
    const qb = this.turnoRepository
      .createQueryBuilder('turno')
      .where('turno.tenant_id = :tenantId', { tenantId });

    if (filtros.operadorId) {
      qb.andWhere('turno.operador_id = :operadorId', {
        operadorId: filtros.operadorId,
      });
    }
    if (filtros.tipoTurno) {
      qb.andWhere('turno.tipo_turno = :tipoTurno', { tipoTurno: filtros.tipoTurno });
    }
    if (filtros.fechaDesde) {
      qb.andWhere('turno.fecha_turno >= :fechaDesde', { fechaDesde: filtros.fechaDesde });
    }
    if (filtros.fechaHasta) {
      qb.andWhere('turno.fecha_turno <= :fechaHasta', { fechaHasta: filtros.fechaHasta });
    }
    if (filtros.enGuardia !== undefined) {
      qb.andWhere('turno.en_guardia = :enGuardia', { enGuardia: filtros.enGuardia });
    }
    if (filtros.unidadAsignadaId) {
      qb.andWhere('turno.unidad_asignada_id = :unidadAsignadaId', {
        unidadAsignadaId: filtros.unidadAsignadaId,
      });
    }
    if (filtros.ausente !== undefined) {
      qb.andWhere('turno.ausente = :ausente', { ausente: filtros.ausente });
    }

    const skip = (paginacion.pagina - 1) * paginacion.limite;

    const [data, total] = await qb
      .orderBy('turno.fecha_turno', 'DESC')
      .addOrderBy('turno.hora_inicio', 'ASC')
      .skip(skip)
      .take(paginacion.limite)
      .getManyAndCount();

    return {
      data,
      total,
      pagina: paginacion.pagina,
      limite: paginacion.limite,
      totalPaginas: Math.ceil(total / paginacion.limite),
    };
  }
}
