import { query, queryOne } from '../../config/database.js';
import { NotFoundError, ValidationError, ConflictError } from '../../shared/errors/index.js';

export type LeaveStatus = 'draft' | 'submitted' | 'approved' | 'rejected' | 'cancelled';
export type LeaveType = 'vacation' | 'sick' | 'personal' | 'maternity' | 'paternity' | 'bereavement' | 'unpaid' | 'other';

export interface LeaveTypeConfig {
  id: string;
  tenant_id: string;
  name: string;
  code?: string;
  leave_type: LeaveType;
  requires_approval: boolean;
  max_days?: number;
  is_paid: boolean;
  color?: string;
  active: boolean;
  created_at: Date;
}

export interface Leave {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  employee_id: string;
  employee_name?: string;
  employee_number?: string;
  leave_type_id: string;
  leave_type_name?: string;
  name?: string;
  date_from: Date;
  date_to: Date;
  number_of_days: number;
  status: LeaveStatus;
  description?: string;
  approved_by?: string;
  approved_by_name?: string;
  approved_at?: Date;
  rejection_reason?: string;
  created_at: Date;
}

export interface CreateLeaveTypeDto {
  name: string;
  code?: string;
  leave_type: LeaveType;
  requires_approval?: boolean;
  max_days?: number;
  is_paid?: boolean;
  color?: string;
}

export interface UpdateLeaveTypeDto {
  name?: string;
  code?: string | null;
  requires_approval?: boolean;
  max_days?: number | null;
  is_paid?: boolean;
  color?: string | null;
  active?: boolean;
}

export interface CreateLeaveDto {
  company_id: string;
  employee_id: string;
  leave_type_id: string;
  name?: string;
  date_from: string;
  date_to: string;
  description?: string;
}

export interface UpdateLeaveDto {
  leave_type_id?: string;
  name?: string | null;
  date_from?: string;
  date_to?: string;
  description?: string | null;
}

export interface LeaveFilters {
  company_id?: string;
  employee_id?: string;
  leave_type_id?: string;
  status?: LeaveStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

class LeavesService {
  // ========== LEAVE TYPES ==========

  async getLeaveTypes(tenantId: string, includeInactive = false): Promise<LeaveTypeConfig[]> {
    let whereClause = 'WHERE tenant_id = $1';
    if (!includeInactive) {
      whereClause += ' AND active = TRUE';
    }

    return query<LeaveTypeConfig>(
      `SELECT * FROM hr.leave_types ${whereClause} ORDER BY name`,
      [tenantId]
    );
  }

  async getLeaveTypeById(id: string, tenantId: string): Promise<LeaveTypeConfig> {
    const leaveType = await queryOne<LeaveTypeConfig>(
      `SELECT * FROM hr.leave_types WHERE id = $1 AND tenant_id = $2`,
      [id, tenantId]
    );

    if (!leaveType) {
      throw new NotFoundError('Tipo de ausencia no encontrado');
    }

    return leaveType;
  }

