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

// ============================================================================
// TYPES
// ============================================================================

export interface EmployeeInfo {
  employee_id: string;
  employee_number: string;
  employee_name: string;
  user_id: string;
  department_id?: string;
  department_name?: string;
  job_position_id?: string;
  job_position_name?: string;
  status: string;
}

export interface EmployeeCostRate {
  employee_id: string;
  employee_name: string;
  user_id: string;
  hourly_cost: number;
  monthly_cost: number;
  currency_id?: string;
  currency_code?: string;
  source: 'contract' | 'cost_rate' | 'default';
  effective_date?: Date;
}

export interface EmployeeTimesheetSummary {
  employee_id: string;
  employee_name: string;
  user_id: string;
  project_id: string;
  project_name: string;
  total_hours: number;
  billable_hours: number;
  non_billable_hours: number;
  billable_amount: number;
  cost_amount: number;
  margin: number;
  margin_percent: number;
}

export interface ProjectProfitability {
  project_id: string;
  project_name: string;
  partner_id?: string;
  partner_name?: string;
  total_hours: number;
  billable_hours: number;
  billable_revenue: number;
  employee_cost: number;
  gross_margin: number;
  margin_percent: number;
  by_employee: {
    employee_id: string;
    employee_name: string;
    hours: number;
    billable_hours: number;
    revenue: number;
    cost: number;
    margin: number;
  }[];
}

export interface CreateCostRateDto {
  company_id: string;
  employee_id: string;
  hourly_cost: number;
  currency_id: string;
  effective_from?: string;
  effective_to?: string;
}

// ============================================================================
// SERVICE
// ============================================================================

class HRIntegrationService {
  // --------------------------------------------------------------------------
  // EMPLOYEE LOOKUPS
  // --------------------------------------------------------------------------

  /**
   * Get employee by user_id
   */
  async getEmployeeByUserId(
    userId: string,
    tenantId: string
  ): Promise<EmployeeInfo | null> {
    const employee = await queryOne<EmployeeInfo>(
      `SELECT e.id as employee_id, e.employee_number,
              CONCAT(e.first_name, ' ', e.last_name) as employee_name,
              e.user_id, e.department_id, d.name as department_name,
              e.job_position_id, j.name as job_position_name, e.status
       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.user_id = $1 AND e.tenant_id = $2 AND e.status = 'active'`,
      [userId, tenantId]
    );

    return employee;
  }

  /**
   * Get all employees with user accounts
   */
  async getEmployeesWithUserAccounts(
    tenantId: string,
    companyId?: string
  ): Promise<EmployeeInfo[]> {
    let whereClause = 'WHERE e.tenant_id = $1 AND e.user_id IS NOT NULL AND e.status = \'active\'';
    const params: any[] = [tenantId];

    if (companyId) {
      whereClause += ' AND e.company_id = $2';
      params.push(companyId);
    }

    return query<EmployeeInfo>(
      `SELECT e.id as employee_id, e.employee_number,
              CONCAT(e.first_name, ' ', e.last_name) as employee_name,
              e.user_id, e.department_id, d.name as department_name,
              e.job_position_id, j.name as job_position_name, e.status
       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
       ${whereClause}
       ORDER BY e.last_name, e.first_name`,
      params
    );
  }

  // --------------------------------------------------------------------------
  // COST RATES
  // --------------------------------------------------------------------------

