<?php

declare(strict_types=1);

namespace ApiModel\Service;

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

final class LeaveService
{
    private const LEAVE_TYPES = ['vacacion', 'enfermedad', 'maternidad_paternidad', 'no_remunerado', 'otros'];
    private const LEAVE_STATUSES = ['pending', 'approved', 'rejected'];

    public function __construct(
        private readonly LeaveRepositoryInterface $leaves,
        private readonly EmployeeRepositoryInterface $employees
    ) {}

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

    /** @return array<string, mixed> */
    public function getLeave(string $companyId, string $id): array
    {
        $row = $this->leaves->findById($id);
        if ($row === null) {
            throw new NotFoundException('Solicitud de ausencia no encontrada');
        }

        // Validate company access
        $emp = $this->employees->findById((string) $row['employee_id']);
        if ($emp === null || (string) $emp['company_id'] !== $companyId) {
            throw new NotFoundException('Solicitud de ausencia no encontrada');
        }

        return $this->mapLeave($row);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function createLeave(string $companyId, array $data): array
    {
        $employeeId = trim((string) ($data['employee_id'] ?? ''));
        $leaveType = strtolower(trim((string) ($data['leave_type'] ?? '')));
        $startDate = trim((string) ($data['start_date'] ?? ''));
        $endDate = trim((string) ($data['end_date'] ?? ''));
        $status = strtolower(trim((string) ($data['status'] ?? 'pending')));
        $notes = isset($data['notes']) ? trim((string) $data['notes']) : '';

        if ($employeeId === '') {
            throw new BadRequestException('El empleado es obligatorio');
        }

        // Validate employee company
        $emp = $this->employees->findById($employeeId);
        if ($emp === null || (string) $emp['company_id'] !== $companyId) {
            throw new NotFoundException('Empleado no encontrado');
        }

        $this->validateLeaveFields($leaveType, $startDate, $endDate, $status);

        $id = Database::uuid();
        $leaveObj = [
            'id' => $id,
            'employee_id' => $employeeId,
            'leave_type' => $leaveType,
            'start_date' => $startDate,
            'end_date' => $endDate,
            'status' => $status,
            'notes' => $notes,
        ];

        $this->leaves->insert($leaveObj);

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

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

        $employeeId = trim((string) ($data['employee_id'] ?? $existing['employee_id']));
        $leaveType = strtolower(trim((string) ($data['leave_type'] ?? $existing['leave_type'])));
        $startDate = trim((string) ($data['start_date'] ?? $existing['start_date']));
        $endDate = trim((string) ($data['end_date'] ?? $existing['end_date']));
        $status = strtolower(trim((string) ($data['status'] ?? $existing['status'])));
        $notes = array_key_exists('notes', $data) ? trim((string) $data['notes']) : ($existing['notes'] ?? '');

        if ($employeeId === '') {
            throw new BadRequestException('El empleado es obligatorio');
        }

        // Validate employee company
        $emp = $this->employees->findById($employeeId);
        if ($emp === null || (string) $emp['company_id'] !== $companyId) {
            throw new NotFoundException('Empleado no encontrado');
        }

        $this->validateLeaveFields($leaveType, $startDate, $endDate, $status);

        $leaveObj = [
            'employee_id' => $employeeId,
            'leave_type' => $leaveType,
            'start_date' => $startDate,
            'end_date' => $endDate,
            'status' => $status,
            'notes' => $notes,
        ];

        $this->leaves->update($id, $leaveObj);

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

    public function deleteLeave(string $companyId, string $id): void
    {
        $this->getLeave($companyId, $id); // Validates existence and company access
        $this->leaves->delete($id);
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapLeave(array $row): array
    {
        return [
            'id' => $row['id'] ?? null,
            'employee_id' => $row['employee_id'] ?? null,
            'first_name' => $row['first_name'] ?? '',
            'last_name' => $row['last_name'] ?? '',
            'identification_number' => $row['identification_number'] ?? '',
            'department_name' => $row['department_name'] ?? null,
            'leave_type' => $row['leave_type'] ?? '',
            'start_date' => $row['start_date'] ?? '',
            'end_date' => $row['end_date'] ?? '',
            'status' => $row['status'] ?? 'pending',
            'notes' => $row['notes'] ?? null,
        ];
    }

    private function validateLeaveFields(string $type, string $startDate, string $endDate, string $status): void
    {
        if (! in_array($type, self::LEAVE_TYPES, true)) {
            throw new BadRequestException('Tipo de ausencia no válido. Permitidos: ' . implode(', ', self::LEAVE_TYPES));
        }
        if (! in_array($status, self::LEAVE_STATUSES, true)) {
            throw new BadRequestException('Estado de ausencia no válido. Permitidos: ' . implode(', ', self::LEAVE_STATUSES));
        }
        if ($startDate === '' || $endDate === '') {
            throw new BadRequestException('Las fechas de inicio y fin son obligatorias');
        }
        if (strtotime($startDate) > strtotime($endDate)) {
            throw new BadRequestException('La fecha de inicio no puede ser posterior a la fecha de fin');
        }
    }
}
