<?php

namespace App\Services;

use App\Models\EmployeeSalaryAssignment;
use App\Models\Loan;
use App\Models\LoanTransaction;
use App\Models\Payroll;
use App\Models\PayrollItem;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class PayrollGenerationService
{
    public function __construct(
        protected TaxCalculationService $taxService,
        protected LoanDeductionService $loanService
    ) {}

    /**
     * Generate a live preview (not persisted) for the given month.
     *
     * @return array{employees: array, total: float}
     */
    public function generatePreview(string $month): array
    {
        $monthDate = \Carbon\Carbon::parse($month)->startOfMonth();

        $assignments = EmployeeSalaryAssignment::whereHas('employee', function ($query) {
            $query->where('status', 'active');
        })->with([
            'employee.employeeProfile.departmentRel',
            'payrollStructure.components.salaryComponent',
        ])->get();

        $employees = [];
        $total = 0;

        foreach ($assignments as $assignment) {
            $breakdown = $this->calculateBreakdown($assignment, $monthDate->toDateString());
            $net = $breakdown['net_salary'];
            $total += $net;

            $employees[] = [
                'employee_id' => $assignment->employee_id,
                'name' => $assignment->employee->name,
                'job_title' => $assignment->employee->employeeProfile?->job_title ?? null,
                'department' => $assignment->employee->employeeProfile?->departmentRel?->name ?? null,
                'gross_salary' => $assignment->gross_salary,
                'total_earnings' => $breakdown['total_earnings'],
                'total_deductions' => $breakdown['total_deductions'],
                'tax_deduction' => $breakdown['tax_deduction'],
                'loan_deduction' => $breakdown['loan_deduction'],
                'net_salary' => $net,
                'salary_breakdown' => $breakdown['salary_breakdown'],
            ];
        }

        return ['employees' => $employees, 'total' => $total, 'month' => $monthDate->toDateString()];
    }

    /**
     * Persist a payroll for the given month.
     * Blocks if approved; overwrites items if pending.
     *
     * @param  array<int, array{addition?: float, deduction?: float}>  $adjustments  Keyed by employee_id
     */
    public function generate(string $month, array $employeeIds, User $actor, array $adjustments = []): Payroll
    {
        $monthDate = \Carbon\Carbon::parse($month)->startOfMonth()->toDateString();

        // Build a convenient lookup map: employee_id => [addition, deduction]
        $adjMap = [];
        foreach ($adjustments as $adj) {
            $adjMap[(int) $adj['employee_id']] = [
                'addition' => (float) ($adj['addition'] ?? 0),
                'deduction' => (float) ($adj['deduction'] ?? 0),
            ];
        }

        return DB::transaction(function () use ($monthDate, $employeeIds, $actor, $adjMap) {
            /** @var Payroll|null $existing */
            $existing = Payroll::whereDate('month', $monthDate)->first();

            if ($existing && $existing->isApproved()) {
                throw new \Exception('Payroll for this month is already approved and cannot be regenerated.');
            }

            $assignments = EmployeeSalaryAssignment::whereHas('employee', function ($query) {
                $query->where('status', 'active');
            })->with([
                'payrollStructure.components.salaryComponent',
            ])->whereIn('employee_id', $employeeIds)->get();

            $items = [];
            $totalAmount = 0;

            foreach ($assignments as $assignment) {
                $breakdown = $this->calculateBreakdown($assignment, $monthDate);
                $empId = (int) $assignment->employee_id;
                $addition = $adjMap[$empId]['addition'] ?? 0.0;
                $deduction = $adjMap[$empId]['deduction'] ?? 0.0;
                $adjustedNet = round($breakdown['net_salary'] + $addition - $deduction, 2);

                $totalAmount += $adjustedNet;
                $items[] = [
                    'employee_id' => $assignment->employee_id,
                    'gross_salary' => $assignment->gross_salary,
                    'total_earnings' => $breakdown['total_earnings'],
                    'total_deductions' => $breakdown['total_deductions'],
                    'tax_deduction' => $breakdown['tax_deduction'],
                    'loan_deduction' => $breakdown['loan_deduction'],
                    'addition_adjustment' => $addition,
                    'deduction_adjustment' => $deduction,
                    'net_salary' => $adjustedNet,
                    'salary_breakdown' => json_encode($breakdown['salary_breakdown']),
                ];
            }

            if ($existing) {
                $existing->update(['total_amount' => $totalAmount, 'status' => 'draft', 'generated_by' => $actor->id]);
                $existing->items()->delete();
                $payroll = $existing;
            } else {
                $payroll = Payroll::create([
                    'month' => $monthDate,
                    'total_amount' => $totalAmount,
                    'status' => 'draft',
                    'generated_by' => $actor->id,
                ]);
            }

            foreach ($items as $item) {
                PayrollItem::create(array_merge($item, ['payroll_id' => $payroll->id]));
            }

            return $payroll;
        });
    }

    /**
     * Approve a payroll — locks it permanently.
     */
    public function approve(Payroll $payroll, User $actor): void
    {
        if ($payroll->isApproved()) {
            throw new \Exception('Payroll is already approved.');
        }

        DB::transaction(function () use ($payroll, $actor) {
            $payroll->update([
                'status' => 'approved',
                'approved_by' => $actor->id,
                'approved_at' => now(),
            ]);

            // Process Loan Deductions permanently
            foreach ($payroll->items as $item) {
                $breakdown = json_decode($item->salary_breakdown, true);
                if (isset($breakdown['deductions'])) {
                    foreach ($breakdown['deductions'] as $deduction) {
                        if ($deduction['name'] === 'Loan Installment' && isset($deduction['loan_id'])) {
                            $loan = Loan::lockForUpdate()->find($deduction['loan_id']);
                            if ($loan && $loan->status === 'approved' && $loan->remaining_balance > 0) {
                                $scheduledAmount = (float) $deduction['amount'];
                                $amount = min($scheduledAmount, (float) $loan->remaining_balance);

                                if ($amount <= 0) {
                                    continue;
                                }

                                $newBalance = max(0, (float) $loan->remaining_balance - $amount);

                                LoanTransaction::create([
                                    'loan_id' => $loan->id,
                                    'payroll_id' => $payroll->id,
                                    'source' => 'payroll',
                                    'recorded_by' => $actor->id,
                                    'deducted_amount' => $amount,
                                    'remaining_after' => $newBalance,
                                ]);

                                $loan->update([
                                    'remaining_balance' => $newBalance,
                                    'status' => $newBalance <= 0 ? 'closed' : $loan->status,
                                ]);
                            }
                        }
                    }
                }
            }
        });
    }

    /**
     * Reject a payroll.
     */
    public function reject(Payroll $payroll, User $actor): void
    {
        if ($payroll->isApproved()) {
            throw new \Exception('Approved payroll cannot be rejected.');
        }

        $payroll->update(['status' => 'rejected']);
    }

    /**
     * Calculate the full salary breakdown from an assignment.
     * This is the ONLY place where calculation happens — never recalculate from payroll_items.
     *
     * @return array{salary_breakdown: array, total_earnings: float, total_deductions: float, net_salary: float}
     */
    public function calculateBreakdown(EmployeeSalaryAssignment $assignment, string $month): array
    {
        $gross = (float) $assignment->gross_salary;
        $earnings = [];
        $deductions = [];
        $totalEarnings = 0;
        $totalDeductions = 0;
        $taxDeduction = 0.0;
        $loanDeduction = 0.0;

        foreach ($assignment->payrollStructure->components as $component) {
            $salaryComponent = $component->salaryComponent;
            $calcType = $component->calculation_type;
            $value = (float) $component->value;

            $amount = $calcType === 'percentage'
                ? round($gross * ($value / 100), 2)
                : round($value, 2);

            $entry = [
                'name' => $salaryComponent->name,
                'type' => $calcType,
                'value' => $value,
                'amount' => $amount,
            ];

            if ($salaryComponent->type === 'earning') {
                $earnings[] = $entry;
                $totalEarnings += $amount;
            } else {
                $deductions[] = $entry;
                $totalDeductions += $amount;
            }
        }

        // Apply Tax Deduction (tracked separately)
        $taxData = $this->taxService->calculateForEmployee($assignment->employee_id, $gross);
        if ($taxData['amount'] > 0) {
            $deductions[] = [
                'name' => $taxData['name'],
                'tax_id' => $taxData['tax_id'],
                'type' => $taxData['type'],
                'amount' => $taxData['amount'],
                'category' => 'tax',
            ];
            $taxDeduction += $taxData['amount'];
            $totalDeductions += $taxData['amount'];
        }

        // Apply Loan Deductions (tracked separately)
        $loanDeductions = $this->loanService->calculateForEmployee($assignment->employee_id, $month);
        foreach ($loanDeductions as $ld) {
            $deductions[] = array_merge($ld, ['category' => 'loan']);
            $loanDeduction += $ld['amount'];
            $totalDeductions += $ld['amount'];
        }

        $netSalary = round($totalEarnings - $totalDeductions, 2);

        return [
            'salary_breakdown' => ['earnings' => $earnings, 'deductions' => $deductions],
            'total_earnings' => round($totalEarnings, 2),
            'total_deductions' => round($totalDeductions, 2),
            'tax_deduction' => round($taxDeduction, 2),
            'loan_deduction' => round($loanDeduction, 2),
            'net_salary' => $netSalary,
        ];
    }
}
