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

export type EmployeeStatus = 'active' | 'inactive' | 'on_leave' | 'terminated';

export interface Employee {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  employee_number: string;
  first_name: string;
  last_name: string;
  middle_name?: string;
  full_name?: string;
  user_id?: string;
  birth_date?: Date;
  gender?: string;
  marital_status?: string;
  nationality?: string;
  identification_id?: string;
  identification_type?: string;
  social_security_number?: string;
  tax_id?: string;
  email?: string;
  work_email?: string;
  phone?: string;
  work_phone?: string;
  mobile?: string;
  emergency_contact?: string;
  emergency_phone?: string;
  street?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  department_id?: string;
  department_name?: string;
  job_position_id?: string;
  job_position_name?: string;
  manager_id?: string;
  manager_name?: string;
  hire_date: Date;
  termination_date?: Date;
  status: EmployeeStatus;
  bank_name?: string;
  bank_account?: string;
  bank_clabe?: string;
  photo_url?: string;
  notes?: string;
  created_at: Date;
}

export interface CreateEmployeeDto {
  company_id: string;
  employee_number: string;
  first_name: string;
  last_name: string;
  middle_name?: string;
  user_id?: string;
  birth_date?: string;
  gender?: string;
  marital_status?: string;
  nationality?: string;
  identification_id?: string;
  identification_type?: string;
  social_security_number?: string;
  tax_id?: string;
  email?: string;
  work_email?: string;
  phone?: string;
  work_phone?: string;
  mobile?: string;
  emergency_contact?: string;
  emergency_phone?: string;
  street?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  department_id?: string;
  job_position_id?: string;
  manager_id?: string;
  hire_date: string;
  bank_name?: string;
  bank_account?: string;
  bank_clabe?: string;
  photo_url?: string;
  notes?: string;
}

export interface UpdateEmployeeDto {
  first_name?: string;
  last_name?: string;
  middle_name?: string | null;
  user_id?: string | null;
  birth_date?: string | null;
  gender?: string | null;
  marital_status?: string | null;
  nationality?: string | null;
  identification_id?: string | null;
  identification_type?: string | null;
  social_security_number?: string | null;
  tax_id?: string | null;
  email?: string | null;
  work_email?: string | null;
  phone?: string | null;
  work_phone?: string | null;
  mobile?: string | null;
  emergency_contact?: string | null;
  emergency_phone?: string | null;
  street?: string | null;
  city?: string | null;
  state?: string | null;
  zip?: string | null;
  country?: string | null;
  department_id?: string | null;
  job_position_id?: string | null;
  manager_id?: string | null;
  bank_name?: string | null;
  bank_account?: string | null;
  bank_clabe?: string | null;
  photo_url?: string | null;
  notes?: string | null;
}

export interface EmployeeFilters {
  company_id?: string;
  department_id?: string;
  status?: EmployeeStatus;
  manager_id?: string;
  search?: string;
  page?: number;
  limit?: number;
}

