<?php
declare(strict_types=1);

namespace App\Modules\Installments\Controllers;

use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Core\EventBus;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Installments\Services\InstallmentCalculator;

class InstallmentController extends Controller
{
    public function index(Request $request): Response
    {
        $db = App::getInstance()->db();
        $filters = [
            'search' => trim((string) $request->get('q', '')),
            'status' => $request->get('status', ''),
            'overdue_only' => $request->get('overdue_only', ''),
        ];
        $page = max(1, (int) $request->get('page', 1));
        $perPage = 25;
        $where = '1=1';
        $params = [];

        if (!empty($filters['status'])) { $where .= ' AND ip.status = ?'; $params[] = $filters['status']; }
        if (!empty($filters['search'])) {
            $where .= ' AND (m.full_name_ar LIKE ? OR m.membership_number LIKE ?)';
            $s = '%' . $filters['search'] . '%'; $params[] = $s; $params[] = $s;
        }

        $countRow = $db->selectOne("SELECT COUNT(*) as cnt FROM installment_plans ip JOIN members m ON m.id = ip.member_id WHERE {$where}", $params);
        $total = (int) ($countRow['cnt'] ?? 0);
        $offset = ($page - 1) * $perPage;

        $rows = $db->select(
            "SELECT ip.*, m.full_name_ar as member_name, m.membership_number,
                    (SELECT COALESCE(SUM(paid_amount), 0) FROM installment_schedule WHERE installment_plan_id = ip.id AND status = 'paid') as total_paid_installments,
                    (SELECT COUNT(*) FROM installment_schedule WHERE installment_plan_id = ip.id AND status = 'pending' AND due_date < CURDATE()) as overdue_count
             FROM installment_plans ip
             JOIN members m ON m.id = ip.member_id
             WHERE {$where}
             " . ($filters['overdue_only'] ? "HAVING overdue_count > 0" : "") . "
             ORDER BY ip.created_at DESC LIMIT {$perPage} OFFSET {$offset}",
            $params
        );

        $pagination = \App\Core\Pagination::paginate($total, $perPage, $page);

        return $this->view('Installments.Views.index', [
            'rows'       => $rows,
            'pagination' => $pagination,
            'filters'    => $filters,
        ]);
    }

    public function show(Request $request, string $id): Response
    {
        $db = App::getInstance()->db();
        $plan = $db->selectOne(
            "SELECT ip.*, m.full_name_ar as member_name, m.membership_number
             FROM installment_plans ip JOIN members m ON m.id = ip.member_id WHERE ip.id = ?",
            [(int) $id]
        );
        if (!$plan) return $this->redirect('/installments')->withError('الخطة غير موجودة');

        $schedule = $db->select(
            "SELECT * FROM installment_schedule WHERE installment_plan_id = ? ORDER BY installment_number ASC",
            [(int) $id]
        );

        return $this->view('Installments.Views.show', [
            'plan'     => $plan,
            'schedule' => $schedule,
        ]);
    }

    public function create(Request $request, string $memberId): Response
    {
        $db = App::getInstance()->db();
        $member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [(int) $memberId]);
        if (!$member) return $this->redirect('/members')->withError('العضو غير موجود');

        $existingActive = $db->selectOne("SELECT * FROM installment_plans WHERE member_id = ? AND status = 'active'", [(int) $memberId]);

        $rateData     = \App\Modules\Rules\Services\RuleEngine::get('INSTALLMENT_INTEREST_RATE');
        $maxMonthsData = \App\Modules\Rules\Services\RuleEngine::get('INSTALLMENT_MAX_MONTHS');