  async createLeaveType(dto: CreateLeaveTypeDto, tenantId: string): Promise<LeaveTypeConfig> {
    const existing = await queryOne(
      `SELECT id FROM hr.leave_types WHERE name = $1 AND tenant_id = $2`,
      [dto.name, tenantId]
    );

    if (existing) {
      throw new ConflictError('Ya existe un tipo de ausencia con ese nombre');
    }

    const leaveType = await queryOne<LeaveTypeConfig>(
      `INSERT INTO hr.leave_types (tenant_id, name, code, leave_type, requires_approval, max_days, is_paid, color)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [
        tenantId, dto.name, dto.code, dto.leave_type,
        dto.requires_approval ?? true, dto.max_days, dto.is_paid ?? true, dto.color
      ]
    );

    return leaveType!;
  }

  async updateLeaveType(id: string, dto: UpdateLeaveTypeDto, tenantId: string): Promise<LeaveTypeConfig> {
    const existing = await this.getLeaveTypeById(id, tenantId);

    if (dto.name && dto.name !== existing.name) {
      const nameExists = await queryOne(
        `SELECT id FROM hr.leave_types WHERE name = $1 AND tenant_id = $2 AND id != $3`,
        [dto.name, tenantId, id]
      );
      if (nameExists) {
        throw new ConflictError('Ya existe un tipo de ausencia con ese nombre');
      }
    }

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    const fieldsToUpdate = ['name', 'code', 'requires_approval', 'max_days', 'is_paid', 'color', 'active'];

    for (const field of fieldsToUpdate) {
      if ((dto as any)[field] !== undefined) {
        updateFields.push(`${field} = $${paramIndex++}`);
        values.push((dto as any)[field]);
      }
    }

    if (updateFields.length === 0) {
      return existing;
    }

    values.push(id, tenantId);

    await query(
      `UPDATE hr.leave_types SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.getLeaveTypeById(id, tenantId);
  }

  async deleteLeaveType(id: string, tenantId: string): Promise<void> {
    await this.getLeaveTypeById(id, tenantId);

    const inUse = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM hr.leaves WHERE leave_type_id = $1`,
      [id]
    );

    if (parseInt(inUse?.count || '0') > 0) {
      throw new ConflictError('No se puede eliminar un tipo de ausencia que esta en uso');
    }

    await query(`DELETE FROM hr.leave_types WHERE id = $1 AND tenant_id = $2`, [id, tenantId]);
  }

  // ========== LEAVES ==========

  async findAll(tenantId: string, filters: LeaveFilters = {}): Promise<{ data: Leave[]; total: number }> {
    const { company_id, employee_id, leave_type_id, status, date_from, date_to, search, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

    let whereClause = 'WHERE l.tenant_id = $1';
    const params: any[] = [tenantId];
    let paramIndex = 2;

    if (company_id) {
      whereClause += ` AND l.company_id = $${paramIndex++}`;
      params.push(company_id);
    }

    if (employee_id) {
      whereClause += ` AND l.employee_id = $${paramIndex++}`;
      params.push(employee_id);
    }

    if (leave_type_id) {
      whereClause += ` AND l.leave_type_id = $${paramIndex++}`;
      params.push(leave_type_id);
    }

    if (status) {
      whereClause += ` AND l.status = $${paramIndex++}`;
      params.push(status);
    }

    if (date_from) {
      whereClause += ` AND l.date_from >= $${paramIndex++}`;
      params.push(date_from);
    }

    if (date_to) {
      whereClause += ` AND l.date_to <= $${paramIndex++}`;
      params.push(date_to);
    }

    if (search) {
      whereClause += ` AND (l.name ILIKE $${paramIndex} OR e.first_name ILIKE $${paramIndex} OR e.last_name ILIKE $${paramIndex})`;
      params.push(`%${search}%`);
      paramIndex++;
    }

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count
       FROM hr.leaves l
       LEFT JOIN hr.employees e ON l.employee_id = e.id
       ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<Leave>(
      `SELECT l.*,
              c.name as company_name,
              CONCAT(e.first_name, ' ', e.last_name) as employee_name,
              e.employee_number,
              lt.name as leave_type_name,
              CONCAT(a.first_name, ' ', a.last_name) as approved_by_name
       FROM hr.leaves l
       LEFT JOIN auth.companies c ON l.company_id = c.id
       LEFT JOIN hr.employees e ON l.employee_id = e.id
       LEFT JOIN hr.leave_types lt ON l.leave_type_id = lt.id
       LEFT JOIN hr.employees a ON l.approved_by = a.user_id
       ${whereClause}
       ORDER BY l.date_from DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

    return {
      data,
      total: parseInt(countResult?.count || '0', 10),
    };
  }

  async findById(id: string, tenantId: string): Promise<Leave> {
    const leave = await queryOne<Leave>(
      `SELECT l.*,
              c.name as company_name,
              CONCAT(e.first_name, ' ', e.last_name) as employee_name,
              e.employee_number,
              lt.name as leave_type_name,
              CONCAT(a.first_name, ' ', a.last_name) as approved_by_name
       FROM hr.leaves l
       LEFT JOIN auth.companies c ON l.company_id = c.id
       LEFT JOIN hr.employees e ON l.employee_id = e.id
       LEFT JOIN hr.leave_types lt ON l.leave_type_id = lt.id
       LEFT JOIN hr.employees a ON l.approved_by = a.user_id
       WHERE l.id = $1 AND l.tenant_id = $2`,
      [id, tenantId]
    );

    if (!leave) {
      throw new NotFoundError('Solicitud de ausencia no encontrada');
    }

    return leave;
  }

  async create(dto: CreateLeaveDto, tenantId: string, userId: string): Promise<Leave> {
    // Calculate number of days
    const startDate = new Date(dto.date_from);
    const endDate = new Date(dto.date_to);
    const diffTime = Math.abs(endDate.getTime() - startDate.getTime());
    const numberOfDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;

    if (numberOfDays <= 0) {
      throw new ValidationError('La fecha de fin debe ser igual o posterior a la fecha de inicio');
    }

    // Check leave type max days
    const leaveType = await this.getLeaveTypeById(dto.leave_type_id, tenantId);
    if (leaveType.max_days && numberOfDays > leaveType.max_days) {
      throw new ValidationError(`Este tipo de ausencia tiene un maximo de ${leaveType.max_days} dias`);
    }

    // Check for overlapping leaves
    const overlap = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM hr.leaves
       WHERE employee_id = $1 AND status IN ('submitted', 'approved')
         AND ((date_from <= $2 AND date_to >= $2) OR (date_from <= $3 AND date_to >= $3)
              OR (date_from >= $2 AND date_to <= $3))`,
      [dto.employee_id, dto.date_from, dto.date_to]
    );

    if (parseInt(overlap?.count || '0') > 0) {
      throw new ValidationError('Ya existe una solicitud de ausencia para estas fechas');
    }

    const leave = await queryOne<Leave>(
      `INSERT INTO hr.leaves (
        tenant_id, company_id, employee_id, leave_type_id, name, date_from, date_to,
        number_of_days, description, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
       RETURNING *`,
      [
        tenantId, dto.company_id, dto.employee_id, dto.leave_type_id, dto.name,
        dto.date_from, dto.date_to, numberOfDays, dto.description, userId
      ]
    );

    return this.findById(leave!.id, tenantId);
  }

  async update(id: string, dto: UpdateLeaveDto, tenantId: string, userId: string): Promise<Leave> {
    const existing = await this.findById(id, tenantId);

    if (existing.status !== 'draft') {
      throw new ValidationError('Solo se pueden editar solicitudes en borrador');
    }

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (dto.leave_type_id !== undefined) {
      updateFields.push(`leave_type_id = $${paramIndex++}`);
      values.push(dto.leave_type_id);
    }
    if (dto.name !== undefined) {
      updateFields.push(`name = $${paramIndex++}`);
      values.push(dto.name);
    }
    if (dto.description !== undefined) {
      updateFields.push(`description = $${paramIndex++}`);
      values.push(dto.description);
    }

    // Recalculate days if dates changed
    let newDateFrom = existing.date_from;
    let newDateTo = existing.date_to;

    if (dto.date_from !== undefined) {
      updateFields.push(`date_from = $${paramIndex++}`);
      values.push(dto.date_from);
      newDateFrom = new Date(dto.date_from);
    }
    if (dto.date_to !== undefined) {
      updateFields.push(`date_to = $${paramIndex++}`);
      values.push(dto.date_to);
      newDateTo = new Date(dto.date_to);
    }

    if (dto.date_from !== undefined || dto.date_to !== undefined) {
      const diffTime = Math.abs(newDateTo.getTime() - newDateFrom.getTime());
      const numberOfDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
      updateFields.push(`number_of_days = $${paramIndex++}`);
      values.push(numberOfDays);
    }

    if (updateFields.length === 0) {
      return existing;
    }

    updateFields.push(`updated_by = $${paramIndex++}`);
    values.push(userId);

    values.push(id, tenantId);

    await query(
      `UPDATE hr.leaves SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.findById(id, tenantId);
  }

  async submit(id: string, tenantId: string, userId: string): Promise<Leave> {
    const leave = await this.findById(id, tenantId);

    if (leave.status !== 'draft') {
      throw new ValidationError('Solo se pueden enviar solicitudes en borrador');
    }

    await query(
      `UPDATE hr.leaves SET
         status = 'submitted',
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async approve(id: string, tenantId: string, userId: string): Promise<Leave> {
    const leave = await this.findById(id, tenantId);

    if (leave.status !== 'submitted') {
      throw new ValidationError('Solo se pueden aprobar solicitudes enviadas');
    }

    await query(
      `UPDATE hr.leaves SET
         status = 'approved',
         approved_by = $1,
         approved_at = CURRENT_TIMESTAMP,
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    // Update employee status if leave starts today or earlier
    const today = new Date().toISOString().split('T')[0];
    if (leave.date_from.toISOString().split('T')[0] <= today && leave.date_to.toISOString().split('T')[0] >= today) {
      await query(
        `UPDATE hr.employees SET status = 'on_leave' WHERE id = $1`,
        [leave.employee_id]
      );
    }

    return this.findById(id, tenantId);
  }

  async reject(id: string, reason: string, tenantId: string, userId: string): Promise<Leave> {
    const leave = await this.findById(id, tenantId);

    if (leave.status !== 'submitted') {
      throw new ValidationError('Solo se pueden rechazar solicitudes enviadas');
    }

    await query(
      `UPDATE hr.leaves SET
         status = 'rejected',
         rejection_reason = $1,
         updated_by = $2,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $3 AND tenant_id = $4`,
      [reason, userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async cancel(id: string, tenantId: string, userId: string): Promise<Leave> {
    const leave = await this.findById(id, tenantId);

    if (leave.status === 'cancelled') {
      throw new ValidationError('La solicitud ya esta cancelada');
    }

    if (leave.status === 'rejected') {
      throw new ValidationError('No se puede cancelar una solicitud rechazada');
    }

    await query(
      `UPDATE hr.leaves SET
         status = 'cancelled',
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async delete(id: string, tenantId: string): Promise<void> {
    const leave = await this.findById(id, tenantId);

    if (leave.status !== 'draft') {
      throw new ValidationError('Solo se pueden eliminar solicitudes en borrador');
    }

    await query(`DELETE FROM hr.leaves WHERE id = $1 AND tenant_id = $2`, [id, tenantId]);
  }
}

export const leavesService = new LeavesService();
