<?php
declare(strict_types=1);

namespace App\Modules\Payments\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\Models\Payment;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Payments\Services\BalanceCalculator;
use App\Modules\Members\Services\MemberNumberGenerator;
use App\Modules\Rules\Services\RuleEngine;

class PaymentController extends Controller
{
    /**
     * List all payments with filters.
     */
    public function index(Request $request): Response
    {
        $filters = [
            'search'         => trim((string) $request->get('q', '')),
            'payment_type'   => $request->get('payment_type', ''),
            'payment_method' => $request->get('payment_method', ''),
            'date_from'      => $request->get('date_from', ''),
            'date_to'        => $request->get('date_to', ''),
        ];
        $page = max(1, (int) $request->get('page', 1));
        $result = Payment::search($filters, 25, $page);

        return $this->view('Payments.Views.history', [
            'rows'       => $result['data'],
            'pagination' => $result['pagination'],
            'filters'    => $filters,
        ]);
    }

    /**
     * Show the payment form for a member.
     * Lists all available payment types and outstanding amounts.
     */
    public function process(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('العضو غير موجود');
        }

        $summary = BalanceCalculator::getSummary((int) $memberId);

        // Build available payment options
        $paymentOptions = [];

        // Form fee
        if (!$summary['form_fee_paid']) {
            $paymentOptions[] = [
                'type'        => 'form_fee',
                'label'       => 'رسوم استمارة',
                'amount'      => $summary['form_fee'],
                'description' => 'رسوم استمارة رقم ' . ($member['form_number'] ?? ''),
                'required'    => true,
            ];
        }

        // Membership value (full cash or down payment)
        if ($member['status'] === 'accepted' && !$summary['membership_fee_paid']) {
            $paymentOptions[] = [
                'type'        => 'membership_fee',
                'label'       => 'قيمة العضوية (كاش كامل)',
                'amount'      => $member['membership_value'] ?? '0.00',
                'description' => 'قيمة العضوية كاملة',
                'required'    => false,
            ];

            // Down payment for installment
            $minDown = bcdiv(bcmul($member['membership_value'] ?? '0', '25', 2), '100', 2);
            $paymentOptions[] = [
                'type'        => 'down_payment',
                'label'       => 'مقدم تقسيط (25% على الأقل)',
                'amount'      => $minDown,
                'description' => 'مقدم تقسيط — الحد الأدنى ' . money($minDown),
                'required'    => false,
                'is_variable' => true,
                'min_amount'  => $minDown,
            ];
        }

        // Installment payment
        if (($summary['installments']['next_due'] ?? null) !== null) {
            $paymentOptions[] = [
                'type'        => 'installment',
                'label'       => 'قسط شهري',
                'amount'      => $summary['installments']['next_due']['amount'] ?? '0.00',
                'description' => 'قسط مستحق ' . ($summary['installments']['next_due']['due_date'] ?? ''),
                'required'    => false,
            ];
        }

        // Annual subscription
        if (bccomp($summary['unpaid_subscriptions'], '0', 2) > 0) {
            $paymentOptions[] = [
                'type'        => 'annual_subscription',
                'label'       => 'اشتراك سنوي',
                'amount'      => $summary['unpaid_subscriptions'],
                'description' => 'اشتراكات سنوية غير مدفوعة (' . $summary['unpaid_subs_count'] . ' اشتراك)',
                'required'    => false,
            ];
        }

        // Fines
        if (bccomp($summary['unpaid_fines'], '0', 2) > 0) {
            $paymentOptions[] = [
                'type'        => 'fine',
                'label'       => 'غرامات',
                'amount'      => $summary['unpaid_fines'],
                'description' => 'غرامات مستحقة',
                'required'    => false,
            ];
        }

        // Generic custom payment
        $paymentOptions[] = [
            'type'        => 'other',
            'label'       => 'دفعة أخرى',
            'amount'      => '0.00',
            'description' => '',
            'required'    => false,
            'is_variable' => true,
        ];

