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

export interface Department {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  code?: string;
  parent_id?: string;
  parent_name?: string;
  manager_id?: string;
  manager_name?: string;
  description?: string;
  color?: string;
  active: boolean;
  employee_count?: number;
  created_at: Date;
}

export interface CreateDepartmentDto {
  company_id: string;
  name: string;
  code?: string;
  parent_id?: string;
  manager_id?: string;
  description?: string;
  color?: string;
}

export interface UpdateDepartmentDto {
  name?: string;
  code?: string | null;
  parent_id?: string | null;
  manager_id?: string | null;
  description?: string | null;
  color?: string | null;
  active?: boolean;
}

export interface DepartmentFilters {
  company_id?: string;
  active?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

// Job Position interfaces
export interface JobPosition {
  id: string;
  tenant_id: string;
  name: string;
  department_id?: string;
  department_name?: string;
  description?: string;
  requirements?: string;
  responsibilities?: string;
  min_salary?: number;
  max_salary?: number;
  active: boolean;
  employee_count?: number;
  created_at: Date;
}

export interface CreateJobPositionDto {
  name: string;
  department_id?: string;
  description?: string;
  requirements?: string;
  responsibilities?: string;
  min_salary?: number;
  max_salary?: number;
}

export interface UpdateJobPositionDto {
  name?: string;
  department_id?: string | null;
  description?: string | null;
  requirements?: string | null;
  responsibilities?: string | null;
  min_salary?: number | null;
  max_salary?: number | null;
  active?: boolean;
}

class DepartmentsService {
  // ========== DEPARTMENTS ==========

  async findAll(tenantId: string, filters: DepartmentFilters = {}): Promise<{ data: Department[]; total: number }> {
    const { company_id, active, search, page = 1, limit = 50 } = filters;
    const offset = (page - 1) * limit;

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

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

    if (active !== undefined) {
      whereClause += ` AND d.active = $${paramIndex++}`;
      params.push(active);
    }

    if (search) {
      whereClause += ` AND (d.name ILIKE $${paramIndex} OR d.code ILIKE $${paramIndex})`;
      params.push(`%${search}%`);
      paramIndex++;
    }

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

    params.push(limit, offset);
    const data = await query<Department>(
      `SELECT d.*,
              c.name as company_name,
              p.name as parent_name,
              CONCAT(m.first_name, ' ', m.last_name) as manager_name,
              COALESCE(ec.employee_count, 0) as employee_count
       FROM hr.departments d
       LEFT JOIN auth.companies c ON d.company_id = c.id
       LEFT JOIN hr.departments p ON d.parent_id = p.id
       LEFT JOIN hr.employees m ON d.manager_id = m.id
       LEFT JOIN (
         SELECT department_id, COUNT(*) as employee_count
         FROM hr.employees
         WHERE status = 'active'
         GROUP BY department_id
       ) ec ON d.id = ec.department_id
       ${whereClause}
       ORDER BY d.name
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Department> {
    const department = await queryOne<Department>(
      `SELECT d.*,
              c.name as company_name,
              p.name as parent_name,
              CONCAT(m.first_name, ' ', m.last_name) as manager_name,
              COALESCE(ec.employee_count, 0) as employee_count
       FROM hr.departments d
       LEFT JOIN auth.companies c ON d.company_id = c.id
       LEFT JOIN hr.departments p ON d.parent_id = p.id
       LEFT JOIN hr.employees m ON d.manager_id = m.id
       LEFT JOIN (
         SELECT department_id, COUNT(*) as employee_count
         FROM hr.employees
         WHERE status = 'active'
         GROUP BY department_id
       ) ec ON d.id = ec.department_id
       WHERE d.id = $1 AND d.tenant_id = $2`,
      [id, tenantId]
    );

    if (!department) {
      throw new NotFoundError('Departamento no encontrado');
    }

    return department;
  }

  async create(dto: CreateDepartmentDto, tenantId: string, userId: string): Promise<Department> {
    // Check unique name within company
    const existing = await queryOne(
      `SELECT id FROM hr.departments WHERE name = $1 AND company_id = $2 AND tenant_id = $3`,
      [dto.name, dto.company_id, tenantId]
    );

    if (existing) {
      throw new ConflictError('Ya existe un departamento con ese nombre en esta empresa');
    }

    const department = await queryOne<Department>(
      `INSERT INTO hr.departments (tenant_id, company_id, name, code, parent_id, manager_id, description, color, created_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
       RETURNING *`,
      [tenantId, dto.company_id, dto.name, dto.code, dto.parent_id, dto.manager_id, dto.description, dto.color, userId]
    );

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

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

    // Check unique name if changing
    if (dto.name && dto.name !== existing.name) {
      const nameExists = await queryOne(
        `SELECT id FROM hr.departments WHERE name = $1 AND company_id = $2 AND tenant_id = $3 AND id != $4`,
        [dto.name, existing.company_id, tenantId, id]
      );
      if (nameExists) {
        throw new ConflictError('Ya existe un departamento con ese nombre en esta empresa');
      }
    }

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

    const fieldsToUpdate = ['name', 'code', 'parent_id', 'manager_id', 'description', '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.departments SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.findById(id, tenantId);
  }

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

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

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

    // Check if department has children
    const hasChildren = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM hr.departments WHERE parent_id = $1`,
      [id]
    );

    if (parseInt(hasChildren?.count || '0') > 0) {
      throw new ConflictError('No se puede eliminar un departamento con subdepartamentos');
    }

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

  // ========== JOB POSITIONS ==========

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

    return query<JobPosition>(
      `SELECT j.*,
              d.name as department_name,
              COALESCE(ec.employee_count, 0) as employee_count
       FROM hr.job_positions j
       LEFT JOIN hr.departments d ON j.department_id = d.id
       LEFT JOIN (
         SELECT job_position_id, COUNT(*) as employee_count
         FROM hr.employees
         WHERE status = 'active'
         GROUP BY job_position_id
       ) ec ON j.id = ec.job_position_id
       ${whereClause}
       ORDER BY j.name`,
      [tenantId]
    );
  }

  async getJobPositionById(id: string, tenantId: string): Promise<JobPosition> {
    const position = await queryOne<JobPosition>(
      `SELECT j.*,
              d.name as department_name,
              COALESCE(ec.employee_count, 0) as employee_count
       FROM hr.job_positions j
       LEFT JOIN hr.departments d ON j.department_id = d.id
       LEFT JOIN (
         SELECT job_position_id, COUNT(*) as employee_count
         FROM hr.employees
         WHERE status = 'active'
         GROUP BY job_position_id
       ) ec ON j.id = ec.job_position_id
       WHERE j.id = $1 AND j.tenant_id = $2`,
      [id, tenantId]
    );

    if (!position) {
      throw new NotFoundError('Puesto no encontrado');
    }

    return position;
  }

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

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

    const position = await queryOne<JobPosition>(
      `INSERT INTO hr.job_positions (tenant_id, name, department_id, description, requirements, responsibilities, min_salary, max_salary)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [tenantId, dto.name, dto.department_id, dto.description, dto.requirements, dto.responsibilities, dto.min_salary, dto.max_salary]
    );

    return position!;
  }

  async updateJobPosition(id: string, dto: UpdateJobPositionDto, tenantId: string): Promise<JobPosition> {
    const existing = await this.getJobPositionById(id, tenantId);

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

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

    const fieldsToUpdate = ['name', 'department_id', 'description', 'requirements', 'responsibilities', 'min_salary', 'max_salary', '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.job_positions SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.getJobPositionById(id, tenantId);
  }

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

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

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

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

export const departmentsService = new DepartmentsService();
