<?php

namespace App\Services\Finance;

use App\Enums\JournalSource;
use App\Models\PayrollItem;
use App\Models\PayrollRun;
use App\Models\Staff;
use App\Services\Accounting\AccountLookupService;
use App\Services\Accounting\JournalService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class PayrollService
{
    public function __construct(
        private readonly JournalService $journalService,
        private readonly AccountLookupService $accountLookup,
        private readonly DocumentNumberService $documentNumbers,
    ) {}

    /**
     * @param  array<string, mixed>  $runData
     * @param  array<int, array<string, mixed>>  $items
     */
    public function run(array $runData, array $items): PayrollRun
    {
        if ($items === []) {
            throw new InvalidArgumentException('Payroll run requires at least one staff member.');
        }

        return DB::transaction(function () use ($runData, $items) {
            $totalGross = '0.00';
            $totalDeductions = '0.00';
            $totalNet = '0.00';
            $totalPaye = '0.00';
            $totalPension = '0.00';

            $payrollRun = PayrollRun::query()->create([
                'reference' => $this->documentNumbers->next('PAY', PayrollRun::class, 'reference'),
                'period_start' => $runData['period_start'],
                'period_end' => $runData['period_end'],
                'pay_date' => $runData['pay_date'],
                'status' => 'processing',
                'processed_by' => $runData['processed_by'],
            ]);

            foreach ($items as $item) {
                $staff = Staff::query()->findOrFail($item['staff_id']);
                $grossPay = (string) ($item['gross_pay'] ?? $staff->salary ?? '0.00');
                $paye = (string) ($item['paye'] ?? bcmul($grossPay, '0.30', 2));
                $pension = (string) ($item['pension'] ?? bcmul($grossPay, '0.05', 2));
                $deductions = bcadd($paye, $pension, 2);
                $netPay = bcsub($grossPay, $deductions, 2);

                PayrollItem::query()->create([
                    'payroll_run_id' => $payrollRun->id,
                    'staff_id' => $staff->id,
                    'gross_pay' => $grossPay,
                    'deductions' => $deductions,
                    'net_pay' => $netPay,
                    'notes' => $item['notes'] ?? null,
                ]);

                $totalGross = bcadd($totalGross, $grossPay, 2);
                $totalDeductions = bcadd($totalDeductions, $deductions, 2);
                $totalNet = bcadd($totalNet, $netPay, 2);
                $totalPaye = bcadd($totalPaye, $paye, 2);
                $totalPension = bcadd($totalPension, $pension, 2);
            }

            $salariesAccount = $this->accountLookup->byCode(
                config('accounting.default_account_codes.salaries_expense', '5001')
            );
            $payeAccount = $this->accountLookup->byCode(
                config('accounting.default_account_codes.paye_payable', '2100')
            );
            $pensionAccount = $this->accountLookup->byCode(
                config('accounting.default_account_codes.pension_payable', '2110')
            );
            $cashAccount = $this->accountLookup->configured('cash');

            $journalLines = [
                [
                    'account_id' => $salariesAccount->id,
                    'debit_amount' => $totalGross,
                    'credit_amount' => 0,
                    'line_memo' => 'Salaries expense',
                ],
                [
                    'account_id' => $payeAccount->id,
                    'debit_amount' => 0,
                    'credit_amount' => $totalPaye,
                    'line_memo' => 'PAYE payable',
                ],
                [
                    'account_id' => $pensionAccount->id,
                    'debit_amount' => 0,
                    'credit_amount' => $totalPension,
                    'line_memo' => 'Pension payable',
                ],
                [
                    'account_id' => $cashAccount->id,
                    'debit_amount' => 0,
                    'credit_amount' => $totalNet,
                    'line_memo' => 'Net payroll payment',
                ],
            ];

            $this->journalService->post([
                'date' => $runData['pay_date'],
                'description' => "Payroll {$payrollRun->reference} ({$runData['period_start']} to {$runData['period_end']})",
                'source' => JournalSource::Payroll->value,
                'source_reference' => $payrollRun->reference,
                'posted_by' => $runData['processed_by'],
            ], $journalLines);

            $payrollRun->update([
                'total_gross' => $totalGross,
                'total_deductions' => $totalDeductions,
                'total_net' => $totalNet,
                'status' => 'posted',
            ]);

            return $payrollRun->fresh(['items.staff', 'processedBy']);
        });
    }
}