class EmployeesService {
  async findAll(tenantId: string, filters: EmployeeFilters = {}): Promise<{ data: Employee[]; total: number }> {
    const { company_id, department_id, status, manager_id, search, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

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

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

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

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

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

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

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

    params.push(limit, offset);
    const data = await query<Employee>(
      `SELECT e.*,
              CONCAT(e.first_name, ' ', COALESCE(e.middle_name || ' ', ''), e.last_name) as full_name,
              c.name as company_name,
              d.name as department_name,
              j.name as job_position_name,
              CONCAT(m.first_name, ' ', m.last_name) as manager_name
       FROM hr.employees e
       LEFT JOIN auth.companies c ON e.company_id = c.id
       LEFT JOIN hr.departments d ON e.department_id = d.id
       LEFT JOIN hr.job_positions j ON e.job_position_id = j.id
       LEFT JOIN hr.employees m ON e.manager_id = m.id
       ${whereClause}
       ORDER BY e.last_name, e.first_name
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Employee> {
    const employee = await queryOne<Employee>(
      `SELECT e.*,
              CONCAT(e.first_name, ' ', COALESCE(e.middle_name || ' ', ''), e.last_name) as full_name,
              c.name as company_name,
              d.name as department_name,
              j.name as job_position_name,
              CONCAT(m.first_name, ' ', m.last_name) as manager_name
       FROM hr.employees e
       LEFT JOIN auth.companies c ON e.company_id = c.id
       LEFT JOIN hr.departments d ON e.department_id = d.id
       LEFT JOIN hr.job_positions j ON e.job_position_id = j.id
       LEFT JOIN hr.employees m ON e.manager_id = m.id
       WHERE e.id = $1 AND e.tenant_id = $2`,
      [id, tenantId]
    );

    if (!employee) {
      throw new NotFoundError('Empleado no encontrado');
    }

    return employee;
  }

  async create(dto: CreateEmployeeDto, tenantId: string, userId: string): Promise<Employee> {
    // Check unique employee number
    const existing = await queryOne(
      `SELECT id FROM hr.employees WHERE employee_number = $1 AND tenant_id = $2`,
      [dto.employee_number, tenantId]
    );

    if (existing) {
      throw new ConflictError('Ya existe un empleado con ese numero');
    }

    const employee = await queryOne<Employee>(
      `INSERT INTO hr.employees (
        tenant_id, company_id, employee_number, first_name, last_name, middle_name,
        user_id, birth_date, gender, marital_status, nationality, identification_id,
        identification_type, social_security_number, tax_id, email, work_email,
        phone, work_phone, mobile, emergency_contact, emergency_phone, street, city,
        state, zip, country, department_id, job_position_id, manager_id, hire_date,
        bank_name, bank_account, bank_clabe, photo_url, notes, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16,
               $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30,
               $31, $32, $33, $34, $35, $36, $37)
       RETURNING *`,
      [
        tenantId, dto.company_id, dto.employee_number, dto.first_name, dto.last_name,
        dto.middle_name, dto.user_id, dto.birth_date, dto.gender, dto.marital_status,
        dto.nationality, dto.identification_id, dto.identification_type,
        dto.social_security_number, dto.tax_id, dto.email, dto.work_email, dto.phone,
        dto.work_phone, dto.mobile, dto.emergency_contact, dto.emergency_phone,
        dto.street, dto.city, dto.state, dto.zip, dto.country, dto.department_id,
        dto.job_position_id, dto.manager_id, dto.hire_date, dto.bank_name,
        dto.bank_account, dto.bank_clabe, dto.photo_url, dto.notes, userId
      ]
    );

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

  async update(id: string, dto: UpdateEmployeeDto, tenantId: string, userId: string): Promise<Employee> {
    await this.findById(id, tenantId);

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

    const fieldsToUpdate = [
      'first_name', 'last_name', 'middle_name', 'user_id', 'birth_date', 'gender',
      'marital_status', 'nationality', 'identification_id', 'identification_type',
      'social_security_number', 'tax_id', 'email', 'work_email', 'phone', 'work_phone',
      'mobile', 'emergency_contact', 'emergency_phone', 'street', 'city', 'state',
      'zip', 'country', 'department_id', 'job_position_id', 'manager_id',
      'bank_name', 'bank_account', 'bank_clabe', 'photo_url', 'notes'
    ];

    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 this.findById(id, tenantId);
    }

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

    values.push(id, tenantId);

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

    return this.findById(id, tenantId);
  }

  async terminate(id: string, terminationDate: string, tenantId: string, userId: string): Promise<Employee> {
    const employee = await this.findById(id, tenantId);

    if (employee.status === 'terminated') {
      throw new ValidationError('El empleado ya esta dado de baja');
    }

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

    // Also terminate active contracts
    await query(
      `UPDATE hr.contracts SET
         status = 'terminated',
         date_end = $1,
         updated_by = $2,
         updated_at = CURRENT_TIMESTAMP
       WHERE employee_id = $3 AND status = 'active'`,
      [terminationDate, userId, id]
    );

    return this.findById(id, tenantId);
  }

  async reactivate(id: string, tenantId: string, userId: string): Promise<Employee> {
    const employee = await this.findById(id, tenantId);

    if (employee.status !== 'terminated' && employee.status !== 'inactive') {
      throw new ValidationError('Solo se pueden reactivar empleados inactivos o dados de baja');
    }

    await query(
      `UPDATE hr.employees SET
         status = 'active',
         termination_date = NULL,
         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 employee = await this.findById(id, tenantId);

    // Check if employee has contracts
    const hasContracts = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM hr.contracts WHERE employee_id = $1`,
      [id]
    );

    if (parseInt(hasContracts?.count || '0') > 0) {
      throw new ConflictError('No se puede eliminar un empleado con contratos asociados');
    }

    // Check if employee is a manager
    const isManager = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM hr.employees WHERE manager_id = $1`,
      [id]
    );

    if (parseInt(isManager?.count || '0') > 0) {
      throw new ConflictError('No se puede eliminar un empleado que es manager de otros');
    }

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

  // Get subordinates
  async getSubordinates(id: string, tenantId: string): Promise<Employee[]> {
    await this.findById(id, tenantId);

    return query<Employee>(
      `SELECT e.*,
              CONCAT(e.first_name, ' ', COALESCE(e.middle_name || ' ', ''), e.last_name) as full_name,
              d.name as department_name,
              j.name as job_position_name
       FROM hr.employees e
       LEFT JOIN hr.departments d ON e.department_id = d.id
       LEFT JOIN hr.job_positions j ON e.job_position_id = j.id
       WHERE e.manager_id = $1 AND e.tenant_id = $2
       ORDER BY e.last_name, e.first_name`,
      [id, tenantId]
    );
  }
}

export const employeesService = new EmployeesService();