        return $this->view('Installments.Views.create', [
            'member'         => $member,
            'existingActive' => $existingActive,
            'interestRate'   => $rateData['percentage'] ?? '22.00',
            'maxMonths'      => $maxMonthsData['months'] ?? 30,
        ]);
    }

    public function store(Request $request, string $memberId): Response
    {
        $this->authorize('installment.create_plan');
        $db = App::getInstance()->db();

        $member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [(int) $memberId]);
        if (!$member) {
            return $this->redirect('/members')->withError('العضو غير موجود');
        }

        $totalAmount = trim((string) $request->post('total_amount', '0'));
        $downPayment = trim((string) $request->post('down_payment', '0'));
        $months = (int) $request->post('number_of_months', 0);
        $startDate = trim((string) $request->post('start_date', date('Y-m-d')));
        $notes = trim((string) $request->post('notes', ''));

        $calc = InstallmentCalculator::calculate($totalAmount, $downPayment, $months, $startDate);
        if (!($calc['success'] ?? false)) {
            $session = App::getInstance()->session();
            $session->flash('_alerts', array_map(fn($e) => ['type' => 'error', 'message' => $e], $calc['errors'] ?? []));
            $session->flash('_old_input', $request->all());
            return $this->redirect("/installments/create/{$memberId}");
        }

        $employee = App::getInstance()->currentEmployee();

        $db->beginTransaction();
        try {
            $planId = $db->insert('installment_plans', [
                'member_id'           => (int) $memberId,
                'total_amount'        => $totalAmount,
                'down_payment'        => $downPayment,
                'remaining_balance'   => $calc['remaining_balance'],
                'interest_rate'       => $calc['interest_rate'],
                'total_interest'      => $calc['total_interest'],
                'total_with_interest' => $calc['total_with_interest'],
                'number_of_months'    => $months,
                'monthly_payment'     => $calc['monthly_payment'],
                'start_date'          => $startDate,
                'cash_settlement_date'=> $calc['cash_settlement_date'],
                'status'              => 'active',
                'notes'               => $notes ?: null,
                'created_by'          => $employee ? (int) $employee->id : null,
                'created_at'          => date('Y-m-d H:i:s'),
                'updated_at'          => date('Y-m-d H:i:s'),
            ]);

            foreach ($calc['schedule'] as $item) {
                $db->insert('installment_schedule', [
                    'installment_plan_id' => $planId,
                    'installment_number'  => $item['number'],
                    'due_date'            => $item['due_date'],
                    'amount'              => $item['amount'],
                    'principal'           => $item['principal'],
                    'interest'            => $item['interest'],
                    'status'              => 'pending',
                    'created_at'          => date('Y-m-d H:i:s'),
                ]);
            }

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            return $this->redirect("/installments/create/{$memberId}")->withError('خطأ: ' . $e->getMessage());
        }

        EventBus::dispatch('installment.plan_created', [
            'plan_id'   => $planId,
            'member_id' => (int) $memberId,
        ]);

        return $this->redirect("/installments/{$planId}")->withSuccess(
            "تم إنشاء خطة التقسيط — {$months} قسط بإجمالي " . money($calc['total_with_interest'])
        );
    }

    /**
     * Recalculate pending installments using the correct flat simple-interest formula.
     * Paid rows are left untouched; only pending rows and plan header are corrected.
     */
    public function recalculate(Request $request, string $id): Response
    {
        $this->authorize('installment.create_plan');
        $db = App::getInstance()->db();

        $plan = $db->selectOne("SELECT * FROM installment_plans WHERE id = ?", [(int) $id]);
        if (!$plan) return $this->redirect('/installments')->withError('الخطة غير موجودة');
        if ($plan['status'] === 'completed') {
            return $this->redirect("/installments/{$id}")->withError('الخطة مكتملة — لا يمكن إعادة الحساب');
        }

        $totalAmount  = $plan['total_amount'];
        $downPayment  = $plan['down_payment'];
        $remaining    = bcsub($totalAmount, $downPayment, 2);
        $months       = (int) $plan['number_of_months'];
        $rate         = $plan['interest_rate'];

        if (bccomp($remaining, '0', 2) <= 0 || $months < 1) {
            return $this->redirect("/installments/{$id}")->withError('بيانات الخطة غير صالحة للحساب');
        }

        // Flat simple interest: remaining × (rate/100) × (months/12)
        $correctTotalInterest = bcmul(
            bcmul($remaining, bcdiv($rate, '100', 10), 10),
            bcdiv((string) $months, '12', 10),
            2
        );
        $correctTotalWI   = bcadd($remaining, $correctTotalInterest, 2);
        $flatMonthly      = bcdiv($correctTotalWI, (string) $months, 2);
        $flatPrincipal    = bcdiv($remaining, (string) $months, 2);
        $flatInterest     = bcdiv($correctTotalInterest, (string) $months, 2);

        $pendingRows = $db->select(
            "SELECT id, installment_number FROM installment_schedule
             WHERE installment_plan_id = ? AND status = 'pending'
             ORDER BY installment_number ASC",
            [(int) $id]
        );

        if (empty($pendingRows)) {
            return $this->redirect("/installments/{$id}")->withError('لا توجد أقساط معلقة لإعادة حسابها');
        }

        // Compute running balance after all paid rows
        $paidPrincipalSum = $db->selectOne(
            "SELECT COALESCE(SUM(principal), 0) as s FROM installment_schedule
             WHERE installment_plan_id = ? AND status = 'paid'",
            [(int) $id]
        );
        $runningBalance = bcsub($remaining, (string) ($paidPrincipalSum['s'] ?? '0'), 2);
        if (bccomp($runningBalance, '0', 2) < 0) $runningBalance = '0.00';

        // Compute how much interest is already baked into paid rows
        $paidInterestSum = $db->selectOne(
            "SELECT COALESCE(SUM(interest), 0) as s FROM installment_schedule
             WHERE installment_plan_id = ? AND status = 'paid'",
            [(int) $id]
        );
        $interestAlreadyPaid = (string) ($paidInterestSum['s'] ?? '0');
        $pendingInterestPool = bcsub($correctTotalInterest, $interestAlreadyPaid, 2);
        if (bccomp($pendingInterestPool, '0', 2) < 0) $pendingInterestPool = '0.00';

        $db->beginTransaction();
        try {
            $pendingCount = count($pendingRows);
            $distributedInterest = '0.00';
            foreach ($pendingRows as $idx => $row) {
                $isLast    = ($idx === $pendingCount - 1);
                $principal = $isLast ? $runningBalance : $flatPrincipal;
                // Last pending row absorbs all remaining interest rounding
                $interest  = $isLast
                    ? bcsub($pendingInterestPool, $distributedInterest, 2)
                    : $flatInterest;
                if (bccomp($interest, '0', 2) < 0) $interest = '0.00';
                $distributedInterest = bcadd($distributedInterest, $interest, 2);
                $amount = bcadd($principal, $interest, 2);

                $runningBalance = bcsub($runningBalance, $principal, 2);
                if (bccomp($runningBalance, '0', 2) < 0) $runningBalance = '0.00';

                $db->update('installment_schedule', [
                    'amount'          => $amount,
                    'principal'       => $principal,
                    'interest'        => $interest,
                    'remaining_after' => $runningBalance,
                    'updated_at'      => date('Y-m-d H:i:s'),
                ], '`id` = ?', [(int) $row['id']]);
            }

            // Update plan header
            $db->update('installment_plans', [
                'remaining_balance'   => bcsub($remaining, (string) ($paidPrincipalSum['s'] ?? '0'), 2),
                'total_interest'      => $correctTotalInterest,
                'total_with_interest' => $correctTotalWI,
                'monthly_payment'     => $flatMonthly,
                'updated_at'          => date('Y-m-d H:i:s'),
            ], '`id` = ?', [(int) $id]);

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            return $this->redirect("/installments/{$id}")->withError('خطأ أثناء إعادة الحساب: ' . $e->getMessage());
        }

        return $this->redirect("/installments/{$id}")->withSuccess(
            'تمت إعادة الحساب — ' . $pendingCount . ' قسط تم تصحيحه | القسط الشهري الصحيح: ' . money($flatMonthly)
        );
    }

    /**
     * GET /installments/{id}/early-settlement
     * Show the early-settlement confirmation page with the breakdown.
     */
    public function earlySettlement(Request $request, string $id): Response
    {
        $this->authorize('installment.pay');
        $db = App::getInstance()->db();

        $plan = $db->selectOne(
            "SELECT ip.*, m.full_name_ar as member_name, m.membership_number, m.form_number
             FROM installment_plans ip JOIN members m ON m.id = ip.member_id
             WHERE ip.id = ?",
            [(int) $id]
        );
        if (!$plan) {
            return $this->redirect('/installments')->withError('الخطة غير موجودة');
        }
        if ($plan['status'] !== 'active') {
            return $this->redirect("/installments/{$id}")->withError('لا يمكن التسوية المبكرة — الخطة ليست نشطة');
        }

        $calc = InstallmentCalculator::calculateEarlySettlement((int) $id);
        if (!($calc['success'] ?? false)) {
            return $this->redirect("/installments/{$id}")->withError($calc['error'] ?? 'خطأ في الحساب');
        }

        return $this->view('Installments.Views.early_settlement', [
            'plan' => $plan,
            'calc' => $calc,
        ]);
    }

    /**
     * POST /installments/{id}/early-settlement
     * Execute the early settlement: charge principal only, waive all remaining interest.
     */
    public function processEarlySettlement(Request $request, string $id): Response
    {
        $this->authorize('installment.pay');
        $db = App::getInstance()->db();

        $plan = $db->selectOne(
            "SELECT * FROM installment_plans WHERE id = ? AND status = 'active'",
            [(int) $id]
        );
        if (!$plan) {
            return $this->redirect('/installments')->withError('الخطة غير موجودة أو غير نشطة');
        }

        $calc = InstallmentCalculator::calculateEarlySettlement((int) $id);
        if (!($calc['success'] ?? false)) {
            return $this->redirect("/installments/{$id}")->withError($calc['error'] ?? 'خطأ في الحساب');
        }

        $paymentMethod = trim((string) $request->post('payment_method', 'cash'));
        $settlementAmount = $calc['settlement_amount'];

        // Single payment for the entire remaining principal
        $result = PaymentService::processPayment([
            'member_id'           => (int) $plan['member_id'],
            'amount'              => $settlementAmount,
            'payment_type'        => 'early_settlement',
            'payment_method'      => $paymentMethod,
            'related_entity_type' => 'installment_plan',
            'related_entity_id'   => (int) $id,
            'description'         => 'تسوية مبكرة لخطة التقسيط #' . $id
                . ' — ' . $calc['items_count'] . ' قسط — أصل الدين فقط بدون فوائد',
        ]);

        if (!$result['success']) {
            return $this->redirect("/installments/{$id}/early-settlement")->withError($result['error']);
        }

        $ts = date('Y-m-d H:i:s');

        $db->beginTransaction();
        try {
            foreach ($calc['pending_items'] as $item) {
                $db->update('installment_schedule', [
                    'interest'    => '0.00',
                    'amount'      => $item['principal'],
                    'paid_amount' => $item['principal'],
                    'payment_id'  => $result['payment_id'],
                    'status'      => 'paid',
                    'paid_at'     => $ts,
                    'updated_at'  => $ts,
                ], '`id` = ?', [(int) $item['id']]);
            }

            $db->update('installment_plans', [
                'status'          => 'completed',
                'is_cash_settled' => 1,
                'updated_at'      => $ts,
            ], '`id` = ?', [(int) $id]);

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            return $this->redirect("/installments/{$id}/early-settlement")
                ->withError('خطأ أثناء تسجيل التسوية: ' . $e->getMessage());
        }

        EventBus::dispatch('installment.early_settled', [
            'plan_id'               => (int) $id,
            'member_id'             => (int) $plan['member_id'],
            'payment_id'            => $result['payment_id'],
            'receipt_id'            => $result['receipt_id'],
            'settlement_amount'     => $settlementAmount,
            'interest_waived'       => $calc['total_interest_waived'],
            'items_settled'         => $calc['items_count'],
        ]);

        return $this->redirect("/installments/{$id}/settlement-receipt/{$result['receipt_id']}")
            ->withSuccess('تمت التسوية المبكرة بنجاح — إيصال: ' . $result['receipt_number']);
    }

    /**
     * GET /installments/{id}/settlement-receipt/{receiptId}
     * Print-friendly early settlement receipt.
     */
    public function settlementReceipt(Request $request, string $id, string $receiptId): Response
    {
        $this->authorize('installment.view');
        $db = App::getInstance()->db();

        $plan = $db->selectOne(
            "SELECT ip.*, m.full_name_ar as member_name, m.form_number, m.membership_number
             FROM installment_plans ip JOIN members m ON m.id = ip.member_id
             WHERE ip.id = ?",
            [(int) $id]
        );
        if (!$plan) {
            return $this->redirect('/installments')->withError('الخطة غير موجودة');
        }

        $receipt = $db->selectOne(
            "SELECT r.*, p.payment_method, p.amount as payment_amount,
                    e.full_name_ar as cashier_name
             FROM receipts r
             LEFT JOIN payments p ON p.id = r.payment_id
             LEFT JOIN employees e ON e.id = r.issued_by_employee_id
             WHERE r.id = ? AND r.member_id = ?",
            [(int) $receiptId, (int) $plan['member_id']]
        );
        if (!$receipt) {
            return $this->redirect("/installments/{$id}")->withError('الإيصال غير موجود');
        }

        // Reconstruct settlement breakdown from the paid items linked to this payment
        $settledItems = $db->select(
            "SELECT * FROM installment_schedule
             WHERE installment_plan_id = ? AND payment_id = ?
             ORDER BY installment_number ASC",
            [(int) $id, (int) $receipt['payment_id']]
        );

        $totalPrincipal = array_reduce(
            $settledItems,
            fn ($carry, $item) => bcadd($carry, (string) $item['paid_amount'], 2),
            '0.00'
        );

        // Reconstruct waived interest: we stored interest=0 but can derive from plan header
        // We compute from the original plan totals vs what was already paid before settlement
        $totalWaived = $db->selectOne(
            "SELECT p2.amount as settlement_amt
             FROM payments p2 WHERE p2.id = ?",
            [(int) $receipt['payment_id']]
        );

        return $this->view('Installments.Views.settlement_receipt', [
            'plan'           => $plan,
            'receipt'        => $receipt,
            'settledItems'   => $settledItems,
            'totalPrincipal' => $totalPrincipal,
            'itemsCount'     => count($settledItems),
        ]);
    }

    public function payInstallment(Request $request, string $planId, string $scheduleId): Response
    {
        $db = App::getInstance()->db();
        $plan = $db->selectOne("SELECT * FROM installment_plans WHERE id = ?", [(int) $planId]);
        if (!$plan) return $this->redirect('/installments')->withError('الخطة غير موجودة');

        $item = $db->selectOne("SELECT * FROM installment_schedule WHERE id = ? AND installment_plan_id = ?", [(int) $scheduleId, (int) $planId]);
        if (!$item || $item['status'] === 'paid') return $this->redirect("/installments/{$planId}")->withError('القسط غير موجود أو مدفوع');

        $amount = $item['amount'];
        $paymentMethod = trim((string) $request->post('payment_method', 'cash'));

        $result = PaymentService::processPayment([
            'member_id'           => (int) $plan['member_id'],
            'amount'              => $amount,
            'payment_type'        => 'installment',
            'payment_method'      => $paymentMethod,
            'related_entity_type' => 'installment_schedule',
            'related_entity_id'   => (int) $scheduleId,
            'description'         => 'قسط رقم ' . $item['installment_number'] . ' — خطة #' . $planId,
        ]);

        if (!$result['success']) return $this->redirect("/installments/{$planId}")->withError($result['error']);

        $db->update('installment_schedule', [
            'paid_amount' => $amount,
            'payment_id'  => $result['payment_id'],
            'status'      => 'paid',
            'paid_at'     => date('Y-m-d H:i:s'),
            'updated_at'  => date('Y-m-d H:i:s'),
        ], '`id` = ?', [(int) $scheduleId]);

        // Check if all paid → complete plan
        $remaining = $db->selectOne("SELECT COUNT(*) as cnt FROM installment_schedule WHERE installment_plan_id = ? AND status = 'pending'", [(int) $planId]);
        if ((int) ($remaining['cnt'] ?? 0) === 0) {
            $db->update('installment_plans', ['status' => 'completed', 'updated_at' => date('Y-m-d H:i:s')], '`id` = ?', [(int) $planId]);
        }

        EventBus::dispatch('installment.paid', ['plan_id' => (int) $planId, 'schedule_id' => (int) $scheduleId, 'member_id' => (int) $plan['member_id']]);

        return $this->redirect("/installments/{$planId}")->withSuccess('تم دفع القسط — إيصال: ' . $result['receipt_number']);
    }
}