<?php
declare(strict_types=1);

namespace App\Modules\Accounting\Services;

use App\Core\App;
use App\Core\EventBus;

final class BankLoanService
{
    public static function createLoan(array $data): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $principal = (string) $data['principal_amount'];
        $rate = (string) $data['interest_rate'];
        $termMonths = (int) $data['term_months'];
        $startDate = $data['start_date'];

        $monthlyRate = bcdiv($rate, '1200', 8);
        $monthlyPayment = self::calculateMonthlyPayment($principal, $monthlyRate, $termMonths);
        $totalInterest = bcsub(bcmul($monthlyPayment, (string) $termMonths, 2), $principal, 2);
        $endDate = date('Y-m-d', strtotime($startDate . " +{$termMonths} months"));

        $loanNumber = 'LOAN-' . date('Ymd') . '-' . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT);

        $db->beginTransaction();
        try {
            $loanId = $db->insert('bank_loans', [
                'loan_number' => $loanNumber,
                'bank_account_id' => (int) $data['bank_account_id'],
                'loan_type' => $data['loan_type'] ?? 'term',
                'principal_amount' => $principal,
                'interest_rate' => $rate,
                'term_months' => $termMonths,
                'start_date' => $startDate,
                'end_date' => $endDate,
                'monthly_payment' => $monthlyPayment,
                'outstanding_balance' => $principal,
                'total_interest' => $totalInterest,
                'collateral_description' => $data['collateral_description'] ?? null,
                'notes' => $data['notes'] ?? null,
                'created_by' => $employee ? (int) $employee->id : null,
            ]);

            self::generateAmortizationSchedule($loanId, $principal, $monthlyRate, $monthlyPayment, $termMonths, $startDate);

            $db->commit();

            EventBus::dispatch('bank_loan.created', ['loan_id' => $loanId]);

            return ['success' => true, 'loan_id' => $loanId, 'loan_number' => $loanNumber];
        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    private static function calculateMonthlyPayment(string $principal, string $monthlyRate, int $termMonths): string
    {
        if (bccomp($monthlyRate, '0', 8) === 0) {
            return bcdiv($principal, (string) $termMonths, 2);
        }

        $onePlusR = bcadd('1', $monthlyRate, 8);
        $power = bcpow($onePlusR, (string) $termMonths, 8);
        $numerator = bcmul($principal, bcmul($monthlyRate, $power, 8), 8);
        $denominator = bcsub($power, '1', 8);

        return bcdiv($numerator, $denominator, 2);
    }

    private static function generateAmortizationSchedule(int $loanId, string $principal, string $monthlyRate, string $monthlyPayment, int $termMonths, string $startDate): void
    {
        $db = App::getInstance()->db();
        $balance = $principal;

        for ($i = 1; $i <= $termMonths; $i++) {
            $interest = bcmul($balance, $monthlyRate, 2);
            $principalPart = bcsub($monthlyPayment, $interest, 2);

            if ($i === $termMonths) {
                $principalPart = $balance;
                $monthlyPayment = bcadd($principalPart, $interest, 2);
            }

            $balance = bcsub($balance, $principalPart, 2);
            $dueDate = date('Y-m-d', strtotime($startDate . " +{$i} months"));

            $db->insert('bank_loan_schedule', [
                'loan_id' => $loanId,
                'installment_number' => $i,
                'due_date' => $dueDate,
                'principal_amount' => $principalPart,
                'interest_amount' => $interest,
                'total_amount' => bcadd($principalPart, $interest, 2),
                'status' => 'upcoming',
            ]);
        }
    }

    public static function recordPayment(int $loanId, int $installmentNumber, string $paidAmount, ?string $paidDate = null): array
    {
        $db = App::getInstance()->db();

        $schedule = $db->selectOne(
            "SELECT * FROM bank_loan_schedule WHERE loan_id = ? AND installment_number = ?",
            [$loanId, $installmentNumber]
        );

        if (!$schedule) {
            return ['success' => false, 'error' => 'القسط غير موجود'];
        }

        $db->beginTransaction();
        try {
            $db->update('bank_loan_schedule', [
                'paid_amount' => $paidAmount,
                'paid_date' => $paidDate ?? date('Y-m-d'),
                'status' => 'paid',
            ], 'id = ?', [$schedule['id']]);

            $db->query(
                "UPDATE bank_loans SET outstanding_balance = outstanding_balance - ?, total_paid = total_paid + ? WHERE id = ?",
                [$schedule['principal_amount'], $paidAmount, $loanId]
            );

            $allPaid = $db->selectOne(
                "SELECT COUNT(*) as cnt FROM bank_loan_schedule WHERE loan_id = ? AND status != 'paid'",
                [$loanId]
            );
            if ((int) ($allPaid['cnt'] ?? 1) === 0) {
                $db->update('bank_loans', ['status' => 'paid_off'], 'id = ?', [$loanId]);
            }

            $db->commit();

            EventBus::dispatch('bank_loan.payment', [
                'loan_id' => $loanId,
                'installment_number' => $installmentNumber,
                'amount' => $paidAmount,
            ]);

            return ['success' => true];
        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    public static function getOverdueInstallments(): array
    {
        $db = App::getInstance()->db();

        return $db->select(
            "SELECT s.*, l.loan_number, l.bank_account_id, ba.account_name_ar as bank_name
             FROM bank_loan_schedule s
             JOIN bank_loans l ON l.id = s.loan_id
             LEFT JOIN bank_accounts ba ON ba.id = l.bank_account_id
             WHERE s.status IN ('upcoming','due') AND s.due_date < CURDATE()
             ORDER BY s.due_date ASC"
        );
    }

    public static function getSchedule(int $loanId): array
    {
        $db = App::getInstance()->db();

        return $db->select(
            "SELECT * FROM bank_loan_schedule WHERE loan_id = ? ORDER BY installment_number ASC",
            [$loanId]
        );
    }
}
