<?php

declare(strict_types=1);

namespace ApiModel\Service;

use ApiModel\Domain\Repository\EmployeeRepositoryInterface;
use ApiModel\Domain\Repository\DepartmentRepositoryInterface;
use ApiModel\Infrastructure\Persistence\Database;
use ApiModel\Shared\Exceptions\BadRequestException;
use ApiModel\Shared\Exceptions\NotFoundException;

final class EmployeeService
{
    private const CONTRACT_TYPES = ['indefinido', 'temporal', 'obra_servicio', 'practicas'];
    private const CONTRACT_STATUSES = ['active', 'suspended', 'terminated'];

    public function __construct(
        private readonly EmployeeRepositoryInterface $employees,
        private readonly DepartmentRepositoryInterface $departments
    ) {}

    // ==========================================
    // DEPARTAMENTOS
    // ==========================================

    /** @return list<array<string, mixed>> */
    public function listDepartments(string $companyId): array
    {
        return array_map($this->mapDepartment(...), $this->departments->listByCompany($companyId));
    }

    /** @return array<string, mixed> */
    public function getDepartment(string $companyId, string $id): array
    {
        $row = $this->departments->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Departamento no encontrado');
        }
        return $this->mapDepartment($row);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function createDepartment(string $companyId, array $data): array
    {
        $name = trim((string) ($data['name'] ?? ''));
        if ($name === '') {
            throw new BadRequestException('El nombre del departamento es obligatorio');
        }

        $id = Database::uuid();
        $this->departments->insert($id, $companyId, $name);

        return $this->getDepartment($companyId, $id);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function updateDepartment(string $companyId, string $id, array $data): array
    {
        $existing = $this->getDepartment($companyId, $id);

        $name = trim((string) ($data['name'] ?? $existing['name']));
        if ($name === '') {
            throw new BadRequestException('El nombre del departamento es obligatorio');
        }
        $isActive = (bool) ($data['is_active'] ?? $existing['is_active']);

        $this->departments->update($id, $name, $isActive);

        return $this->getDepartment($companyId, $id);
    }

    public function deleteDepartment(string $companyId, string $id): void
    {
        $this->getDepartment($companyId, $id); // Validates existence & company access
        $this->departments->delete($id);
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapDepartment(array $row): array
    {
        return [
            'id' => $row['id'] ?? null,
            'company_id' => $row['company_id'] ?? null,
            'name' => $row['name'] ?? '',
            'is_active' => (bool) ($row['is_active'] ?? true),
        ];
    }

    // ==========================================
    // EMPLEADOS
    // ==========================================

    /** @return list<array<string, mixed>> */
    public function listEmployees(string $companyId): array
    {
        return array_map($this->mapEmployee(...), $this->employees->listByCompany($companyId));
    }

    /** @return list<array<string, mixed>> */
    public function listActiveEmployees(string $companyId): array
    {
        return array_map($this->mapEmployee(...), $this->employees->listActiveByCompany($companyId));
    }

    /** @return array<string, mixed> */
    public function getEmployee(string $companyId, string $id): array
    {
        $row = $this->employees->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Empleado no encontrado');
        }
        return $this->mapEmployee($row);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function createEmployee(string $companyId, array $data): array
    {
        $firstName = trim((string) ($data['first_name'] ?? ''));
        $lastName = trim((string) ($data['last_name'] ?? ''));
        $email = trim((string) ($data['email'] ?? ''));
        $phone = isset($data['phone']) ? trim((string) $data['phone']) : null;
        $idNumber = trim((string) ($data['identification_number'] ?? ''));
        $hiringDate = trim((string) ($data['hiring_date'] ?? ''));
        $deptId = isset($data['department_id']) && trim((string) $data['department_id']) !== '' ? trim((string) $data['department_id']) : null;

        $this->validateEmployeeFields($firstName, $lastName, $email, $idNumber, $hiringDate);

        if ($deptId !== null) {
            $this->getDepartment($companyId, $deptId); // Validate department exists and belongs to company
        }

        $id = Database::uuid();
        $employeeObj = [
            'id' => $id,
            'company_id' => $companyId,
            'first_name' => $firstName,
            'last_name' => $lastName,
            'email' => $email,
            'phone' => $phone,
            'identification_number' => $idNumber,
            'hiring_date' => $hiringDate,
            'department_id' => $deptId,
            'is_active' => true,
        ];

        $this->employees->insert($employeeObj);

        // Si se pasan datos del contrato, lo guardamos
        if (isset($data['contract']) && is_array($data['contract'])) {
            $this->saveContract($id, $data['contract']);
        }

        return $this->getEmployee($companyId, $id);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function updateEmployee(string $companyId, string $id, array $data): array
    {
        $existing = $this->getEmployee($companyId, $id);

        $firstName = trim((string) ($data['first_name'] ?? $existing['first_name']));
        $lastName = trim((string) ($data['last_name'] ?? $existing['last_name']));
        $email = trim((string) ($data['email'] ?? $existing['email']));
        $phone = array_key_exists('phone', $data) ? (trim((string) $data['phone']) ?: null) : ($existing['phone'] ?: null);
        $idNumber = trim((string) ($data['identification_number'] ?? $existing['identification_number']));
        $hiringDate = trim((string) ($data['hiring_date'] ?? $existing['hiring_date']));
        $deptId = array_key_exists('department_id', $data) ? (trim((string) $data['department_id']) ?: null) : ($existing['department_id'] ?: null);
        $isActive = (bool) ($data['is_active'] ?? $existing['is_active']);

        $this->validateEmployeeFields($firstName, $lastName, $email, $idNumber, $hiringDate);

        if ($deptId !== null) {
            $this->getDepartment($companyId, $deptId);
        }

        $employeeObj = [
            'first_name' => $firstName,
            'last_name' => $lastName,
            'email' => $email,
            'phone' => $phone,
            'identification_number' => $idNumber,
            'hiring_date' => $hiringDate,
            'department_id' => $deptId,
            'is_active' => $isActive,
        ];

        $this->employees->update($id, $employeeObj);

        if (isset($data['contract']) && is_array($data['contract'])) {
            $this->saveContract($id, $data['contract']);
        }

        return $this->getEmployee($companyId, $id);
    }

    public function deleteEmployee(string $companyId, string $id): void
    {
        $this->getEmployee($companyId, $id); // Validation
        $this->employees->deleteContract($id);
        $this->employees->delete($id);
    }

    /** @return array<string, mixed>|null */
    public function getContract(string $employeeId): ?array
    {
        $contract = $this->employees->findContractByEmployeeId($employeeId);
        return $contract !== null ? $this->mapContract($contract) : null;
    }

    /** @param array<string, mixed> $data */
    public function saveContract(string $employeeId, array $data): array
    {
        $id = trim((string) ($data['id'] ?? ''));
        if ($id === '' || $id === '0') {
            $existingContract = $this->employees->findContractByEmployeeId($employeeId);
            if ($existingContract !== null) {
                $id = (string) $existingContract['id'];
            } else {
                $id = Database::uuid();
            }
        }

        $type = strtolower(trim((string) ($data['contract_type'] ?? 'indefinido')));
        $startDate = trim((string) ($data['start_date'] ?? date('Y-m-d')));
        $endDate = isset($data['end_date']) && trim((string) $data['end_date']) !== '' ? trim((string) $data['end_date']) : null;
        $baseSalary = (float) ($data['base_salary'] ?? 0.0);
        $status = strtolower(trim((string) ($data['status'] ?? 'active')));

        if (! in_array($type, self::CONTRACT_TYPES, true)) {
            throw new BadRequestException('Tipo de contrato no válido. Permitidos: ' . implode(', ', self::CONTRACT_TYPES));
        }
        if (! in_array($status, self::CONTRACT_STATUSES, true)) {
            throw new BadRequestException('Estado de contrato no válido. Permitidos: ' . implode(', ', self::CONTRACT_STATUSES));
        }
        if ($baseSalary < 0) {
            throw new BadRequestException('El salario base no puede ser negativo');
        }
        if ($startDate === '') {
            throw new BadRequestException('La fecha de inicio de contrato es obligatoria');
        }

        $contractObj = [
            'id' => $id,
            'employee_id' => $employeeId,
            'contract_type' => $type,
            'start_date' => $startDate,
            'end_date' => $endDate,
            'base_salary' => $baseSalary,
            'status' => $status,
        ];

        $this->employees->saveContract($contractObj);

        return $this->mapContract($contractObj);
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapEmployee(array $row): array
    {
        $contract = $this->getContract((string) $row['id']);
        return [
            'id' => $row['id'] ?? null,
            'company_id' => $row['company_id'] ?? null,
            'first_name' => $row['first_name'] ?? '',
            'last_name' => $row['last_name'] ?? '',
            'email' => $row['email'] ?? '',
            'phone' => $row['phone'] ?? null,
            'identification_number' => $row['identification_number'] ?? '',
            'hiring_date' => $row['hiring_date'] ?? '',
            'department_id' => $row['department_id'] ?? null,
            'department_name' => $row['department_name'] ?? null,
            'is_active' => (bool) ($row['is_active'] ?? true),
            'contract' => $contract,
            'base_salary' => $contract ? $contract['base_salary'] : (float) ($row['base_salary'] ?? 0.0),
            'contract_type' => $contract ? $contract['contract_type'] : ($row['contract_type'] ?? null),
        ];
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapContract(array $row): array
    {
        return [
            'id' => $row['id'] ?? null,
            'employee_id' => $row['employee_id'] ?? null,
            'contract_type' => $row['contract_type'] ?? 'indefinido',
            'start_date' => $row['start_date'] ?? '',
            'end_date' => $row['end_date'] ?? null,
            'base_salary' => (float) ($row['base_salary'] ?? 0.0),
            'status' => $row['status'] ?? 'active',
        ];
    }

    private function validateEmployeeFields(string $firstName, string $lastName, string $email, string $idNumber, string $hiringDate): void
    {
        if ($firstName === '' || $lastName === '') {
            throw new BadRequestException('El nombre y el apellido son obligatorios');
        }
        if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new BadRequestException('Debe proporcionar un correo electrónico válido');
        }
        if ($idNumber === '') {
            throw new BadRequestException('El número de identificación (cédula/NIT) es obligatorio');
        }
        if ($hiringDate === '') {
            throw new BadRequestException('La fecha de contratación es obligatoria');
        }
    }
}