  /**
   * Get employee cost rate
   * Priority: explicit cost_rate > active contract wage > default
   */
  async getEmployeeCostRate(
    employeeId: string,
    tenantId: string,
    date?: Date
  ): Promise<EmployeeCostRate | null> {
    const targetDate = date || new Date();

    // First check explicit cost rates table
    const explicitRate = await queryOne<{
      hourly_cost: string;
      currency_id: string;
      currency_code: string;
      effective_from: Date;
    }>(
      `SELECT ecr.hourly_cost, ecr.currency_id, c.code as currency_code, ecr.effective_from
       FROM hr.employee_cost_rates ecr
       LEFT JOIN core.currencies c ON ecr.currency_id = c.id
       WHERE ecr.employee_id = $1
         AND ecr.tenant_id = $2
         AND ecr.active = true
         AND (ecr.effective_from IS NULL OR ecr.effective_from <= $3)
         AND (ecr.effective_to IS NULL OR ecr.effective_to >= $3)
       ORDER BY ecr.effective_from DESC NULLS LAST
       LIMIT 1`,
      [employeeId, tenantId, targetDate]
    );

    // Get employee info
    const employee = await queryOne<{
      employee_id: string;
      employee_name: string;
      user_id: string;
    }>(
      `SELECT id as employee_id,
              CONCAT(first_name, ' ', last_name) as employee_name,
              user_id
       FROM hr.employees
       WHERE id = $1 AND tenant_id = $2`,
      [employeeId, tenantId]
    );

    if (!employee) {
      return null;
    }

    if (explicitRate) {
      const hourlyCost = parseFloat(explicitRate.hourly_cost);
      return {
        employee_id: employee.employee_id,
        employee_name: employee.employee_name,
        user_id: employee.user_id,
        hourly_cost: hourlyCost,
        monthly_cost: hourlyCost * 173.33, // ~40hrs/week * 4.33 weeks
        currency_id: explicitRate.currency_id,
        currency_code: explicitRate.currency_code,
        source: 'cost_rate',
        effective_date: explicitRate.effective_from,
      };
    }

    // Fallback to contract wage
    const contract = await queryOne<{
      wage: string;
      wage_type: string;
      hours_per_week: string;
      currency_id: string;
      currency_code: string;
    }>(
      `SELECT c.wage, c.wage_type, c.hours_per_week, c.currency_id, cur.code as currency_code
       FROM hr.contracts c
       LEFT JOIN core.currencies cur ON c.currency_id = cur.id
       WHERE c.employee_id = $1
         AND c.tenant_id = $2
         AND c.status = 'active'
         AND c.date_start <= $3
         AND (c.date_end IS NULL OR c.date_end >= $3)
       ORDER BY c.date_start DESC
       LIMIT 1`,
      [employeeId, tenantId, targetDate]
    );

    if (contract) {
      const wage = parseFloat(contract.wage);
      const hoursPerWeek = parseFloat(contract.hours_per_week) || 40;
      const wageType = contract.wage_type || 'monthly';

      let hourlyCost: number;
      let monthlyCost: number;

      if (wageType === 'hourly') {
        hourlyCost = wage;
        monthlyCost = wage * hoursPerWeek * 4.33;
      } else if (wageType === 'daily') {
        hourlyCost = wage / 8;
        monthlyCost = wage * 21.67; // avg workdays per month
      } else {
        // monthly (default)
        monthlyCost = wage;
        hourlyCost = wage / (hoursPerWeek * 4.33);
      }

      return {
        employee_id: employee.employee_id,
        employee_name: employee.employee_name,
        user_id: employee.user_id,
        hourly_cost: hourlyCost,
        monthly_cost: monthlyCost,
        currency_id: contract.currency_id,
        currency_code: contract.currency_code,
        source: 'contract',
      };
    }

    // No cost data available - return with zero cost
    return {
      employee_id: employee.employee_id,
      employee_name: employee.employee_name,
      user_id: employee.user_id,
      hourly_cost: 0,
      monthly_cost: 0,
      source: 'default',
    };
  }

