<?php

namespace App\Libraries;

use App\Interfaces\TuitionCalculatorInterface;
use App\Libraries\Tuition\NewTuitionCalculatorService;
use App\Libraries\Tuition\OldTuitionCalculatorService;
use App\Models\AdditionalChargeModel;
use App\Models\ClassSectionModel;
use App\Models\ConfigurationModel;
use App\Models\DiscountUsageModel;
use App\Models\EnrollmentModel;
use App\Models\EventChargesModel;
use App\Models\InvoiceEventModel;
use App\Models\InvoiceModel;
use App\Models\PaymentModel;
use App\Models\RefundModel;
use App\Models\StudentClassModel;
use App\Models\StudentModel;

class InvoiceLedgerService
{
    protected InvoiceModel $invoiceModel;
    protected PaymentModel $paymentModel;
    protected RefundModel $refundModel;
    protected DiscountUsageModel $discountUsageModel;
    protected AdditionalChargeModel $additionalChargeModel;
    protected ConfigurationModel $configurationModel;
    protected EnrollmentModel $enrollmentModel;
    protected StudentClassModel $studentClassModel;
    protected ClassSectionModel $classSectionModel;
    protected EventChargesModel $eventChargesModel;
    protected InvoiceEventModel $invoiceEventModel;
    protected StudentModel $studentModel;
    protected TuitionCalculatorInterface $oldCalculator;
    protected TuitionCalculatorInterface $newCalculator;

    public function __construct()
    {
        $this->invoiceModel = new InvoiceModel();
        $this->paymentModel = new PaymentModel();
        $this->refundModel = new RefundModel();
        $this->discountUsageModel = new DiscountUsageModel();
        $this->additionalChargeModel = new AdditionalChargeModel();
        $this->configurationModel = new ConfigurationModel();
        $this->enrollmentModel = new EnrollmentModel();
        $this->studentClassModel = new StudentClassModel();
        $this->classSectionModel = new ClassSectionModel();
        $this->eventChargesModel = new EventChargesModel();
        $this->invoiceEventModel = new InvoiceEventModel();
        $this->studentModel = new StudentModel();
        $this->oldCalculator = new OldTuitionCalculatorService();
        $this->newCalculator = new NewTuitionCalculatorService();
    }

    public function calculateInvoice(int $invoiceId): array
    {
        $invoice = $this->loadInvoice($invoiceId);
        if ($invoice === null) {
            throw new \RuntimeException('Invoice not found.');
        }

        $tuitionTotal = $this->calculateTuitionTotal($invoice);
        $eventTotal = $this->calculateEventTotal($invoice);
        $additionalTotal = $this->calculateAdditionalCharges($invoiceId);
        $discountRawTotal = $this->calculateDiscounts($invoiceId);
        $paidTotal = $this->calculateValidPayments($invoiceId);
        $refundPaidTotal = $this->calculatePaidRefunds($invoiceId);

        $tuitionCents = $this->toCents($tuitionTotal);
        $eventCents = $this->toCents($eventTotal);
        $additionalCents = $this->toCents($additionalTotal);
        $discountRawCents = $this->toCents($discountRawTotal);
        $paidCents = $this->toCents($paidTotal);
        $refundPaidCents = $this->toCents($refundPaidTotal);

        $discountBaseCents = max(0, $tuitionCents + $additionalCents);
        $discountCents = min($discountRawCents, $discountBaseCents);
        $totalAmountCents = $tuitionCents + $eventCents + $additionalCents;
        $balanceCents = max(0, $totalAmountCents - $discountCents - $paidCents - $refundPaidCents);

        if ($balanceCents === 0) {
            $status = FinancialStatus::INVOICE_PAID;
        } elseif ($paidCents > 0 || $discountCents > 0 || $refundPaidCents > 0) {
            $status = FinancialStatus::INVOICE_PARTIALLY_PAID;
        } else {
            $status = FinancialStatus::INVOICE_UNPAID;
        }

        return [
            'invoice_id' => $invoiceId,
            'tuition_total' => $this->fromCents($tuitionCents),
            'event_total' => $this->fromCents($eventCents),
            'additional_total' => $this->fromCents($additionalCents),
            'discount_total' => $this->fromCents($discountCents),
            'discount_raw_total' => $this->fromCents($discountRawCents),
            'paid_amount' => $this->fromCents($paidCents),
            'refund_paid_total' => $this->fromCents($refundPaidCents),
            'total_amount' => $this->fromCents($totalAmountCents),
            'balance' => $this->fromCents($balanceCents),
            'status' => $status,
            'has_discount' => $discountCents > 0 ? 1 : 0,
        ];
    }

