<?php

namespace Modules\Payroll\Services;

use App\Models\ActivityLogModel;
use App\Models\EmployeeModel;
use App\Models\EmployeeSalaryStructureModel;
use App\Models\LeaveBalanceModel;
use App\Models\LeaveTypeModel;
use App\Models\PayrollSettingsModel;
use App\Models\PayrollSettlementModel;

class PayrollSettlementService
{
    protected PayrollSettlementModel $settlements;
    protected EmployeeModel $employees;
    protected EmployeeSalaryStructureModel $salaries;
    protected PayrollSettingsModel $settings;
    protected LeaveBalanceModel $leaveBalances;
    protected LeaveTypeModel $leaveTypes;
    protected PayrollCalculationService $calculator;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->settlements   = new PayrollSettlementModel();
        $this->employees     = new EmployeeModel();
        $this->salaries      = new EmployeeSalaryStructureModel();
        $this->settings      = new PayrollSettingsModel();
        $this->leaveBalances = new LeaveBalanceModel();
        $this->leaveTypes    = new LeaveTypeModel();
        $this->calculator    = new PayrollCalculationService();
        $this->activity      = new ActivityLogModel();
    }

    public function calculate(int $employeeId, string $terminationDate, string $lastWorkingDay): array
    {
        $employee = $this->employees->find($employeeId);
        if (! $employee) {
            return ['success' => false, 'errors' => ['employee_id' => 'Employee not found.']];
        }

        $structure = $this->salaries->getEffectiveForEmployee($employeeId, $lastWorkingDay);
        if (! $structure) {
            return ['success' => false, 'errors' => ['employee_id' => 'No salary structure found for this employee.']];
        }

        $settings       = $this->settings->getSettings();
        $yearsOfService = $this->calculator->yearsOfService($employee['hire_date'] ?? null);
        $basic          = (float) $structure['basic_salary'];
        $social         = (float) $structure['social_allowance'];
        $gross          = $this->salaries->grossSalary($structure);
        $workingDays    = (int) $settings['working_days_per_month'];
        $dailyRate      = $workingDays > 0 ? $basic / $workingDays : 0;

        $unusedLeaveDays = $this->getUnusedLeaveDays($employeeId);
        $leaveEncashment = $unusedLeaveDays * $dailyRate;

        $isBahraini  = $this->calculator->isBahrainiNational($employee);
        $legacyEosb  = 0.0;
        if (! $isBahraini) {
            $legacyEosb = $this->calculateLegacyEosb($basic + $social, $yearsOfService, $settings['eosb_effective_date'], $employee['hire_date']);
        }

        $lastSalary = $gross;
        $total      = $lastSalary + $leaveEncashment + $legacyEosb;

        return [
            'success' => true,
            'data'    => [
                'employee'          => $employee,
                'structure'         => $structure,
                'years_of_service'  => round($yearsOfService, 2),
                'unused_leave_days' => $unusedLeaveDays,
                'leave_encashment'  => round($leaveEncashment, 3),
                'legacy_eosb'       => round($legacyEosb, 3),
                'last_salary'       => round($lastSalary, 3),
                'total_settlement'  => round($total, 3),
                'is_bahraini'       => $isBahraini,
            ],
        ];
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $employeeId       = (int) ($input['employee_id'] ?? 0);
        $terminationDate  = $input['termination_date'] ?? '';
        $lastWorkingDay   = $input['last_working_day'] ?? $terminationDate;

        $preview = $this->calculate($employeeId, $terminationDate, $lastWorkingDay);
        if (! $preview['success']) {
            return $preview;
        }

        $data = $preview['data'];
        $id   = $this->settlements->insert([
            'employee_id'        => $employeeId,
            'termination_date'   => $terminationDate,
            'last_working_day'   => $lastWorkingDay,
            'years_of_service'   => $data['years_of_service'],
            'unused_leave_days'  => $data['unused_leave_days'],
            'leave_encashment'   => (float) ($input['leave_encashment'] ?? $data['leave_encashment']),
            'legacy_eosb'        => (float) ($input['legacy_eosb'] ?? $data['legacy_eosb']),
            'notice_pay'         => round((float) ($input['notice_pay'] ?? 0), 3),
            'last_salary'        => (float) ($input['last_salary'] ?? $data['last_salary']),
            'other_entitlements' => round((float) ($input['other_entitlements'] ?? 0), 3),
            'total_deductions'   => round((float) ($input['total_deductions'] ?? 0), 3),
            'total_settlement'   => round((float) ($input['total_settlement'] ?? (
                ($input['last_salary'] ?? $data['last_salary'])
                + ($input['leave_encashment'] ?? $data['leave_encashment'])
                + ($input['legacy_eosb'] ?? $data['legacy_eosb'])
                + ($input['notice_pay'] ?? 0)
                + ($input['other_entitlements'] ?? 0)
                - ($input['total_deductions'] ?? 0)
            )), 3),
            'status'             => 'draft',
            'notes'              => trim((string) ($input['notes'] ?? '')) ?: null,
            'created_by'         => $actingUserId,
        ]);

        $this->activity->log('payroll_settlements', 'created', $actingUserId, "Created settlement #{$id} for employee #{$employeeId}", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function approve(int $id, ?int $actingUserId): array
    {
        $settlement = $this->settlements->find($id);
        if (! $settlement) {
            return ['success' => false, 'errors' => ['id' => 'Settlement not found.']];
        }

        $this->settlements->update($id, [
            'status'      => 'approved',
            'approved_by' => $actingUserId,
            'approved_at' => date('Y-m-d H:i:s'),
        ]);

        $this->activity->log('payroll_settlements', 'approved', $actingUserId, "Approved settlement #{$id}", (string) $id);

        return ['success' => true];
    }

    public function markPaid(int $id, ?int $actingUserId): array
    {
        $settlement = $this->settlements->find($id);
        if (! $settlement) {
            return ['success' => false, 'errors' => ['id' => 'Settlement not found.']];
        }

        $this->settlements->update($id, [
            'status'  => 'paid',
            'paid_at' => date('Y-m-d H:i:s'),
        ]);

        $this->activity->log('payroll_settlements', 'paid', $actingUserId, "Marked settlement #{$id} as paid", (string) $id);

        return ['success' => true];
    }

    protected function getUnusedLeaveDays(int $employeeId): float
    {
        $year    = (int) date('Y');
        $annual  = $this->leaveTypes->where('code', 'AL')->first();
        if (! $annual) {
            return 0.0;
        }

        $balance = $this->leaveBalances
            ->where('employee_id', $employeeId)
            ->where('leave_type_id', (int) $annual['id'])
            ->where('year', $year)
            ->first();

        if (! $balance) {
            return 0.0;
        }

        return max(0, (float) $balance['entitled'] - (float) $balance['used']);
    }

    protected function calculateLegacyEosb(float $baseSalary, float $years, string $eosbEffectiveDate, ?string $hireDate): float
    {
        if (! $hireDate || $hireDate >= $eosbEffectiveDate) {
            return 0.0;
        }

        $hire       = new \DateTime($hireDate);
        $effective  = new \DateTime($eosbEffectiveDate);
        $legacyYears = min($years, $hire->diff($effective)->days / 365.25);

        if ($legacyYears <= 0) {
            return 0.0;
        }

        $amount = 0.0;
        if ($legacyYears <= 3) {
            $amount = ($baseSalary / 2) * $legacyYears;
        } else {
            $amount = ($baseSalary / 2) * 3 + $baseSalary * ($legacyYears - 3);
        }

        return round($amount, 3);
    }
}