        return $this->view('Payments.Views.process', [
            'member'         => $member,
            'summary'        => $summary,
            'paymentOptions' => $paymentOptions,
        ]);
    }

    /**
     * Handle payment submission.
     */
    public function store(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('العضو غير موجود');
        }

        $paymentType = trim((string) $request->post('payment_type', ''));
        $amount = trim((string) $request->post('amount', '0'));
        $paymentMethod = trim((string) $request->post('payment_method', 'cash'));
        $notes = trim((string) $request->post('notes', ''));

        if ($paymentType === '' || bccomp($amount, '0.01', 2) < 0) {
            return $this->redirect("/payments/process/{$memberId}")->withError('بيانات الدفع غير صالحة');
        }

        $description = PaymentService::getPaymentTypeLabel($paymentType);
        if ($member['form_number']) {
            $description .= ' — استمارة ' . $member['form_number'];
        }

        $result = PaymentService::processPayment([
            'member_id'           => (int) $memberId,
            'amount'              => $amount,
            'payment_type'        => $paymentType,
            'payment_method'      => $paymentMethod,
            'related_entity_type' => 'members',
            'related_entity_id'   => (int) $memberId,
            'description'         => $description,
            'notes'               => $notes,
            'check_number'        => $request->post('check_number'),
            'check_bank'          => $request->post('check_bank'),
            'check_date'          => $request->post('check_date'),
            'visa_reference'      => $request->post('visa_reference'),
            'transfer_reference'  => $request->post('transfer_reference'),
            'transfer_bank'       => $request->post('transfer_bank'),
        ]);

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

        // ── Post-payment actions based on type ──

        // Membership fee paid → assign membership number + activate
        if (in_array($paymentType, ['membership_fee', 'down_payment'])) {
            $this->handleMembershipPayment((int) $memberId, $paymentType, $amount);
        }

        // Annual subscription paid → update subscriptions table
        if ($paymentType === 'annual_subscription') {
            $this->handleSubscriptionPayment((int) $memberId, $amount, $result['payment_id']);
        }

        // Fine paid → update fines table
        if ($paymentType === 'fine') {
            $this->handleFinePayment((int) $memberId, $amount, $result['payment_id']);
        }

        // Installment paid → update installment_schedule
        if ($paymentType === 'installment') {
            $this->handleInstallmentPayment((int) $memberId, $amount, $result['payment_id']);
        }

        return $this->redirect("/members/{$memberId}")
            ->withSuccess('تم تسجيل الدفع — ' . money($amount) . ' — إيصال: ' . $result['receipt_number']);
    }

    /**
     * Handle membership value payment — assign number + activate.
     */
    private function handleMembershipPayment(int $memberId, string $type, string $amount): void
    {
        $db = App::getInstance()->db();
        $member = $db->selectOne("SELECT * FROM members WHERE id = ?", [$memberId]);
        if (!$member) return;

        // If full cash payment → activate immediately
        if ($type === 'membership_fee') {
            $number = MemberNumberGenerator::assign($memberId);
            $db->update('members', [
                'status'     => 'active',
                'updated_at' => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$memberId]);

            EventBus::dispatch('member.activated', [
                'member_id'         => $memberId,
                'membership_number' => $number,
                'payment_method'    => 'cash_full',
            ]);
        }

        // If down payment → create installment plan + activate
        if ($type === 'down_payment') {
            $membershipValue = $member['membership_value'] ?? '0.00';
            $remaining = bcsub($membershipValue, $amount, 2);

            if (bccomp($remaining, '0', 2) > 0) {
                // Create installment plan
                $interestRateData = RuleEngine::get('INSTALLMENT_INTEREST_RATE');
                $interestRate = $interestRateData['percentage'] ?? '22.00';
                $months = 30; // Max default, can be overridden
                $totalInterest = bcdiv(bcmul($remaining, $interestRate, 4), '100', 2);
                $totalWithInterest = bcadd($remaining, $totalInterest, 2);
                $monthlyPayment = bcdiv($totalWithInterest, (string) $months, 2);

                $planId = $db->insert('installment_plans', [
                    'member_id'            => $memberId,
                    'related_entity_type'  => 'members',
                    'related_entity_id'    => $memberId,
                    'total_amount'         => $membershipValue,
                    'down_payment'         => $amount,
                    'remaining_balance'    => $remaining,
                    'interest_rate'        => $interestRate,
                    'total_interest'       => $totalInterest,
                    'total_with_interest'  => $totalWithInterest,
                    'number_of_months'     => $months,
                    'monthly_payment'      => $monthlyPayment,
                    'start_date'           => date('Y-m-d'),
                    'status'               => 'active',
                    'notes'                => 'خطة تقسيط — قيمة العضوية',
                    'created_at'           => date('Y-m-d H:i:s'),
                    'updated_at'           => date('Y-m-d H:i:s'),
                ]);

                // Generate schedule
                $remainingAfter = $totalWithInterest;
                for ($i = 1; $i <= $months; $i++) {
                    $principal = bcdiv($remaining, (string) $months, 2);
                    $interest = bcdiv($totalInterest, (string) $months, 2);
                    $installmentAmount = bcadd($principal, $interest, 2);
                    $remainingAfter = bcsub($remainingAfter, $installmentAmount, 2);
                    if (bccomp($remainingAfter, '0', 2) < 0) $remainingAfter = '0.00';

                    $dueDate = date('Y-m-d', strtotime("+{$i} months"));

                    $db->insert('installment_schedule', [
                        'installment_plan_id' => $planId,
                        'installment_number'  => $i,
                        'due_date'            => $dueDate,
                        'amount'              => $installmentAmount,
                        'principal'           => $principal,
                        'interest'            => $interest,
                        'remaining_after'     => $remainingAfter,
                        'paid_amount'         => '0.00',
                        'status'              => 'pending',
                        'created_at'          => date('Y-m-d H:i:s'),
                        'updated_at'          => date('Y-m-d H:i:s'),
                    ]);
                }

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

            // Installment path: member must submit cheques before activation
            $db->update('members', [
                'status'     => 'pending_cheques',
                'updated_at' => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$memberId]);
        }
    }

    /**
     * Mark pending subscriptions as paid (oldest first) up to the paid amount.
     */
    private function handleSubscriptionPayment(int $memberId, string $amount, int $paymentId): void
    {
        $db = App::getInstance()->db();
        $subs = $db->select(
            "SELECT id, total_amount, fine_amount, paid_amount, financial_year, person_name
             FROM subscriptions WHERE member_id = ? AND status IN ('pending','overdue')
             ORDER BY financial_year ASC, id ASC",
            [$memberId]
        );

        $remaining = $amount;
        $ts = date('Y-m-d H:i:s');
        foreach ($subs as $sub) {
            if (bccomp($remaining, '0.01', 2) < 0) break;

            $due = bcsub(bcadd($sub['total_amount'], $sub['fine_amount'], 2), $sub['paid_amount'], 2);
            if (bccomp($due, '0', 2) <= 0) continue;

            $pay = bccomp($remaining, $due, 2) >= 0 ? $due : $remaining;
            $newPaid = bcadd($sub['paid_amount'], $pay, 2);
            $totalDue = bcadd($sub['total_amount'], $sub['fine_amount'], 2);
            $status = bccomp($newPaid, $totalDue, 2) >= 0 ? 'paid' : 'partial';

            $db->update('subscriptions', [
                'paid_amount' => $newPaid,
                'payment_id'  => $paymentId,
                'status'      => $status,
                'paid_at'     => $status === 'paid' ? $ts : null,
                'updated_at'  => $ts,
            ], '`id` = ?', [(int) $sub['id']]);

            $remaining = bcsub($remaining, $pay, 2);

            if ($status === 'paid') {
                EventBus::dispatch('subscription.paid', [
                    'subscription_id' => (int) $sub['id'],
                    'member_id'       => $memberId,
                    'year'            => $sub['financial_year'],
                    'amount'          => $pay,
                ]);
            }
        }
    }

    /**
     * Mark pending fines as paid (oldest first) up to the paid amount.
     */
    private function handleFinePayment(int $memberId, string $amount, int $paymentId): void
    {
        $db = App::getInstance()->db();
        $fines = $db->select(
            "SELECT id, amount as fine_amount, paid_amount FROM fines
             WHERE member_id = ? AND status IN ('imposed','pending','appeal_upheld') AND (amount - paid_amount) > 0
             ORDER BY id ASC",
            [$memberId]
        );

        $remaining = $amount;
        $ts = date('Y-m-d H:i:s');
        foreach ($fines as $fine) {
            if (bccomp($remaining, '0.01', 2) < 0) break;

            $due = bcsub($fine['fine_amount'], $fine['paid_amount'], 2);
            if (bccomp($due, '0', 2) <= 0) continue;

            $pay = bccomp($remaining, $due, 2) >= 0 ? $due : $remaining;
            $newPaid = bcadd($fine['paid_amount'], $pay, 2);
            $status = bccomp($newPaid, $fine['fine_amount'], 2) >= 0 ? 'paid' : 'imposed';

            $db->update('fines', [
                'paid_amount' => $newPaid,
                'payment_id'  => $paymentId,
                'status'      => $status,
                'paid_at'     => $status === 'paid' ? $ts : null,
                'updated_at'  => $ts,
            ], '`id` = ?', [(int) $fine['id']]);

            $remaining = bcsub($remaining, $pay, 2);

            if ($status === 'paid') {
                EventBus::dispatch('fine.paid', [
                    'fine_id'   => (int) $fine['id'],
                    'member_id' => $memberId,
                    'amount'    => $pay,
                ]);
            }
        }
    }

    /**
     * Mark next pending installment(s) as paid.
     */
    private function handleInstallmentPayment(int $memberId, string $amount, int $paymentId): void
    {
        $db = App::getInstance()->db();
        $installments = $db->select(
            "SELECT s.id, s.amount as installment_amount, s.paid_amount, s.due_date, p.id as plan_id
             FROM installment_schedule s JOIN installment_plans p ON p.id = s.installment_plan_id
             WHERE p.member_id = ? AND p.status IN ('active','overdue') AND s.status IN ('pending','overdue')
             ORDER BY s.due_date ASC, s.installment_number ASC",
            [$memberId]
        );

        $remaining = $amount;
        $ts = date('Y-m-d H:i:s');
        foreach ($installments as $inst) {
            if (bccomp($remaining, '0.01', 2) < 0) break;

            $due = bcsub($inst['installment_amount'], $inst['paid_amount'], 2);
            if (bccomp($due, '0', 2) <= 0) continue;

            $pay = bccomp($remaining, $due, 2) >= 0 ? $due : $remaining;
            $newPaid = bcadd($inst['paid_amount'], $pay, 2);
            $status = bccomp($newPaid, $inst['installment_amount'], 2) >= 0 ? 'paid' : 'partial';

            $db->update('installment_schedule', [
                'paid_amount' => $newPaid,
                'payment_id'  => $paymentId,
                'status'      => $status,
                'paid_at'     => $status === 'paid' ? $ts : null,
                'updated_at'  => $ts,
            ], '`id` = ?', [(int) $inst['id']]);

            $remaining = bcsub($remaining, $pay, 2);
        }
    }

    /**
     * Show single payment details.
     */
    public function show(Request $request, string $id): Response
    {
        $db = App::getInstance()->db();
        $payment = $db->selectOne(
            "SELECT p.*, m.full_name_ar as member_name, m.membership_number, 
                    r.receipt_number, e.full_name_ar as received_by_name,
                    ve.full_name_ar as voided_by_name
             FROM payments p
             JOIN members m ON m.id = p.member_id
             LEFT JOIN receipts r ON r.id = p.receipt_id
             LEFT JOIN employees e ON e.id = p.received_by_employee_id
             LEFT JOIN employees ve ON ve.id = p.voided_by
             WHERE p.id = ?",
            [(int) $id]
        );
        if (!$payment) {
            return $this->redirect('/payments')->withError('الدفعة غير موجودة');
        }

        // Check if void is allowed
        $canVoid = ['allowed' => false];
        if (!$payment['is_voided']) {
            $hoursSince = (time() - strtotime($payment['created_at'])) / 3600;
            $canVoid['allowed'] = ($hoursSince <= 24);
            // Super admin can always void
            $employee = App::getInstance()->currentEmployee();
            if ($employee) {
                $sa = $db->selectOne(
                    "SELECT 1 FROM employee_roles er JOIN roles r ON r.id = er.role_id 
                     WHERE er.employee_id = ? AND r.role_code = 'super_admin' AND er.is_active = 1 LIMIT 1",
                    [(int) $employee->id]
                );
                if ($sa) $canVoid['allowed'] = true;
            }
        }

        return $this->view('Payments.Views.history', [
            'payment'    => $payment,
            'canVoid'    => $canVoid,
            'singleView' => true,
            'rows'       => [],
            'pagination' => ['last_page' => 1, 'current_page' => 1],
            'filters'    => [],
        ]);
    }

    /**
     * Void a payment.
     */
    public function void(Request $request, string $id): Response
    {
        $reason = trim((string) $request->post('void_reason', ''));
        if ($reason === '') {
            return $this->redirect("/payments/{$id}")->withError('سبب الإلغاء مطلوب');
        }

        $result = PaymentService::voidPayment((int) $id, $reason);
        if (!$result['success']) {
            return $this->redirect("/payments/{$id}")->withError($result['error']);
        }

        return $this->redirect("/payments/{$id}")->withSuccess('تم إلغاء الدفعة والإيصال');
    }

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

        $payments = Payment::getForMember((int) $memberId, true);
        $summary = BalanceCalculator::getSummary((int) $memberId);

        return $this->view('Payments.Views.history', [
            'rows'       => $payments,
            'member'     => $member,
            'summary'    => $summary,
            'memberView' => true,
            'pagination' => ['last_page' => 1, 'current_page' => 1],
            'filters'    => [],
        ]);
    }

    /**
     * Daily cash report.
     */
    public function dailyReport(Request $request): Response
    {
        $date = $request->get('date', date('Y-m-d'));
        $branchId = $request->get('branch_id') ? (int) $request->get('branch_id') : null;

        $report = Payment::getDailyTotals($date, $branchId);

        $db = App::getInstance()->db();
        $branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");

        // Get individual payments for the day
        $where = 'p.payment_date = ? AND p.is_voided = 0';
        $params = [$date];
        if ($branchId) {
            $where .= ' AND m.branch_id = ?';
            $params[] = $branchId;
        }
        $payments = $db->select(
            "SELECT p.*, m.full_name_ar as member_name, m.membership_number, r.receipt_number
             FROM payments p JOIN members m ON m.id = p.member_id LEFT JOIN receipts r ON r.id = p.receipt_id
             WHERE {$where} ORDER BY p.id ASC",
            $params
        );

        return $this->view('Payments.Views.daily-report', [
            'date'     => $date,
            'branchId' => $branchId,
            'report'   => $report,
            'payments' => $payments,
            'branches' => $branches,
        ]);
    }
}