    public function recalculateInvoice(int $invoiceId): array
    {
        $calculation = $this->calculateInvoice($invoiceId);
        $payload = [
            'total_amount' => $calculation['total_amount'],
            'paid_amount' => $calculation['paid_amount'],
            'balance' => $calculation['balance'],
            'status' => $calculation['status'],
            'has_discount' => $calculation['has_discount'],
            'updated_at' => utc_now(),
        ];

        if ($this->invoiceModel->db->fieldExists('discount', $this->invoiceModel->table)) {
            $payload['discount'] = $calculation['discount_total'];
        }

        $this->invoiceModel->update($invoiceId, $payload);

        return $calculation;
    }

    protected function loadInvoice(int $invoiceId): ?array
    {
        return $this->invoiceModel->find($invoiceId);
    }

    protected function calculateTuitionTotal(array $invoice): float
    {
        $parentId = (int) ($invoice['parent_id'] ?? 0);
        $schoolYear = (string) ($invoice['school_year'] ?? '');
        if ($parentId <= 0 || $schoolYear === '') {
            return 0.0;
        }

        $students = $this->loadTuitionStudents($parentId, $schoolYear);
        $config = $this->getTuitionConfig();
        $calculator = $this->resolveActiveCalculator();

        return (float) ($calculator->calculateFamilyTuition($students, $config)['total'] ?? 0.0);
    }

    protected function calculateEventTotal(array $invoice): float
    {
        $invoiceId = (int) ($invoice['id'] ?? 0);
        $invoiceEventRows = $this->invoiceEventModel
            ->select('COALESCE(SUM(amount),0) AS total_amount')
            ->where('invoice_id', $invoiceId)
            ->findAll();

        if (!empty($invoiceEventRows)) {
            $total = (float) ($invoiceEventRows[0]['total_amount'] ?? 0);
            if ($total > 0) {
                return $total;
            }
        }

        $rows = $this->eventChargesModel
            ->select('COALESCE(SUM(charged),0) AS total_amount')
            ->where('parent_id', (int) ($invoice['parent_id'] ?? 0))
            ->where('school_year', (string) ($invoice['school_year'] ?? ''))
            ->where('semester', (string) ($invoice['semester'] ?? ''))
            ->findAll();

        return (float) ($rows[0]['total_amount'] ?? 0);
    }

    protected function calculateAdditionalCharges(int $invoiceId): float
    {
        $rows = $this->additionalChargeModel
            ->select('COALESCE(SUM(amount),0) AS total_amount')
            ->where('invoice_id', $invoiceId)
            ->whereNotIn('status', ['void', FinancialStatus::ADDITIONAL_CHARGE_VOIDED, 'cancelled', 'canceled'])
            ->findAll();

        return (float) ($rows[0]['total_amount'] ?? 0);
    }

    protected function calculateDiscounts(int $invoiceId): float
    {
        $row = $this->discountUsageModel
            ->select('COALESCE(SUM(discount_amount),0) AS total_amount')
            ->where('invoice_id', $invoiceId)
            ->first();

        return (float) ($row['total_amount'] ?? 0);
    }

    protected function calculateValidPayments(int $invoiceId): float
    {
        $query = $this->paymentModel
            ->select('COALESCE(SUM(paid_amount),0) AS total_amount')
            ->where('invoice_id', $invoiceId);

        if ($this->paymentModel->db->fieldExists('status', $this->paymentModel->table)) {
            $query->groupStart()
                ->whereNotIn('status', FinancialStatus::EXCLUDED_PAYMENT_STATUSES)
                ->orWhere('status IS NULL', null, false)
            ->groupEnd();
        }

        if ($this->paymentModel->db->fieldExists('is_void', $this->paymentModel->table)) {
            $query->groupStart()
                ->where('is_void', 0)
                ->orWhere('is_void IS NULL', null, false)
            ->groupEnd();
        }

        $row = $query->first();

        return (float) ($row['total_amount'] ?? 0);
    }