  /**
   * Create or update employee cost rate
   */
  async setEmployeeCostRate(
    dto: CreateCostRateDto,
    tenantId: string,
    userId: string
  ): Promise<void> {
    if (dto.hourly_cost < 0) {
      throw new ValidationError('El costo por hora no puede ser negativo');
    }

    // Deactivate any existing rates for the same period
    if (dto.effective_from) {
      await query(
        `UPDATE hr.employee_cost_rates
         SET active = false, updated_by = $1, updated_at = CURRENT_TIMESTAMP
         WHERE employee_id = $2 AND tenant_id = $3 AND active = true
           AND (effective_from IS NULL OR effective_from >= $4)`,
        [userId, dto.employee_id, tenantId, dto.effective_from]
      );
    }

    await query(
      `INSERT INTO hr.employee_cost_rates (
        tenant_id, company_id, employee_id, hourly_cost, currency_id,
        effective_from, effective_to, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
      [
        tenantId, dto.company_id, dto.employee_id, dto.hourly_cost,
        dto.currency_id, dto.effective_from, dto.effective_to, userId
      ]
    );

    logger.info('Employee cost rate set', {
      employee_id: dto.employee_id,
      hourly_cost: dto.hourly_cost,
    });
  }

  // --------------------------------------------------------------------------
  // PROJECT PROFITABILITY
  // --------------------------------------------------------------------------

  /**
   * Calculate project profitability
   */
  async getProjectProfitability(
    tenantId: string,
    projectId: string
  ): Promise<ProjectProfitability> {
    // Get project info
    const project = await queryOne<{
      id: string;
      name: string;
      partner_id: string;
      partner_name: string;
    }>(
      `SELECT p.id, p.name, p.partner_id, pr.name as partner_name
       FROM projects.projects p
       LEFT JOIN core.partners pr ON p.partner_id = pr.id
       WHERE p.id = $1 AND p.tenant_id = $2`,
      [projectId, tenantId]
    );

    if (!project) {
      throw new NotFoundError('Proyecto no encontrado');
    }

    // Get timesheets with billing info
    const timesheets = await query<{
      user_id: string;
      hours: string;
      billable: boolean;
      hourly_rate: string;
      billable_amount: string;
    }>(
      `SELECT ts.user_id, ts.hours, ts.billable,
              COALESCE(br.hourly_rate, 0) as hourly_rate,
              COALESCE(br.hourly_rate * ts.hours, 0) as billable_amount
       FROM projects.timesheets ts
       LEFT JOIN LATERAL (
         SELECT hourly_rate
         FROM projects.billing_rates br2
         WHERE br2.tenant_id = ts.tenant_id
           AND br2.company_id = ts.company_id
           AND br2.active = true
           AND (br2.effective_from IS NULL OR br2.effective_from <= ts.date)
           AND (br2.effective_to IS NULL OR br2.effective_to >= ts.date)
           AND (
             (br2.project_id = ts.project_id AND br2.user_id = ts.user_id) OR
             (br2.project_id = ts.project_id AND br2.user_id IS NULL) OR
             (br2.project_id IS NULL AND br2.user_id = ts.user_id) OR
             (br2.project_id IS NULL AND br2.user_id IS NULL)
           )
         ORDER BY
           CASE
             WHEN br2.project_id IS NOT NULL AND br2.user_id IS NOT NULL THEN 1
             WHEN br2.project_id IS NOT NULL THEN 2
             WHEN br2.user_id IS NOT NULL THEN 3
             ELSE 4
           END
         LIMIT 1
       ) br ON true
       WHERE ts.project_id = $1 AND ts.tenant_id = $2 AND ts.status = 'approved'`,
      [projectId, tenantId]
    );

    // Get unique users with their employees
    const userIds = [...new Set(timesheets.map(ts => ts.user_id))];

    // Get employee cost rates for each user
    const employeeCosts = new Map<string, { employee_id: string; employee_name: string; hourly_cost: number }>();

    for (const userId of userIds) {
      const employee = await this.getEmployeeByUserId(userId, tenantId);
      if (employee) {
        const costRate = await this.getEmployeeCostRate(employee.employee_id, tenantId);
        if (costRate) {
          employeeCosts.set(userId, {
            employee_id: employee.employee_id,
            employee_name: employee.employee_name,
            hourly_cost: costRate.hourly_cost,
          });
        }
      }
    }

    // Calculate by employee
    const byEmployeeMap = new Map<string, {
      employee_id: string;
      employee_name: string;
      hours: number;
      billable_hours: number;
      revenue: number;
      cost: number;
    }>();

    let totalHours = 0;
    let billableHours = 0;
    let totalRevenue = 0;
    let totalCost = 0;

    for (const ts of timesheets) {
      const hours = parseFloat(ts.hours);
      const billableAmount = parseFloat(ts.billable_amount);
      const employeeInfo = employeeCosts.get(ts.user_id);
      const hourlyCost = employeeInfo?.hourly_cost || 0;
      const cost = hours * hourlyCost;

      totalHours += hours;
      totalCost += cost;

      if (ts.billable) {
        billableHours += hours;
        totalRevenue += billableAmount;
      }

      const key = ts.user_id;
      const existing = byEmployeeMap.get(key);

      if (existing) {
        existing.hours += hours;
        if (ts.billable) {
          existing.billable_hours += hours;
          existing.revenue += billableAmount;
        }
        existing.cost += cost;
      } else {
        byEmployeeMap.set(key, {
          employee_id: employeeInfo?.employee_id || ts.user_id,
          employee_name: employeeInfo?.employee_name || 'Unknown',
          hours,
          billable_hours: ts.billable ? hours : 0,
          revenue: ts.billable ? billableAmount : 0,
          cost,
        });
      }
    }

    const grossMargin = totalRevenue - totalCost;
    const marginPercent = totalRevenue > 0 ? (grossMargin / totalRevenue) * 100 : 0;

    const byEmployee = Array.from(byEmployeeMap.values()).map(emp => ({
      ...emp,
      margin: emp.revenue - emp.cost,
    })).sort((a, b) => b.hours - a.hours);

    return {
      project_id: project.id,
      project_name: project.name,
      partner_id: project.partner_id,
      partner_name: project.partner_name,
      total_hours: totalHours,
      billable_hours: billableHours,
      billable_revenue: totalRevenue,
      employee_cost: totalCost,
      gross_margin: grossMargin,
      margin_percent: marginPercent,
      by_employee: byEmployee,
    };
  }

  /**
   * Get employee timesheet summary across projects
   */
  async getEmployeeTimesheetSummary(
    tenantId: string,
    employeeId: string,
    dateFrom?: string,
    dateTo?: string
  ): Promise<EmployeeTimesheetSummary[]> {
    // Get employee with user_id
    const employee = await queryOne<{
      employee_id: string;
      employee_name: string;
      user_id: string;
    }>(
      `SELECT id as employee_id,
              CONCAT(first_name, ' ', last_name) as employee_name,
              user_id
       FROM hr.employees
       WHERE id = $1 AND tenant_id = $2`,
      [employeeId, tenantId]
    );

    if (!employee || !employee.user_id) {
      throw new NotFoundError('Empleado no encontrado o sin usuario asociado');
    }

    // Get cost rate
    const costRate = await this.getEmployeeCostRate(employeeId, tenantId);
    const hourlyCost = costRate?.hourly_cost || 0;

    let whereClause = `WHERE ts.user_id = $1 AND ts.tenant_id = $2 AND ts.status = 'approved'`;
    const params: any[] = [employee.user_id, tenantId];
    let paramIndex = 3;

    if (dateFrom) {
      whereClause += ` AND ts.date >= $${paramIndex++}`;
      params.push(dateFrom);
    }

    if (dateTo) {
      whereClause += ` AND ts.date <= $${paramIndex++}`;
      params.push(dateTo);
    }

    const timesheets = await query<{
      project_id: string;
      project_name: string;
      total_hours: string;
      billable_hours: string;
      non_billable_hours: string;
      billable_amount: string;
    }>(
      `SELECT ts.project_id, p.name as project_name,
              SUM(ts.hours) as total_hours,
              SUM(CASE WHEN ts.billable THEN ts.hours ELSE 0 END) as billable_hours,
              SUM(CASE WHEN NOT ts.billable THEN ts.hours ELSE 0 END) as non_billable_hours,
              SUM(CASE WHEN ts.billable THEN COALESCE(br.hourly_rate * ts.hours, 0) ELSE 0 END) as billable_amount
       FROM projects.timesheets ts
       JOIN projects.projects p ON ts.project_id = p.id
       LEFT JOIN LATERAL (
         SELECT hourly_rate
         FROM projects.billing_rates br2
         WHERE br2.tenant_id = ts.tenant_id
           AND br2.company_id = ts.company_id
           AND br2.active = true
           AND (br2.effective_from IS NULL OR br2.effective_from <= ts.date)
           AND (br2.effective_to IS NULL OR br2.effective_to >= ts.date)
           AND (
             (br2.project_id = ts.project_id AND br2.user_id = ts.user_id) OR
             (br2.project_id = ts.project_id AND br2.user_id IS NULL) OR
             (br2.project_id IS NULL AND br2.user_id = ts.user_id) OR
             (br2.project_id IS NULL AND br2.user_id IS NULL)
           )
         ORDER BY
           CASE
             WHEN br2.project_id IS NOT NULL AND br2.user_id IS NOT NULL THEN 1
             WHEN br2.project_id IS NOT NULL THEN 2
             WHEN br2.user_id IS NOT NULL THEN 3
             ELSE 4
           END
         LIMIT 1
       ) br ON true
       ${whereClause}
       GROUP BY ts.project_id, p.name
       ORDER BY total_hours DESC`,
      params
    );

    return timesheets.map(ts => {
      const totalHours = parseFloat(ts.total_hours);
      const billableHours = parseFloat(ts.billable_hours);
      const billableAmount = parseFloat(ts.billable_amount);
      const costAmount = totalHours * hourlyCost;
      const margin = billableAmount - costAmount;

      return {
        employee_id: employee.employee_id,
        employee_name: employee.employee_name,
        user_id: employee.user_id,
        project_id: ts.project_id,
        project_name: ts.project_name,
        total_hours: totalHours,
        billable_hours: billableHours,
        non_billable_hours: parseFloat(ts.non_billable_hours),
        billable_amount: billableAmount,
        cost_amount: costAmount,
        margin,
        margin_percent: billableAmount > 0 ? (margin / billableAmount) * 100 : 0,
      };
    });
  }

  /**
   * Validate project team assignment
   */
  async validateTeamAssignment(
    tenantId: string,
    projectId: string,
    userId: string
  ): Promise<{ valid: boolean; employee?: EmployeeInfo; message?: string }> {
    // Check if user has an employee record
    const employee = await this.getEmployeeByUserId(userId, tenantId);

    if (!employee) {
      return {
        valid: false,
        message: 'El usuario no tiene un registro de empleado activo',
      };
    }

    // Get project info
    const project = await queryOne<{ company_id: string }>(
      `SELECT company_id FROM projects.projects WHERE id = $1 AND tenant_id = $2`,
      [projectId, tenantId]
    );

    if (!project) {
      return {
        valid: false,
        message: 'Proyecto no encontrado',
      };
    }

    // Optionally check if employee belongs to the same company
    // This is a soft validation - you might want to allow cross-company assignments
    const employeeCompany = await queryOne<{ company_id: string }>(
      `SELECT company_id FROM hr.employees WHERE id = $1`,
      [employee.employee_id]
    );

    if (employeeCompany && employeeCompany.company_id !== project.company_id) {
      return {
        valid: true, // Still valid but with warning
        employee,
        message: 'El empleado pertenece a una compañía diferente al proyecto',
      };
    }

    return {
      valid: true,
      employee,
    };
  }
}

export const hrIntegrationService = new HRIntegrationService();