    protected function calculatePaidRefunds(int $invoiceId): float
    {
        $row = $this->refundModel
            ->select('COALESCE(SUM(refund_paid_amount),0) AS total_amount')
            ->where('invoice_id', $invoiceId)
            ->whereIn('status', FinancialStatus::REFUND_REDUCES_INVOICE_STATUSES)
            ->first();

        return (float) ($row['total_amount'] ?? 0);
    }

    protected function loadTuitionStudents(int $parentId, string $schoolYear): array
    {
        $enrollments = $this->enrollmentModel
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->findAll();

        if (empty($enrollments)) {
            return [];
        }

        $refundDeadline = (string) ($this->configurationModel->getConfig('refund_deadline') ?? '');
        $includeWithdrawn = !$this->isWithinRefundWindow($refundDeadline);
        $eligibleStatuses = ['enrolled', 'payment pending'];

        if ($includeWithdrawn) {
            array_push($eligibleStatuses, 'withdrawn', 'refund pending', 'withdraw under review');
        }

        $students = [];
        foreach ($enrollments as $enrollment) {
            $status = strtolower(trim((string) ($enrollment['enrollment_status'] ?? '')));
            $studentId = (int) ($enrollment['student_id'] ?? 0);
            if ($studentId <= 0 || !in_array($status, $eligibleStatuses, true)) {
                continue;
            }

            if (!$this->studentClassModel->hasNonEventAssignment($studentId, $schoolYear)) {
                continue;
            }

            $gradeName = $this->resolveGradeName($studentId, $schoolYear, $enrollment['class_section_id'] ?? null);
            $student = $this->studentModel->find($studentId) ?? [];

            $students[] = [
                'student_id' => $studentId,
                'student_name' => trim(((string) ($student['firstname'] ?? '')) . ' ' . ((string) ($student['lastname'] ?? ''))),
                'grade_level' => $gradeName,
            ];
        }

        return $students;
    }

    protected function resolveGradeName(int $studentId, string $schoolYear, $classSectionId = null): string
    {
        $sectionId = $classSectionId;
        if (empty($sectionId)) {
            $row = $this->studentClassModel
                ->select('class_section_id')
                ->where('student_id', $studentId)
                ->where('school_year', $schoolYear)
                ->orderBy('updated_at', 'DESC')
                ->first();
            $sectionId = $row['class_section_id'] ?? null;
        }

        if (empty($sectionId)) {
            return 'N/A';
        }

        $name = $this->classSectionModel->getClassSectionNameBySectionId($sectionId);

        return is_string($name) && $name !== '' ? strtoupper(trim($name)) : 'N/A';
    }

    protected function resolveActiveCalculator(): TuitionCalculatorInterface
    {
        $version = strtolower(trim((string) ($this->configurationModel->getConfig('tuition_calculator_version') ?? 'old')));

        return $version === 'new' ? $this->newCalculator : $this->oldCalculator;
    }

    protected function getTuitionConfig(): array
    {
        return [
            'grade_fee' => $this->configurationModel->getConfig('grade_fee'),
            'first_student_fee' => $this->configurationModel->getConfig('first_student_fee'),
            'second_student_fee' => $this->configurationModel->getConfig('second_student_fee'),
            'youth_fee' => $this->configurationModel->getConfig('youth_fee'),
            'new_tuition_full_amount' => $this->configurationModel->getConfig('new_tuition_full_amount'),
            'new_tuition_youth_amount' => $this->configurationModel->getConfig('new_tuition_youth_amount'),
            'new_tuition_second_student_discount' => $this->configurationModel->getConfig('new_tuition_second_student_discount'),
            'new_tuition_third_student_discount' => $this->configurationModel->getConfig('new_tuition_third_student_discount'),
            'new_tuition_fourth_plus_discount' => $this->configurationModel->getConfig('new_tuition_fourth_plus_discount'),
        ];
    }

    protected function isWithinRefundWindow(string $refundDeadline): bool
    {
        if ($refundDeadline === '') {
            return true;
        }

        try {
            $timeZone = new \DateTimeZone((string) (config('School')->attendance['timezone'] ?? user_timezone()));
            $today = new \DateTimeImmutable('today', $timeZone);
            $deadline = new \DateTimeImmutable($refundDeadline, $timeZone);

            return $today <= $deadline;
        } catch (\Throwable $e) {
            return true;
        }
    }

    protected function toCents($amount): int
    {
        return (int) round(((float) $amount) * 100);
    }

    protected function fromCents(int $cents): string
    {
        return number_format($cents / 100, 2, '.', '');
    }
}
