<?php

namespace App\Services\Discounts;

use App\Models\AdditionalCharge;
use App\Models\ClassSection;
use App\Models\Configuration;
use App\Models\Enrollment;
use App\Models\EventCharges;
use App\Models\Invoice;
use App\Models\Payment;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class DiscountInvoiceService
{
    public function getCurrentInvoiceBalance(int $invoiceId, string $schoolYear): float
    {
        $invoice = Invoice::query()->find($invoiceId);
        if (!$invoice) {
            return 0.0;
        }

        $paymentQuery = Payment::query()
            ->selectRaw('COALESCE(SUM(paid_amount),0) AS total_paid')
            ->where('invoice_id', $invoiceId)
            ->where('school_year', $schoolYear);

        if (Schema::hasColumn('payments', 'status')) {
            $paymentQuery->where(function ($q) {
                $q->whereNotIn('status', [
                    'void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled',
                ])->orWhereNull('status');
            });
        }

        if (Schema::hasColumn('payments', 'is_void')) {
            $paymentQuery->where(function ($q) {
                $q->where('is_void', 0)->orWhereNull('is_void');
            });
        }

        $rowPaid = (array) $paymentQuery->first();
        $totalPaid = (float) ($rowPaid['total_paid'] ?? 0);

        $rowDisc = (array) DB::table('discount_usages as du')
            ->join('invoices as i', 'i.id', '=', 'du.invoice_id')
            ->where('du.invoice_id', $invoiceId)
            ->where('i.school_year', $schoolYear)
            ->selectRaw('COALESCE(SUM(du.discount_amount),0) AS total_disc')
            ->first();
        $totalDisc = (float) ($rowDisc['total_disc'] ?? 0);

        $rowRefund = (array) DB::table('refunds')
            ->selectRaw('COALESCE(SUM(refund_paid_amount),0) AS total_refund_paid')
            ->where('invoice_id', $invoiceId)
            ->where('school_year', $schoolYear)
            ->whereIn('status', ['Partial', 'Paid'])
            ->first();
        $totalRefundPaid = (float) ($rowRefund['total_refund_paid'] ?? 0);

        $total = (float) ($invoice->total_amount ?? 0);
        return max(0.0, round($total - $totalPaid - $totalDisc - $totalRefundPaid, 2));
    }

    public function recalculateInvoice(int $invoiceId, string $schoolYear): void
    {
        $invoice = Invoice::query()->find($invoiceId);
        if (!$invoice) {
            return;
        }

        $parentId = (int) ($invoice->parent_id ?? 0);
        if ($parentId <= 0) {
            return;
        }

        $enrollments = Enrollment::query()
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        $registered = [];
        $withdrawn = [];
        foreach ($enrollments as $e) {
            $row = [
                'student_id' => (int) ($e['student_id'] ?? 0),
                'class_section_id' => $e['class_section_id'] ?? null,
                'enrollment_status' => (string) ($e['enrollment_status'] ?? ''),
            ];
            if (in_array($row['enrollment_status'], ['enrolled', 'payment pending'], true)) {
                $registered[] = $row;
            } elseif (in_array($row['enrollment_status'], ['withdrawn', 'refund pending', 'withdraw under review'], true)) {
                $withdrawn[] = $row;
            }
        }

        $refundDeadline = (string) (Configuration::getConfig('refund_deadline') ?? '');
        $refundAllowed = true;
        if ($refundDeadline !== '') {
            try {
                $tzName = function_exists('user_timezone') ? (string) user_timezone() : 'UTC';
                $tz = new \DateTimeZone($tzName);
                $today = new \DateTimeImmutable('today', $tz);
                $deadline = new \DateTimeImmutable($refundDeadline, $tz);
                $refundAllowed = $today <= $deadline;
            } catch (\Throwable $e) {
                $refundAllowed = true;
            }
        }

        $tuitionStudents = $registered;
        if (!$refundAllowed) {
            $tuitionStudents = array_merge($tuitionStudents, $withdrawn);
        }

        $gradeFee = (int) (Configuration::getConfig('grade_fee') ?? 9);
        $firstStudentFee = (float) (Configuration::getConfig('first_student_fee') ?? 350);
        $secondStudentFee = (float) (Configuration::getConfig('second_student_fee') ?? 200);
        $youthFee = (float) (Configuration::getConfig('youth_fee') ?? 180);

        foreach ($tuitionStudents as &$s) {
            $name = null;
            if (!empty($s['class_section_id'])) {
                $name = ClassSection::getClassSectionNameBySectionId($s['class_section_id']);
            }
            $s['grade_name'] = is_string($name) ? strtoupper(trim($name)) : 'N/A';
        }
        unset($s);

        $regularCount = 0;
        $youthCount = 0;
        foreach ($tuitionStudents as $s) {
            $lvl = $this->parseGradeLevel((string) ($s['grade_name'] ?? ''), $gradeFee);
            if ($lvl > $gradeFee) {
                $youthCount++;
            } else {
                $regularCount++;
            }
        }

        $tuitionSubtotal = 0.0;
        $tuitionSubtotal += $youthCount * $youthFee;
        if ($regularCount >= 2) {
            $tuitionSubtotal += $firstStudentFee + ($regularCount - 1) * $secondStudentFee;
        } elseif ($regularCount === 1) {
            $tuitionSubtotal += $firstStudentFee;
        }

        $eventSubtotal = 0.0;
        $events = EventCharges::getChargesWithEventInfo($parentId, $schoolYear) ?? [];
        foreach ($events as $ev) {
            $eventSubtotal += (float) ($ev['charged'] ?? 0.0);
        }

        $additionalSubtotal = 0.0;
        $rows = AdditionalCharge::query()
            ->select('charge_type', 'amount')
            ->where('invoice_id', $invoiceId)
            ->where('status', 'applied')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        foreach ($rows as $r) {
            $amt = (float) ($r['amount'] ?? 0);
            $typ = strtolower((string) ($r['charge_type'] ?? 'add'));
            $amt = $typ === 'deduct' ? -abs($amt) : abs($amt);
            $additionalSubtotal += $amt;
        }

        $newTotal = round($tuitionSubtotal + $eventSubtotal + $additionalSubtotal, 2);

        $paymentQuery = Payment::query()
            ->where('invoice_id', $invoiceId)
            ->where('school_year', $schoolYear);

        if (Schema::hasColumn('payments', 'status')) {
            $paymentQuery->where(function ($q) {
                $q->whereNotIn('status', [
                    'void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled',
                ])->orWhereNull('status');
            });
        }
        if (Schema::hasColumn('payments', 'is_void')) {
            $paymentQuery->where(function ($q) {
                $q->where('is_void', 0)->orWhereNull('is_void');
            });
        }

        $payments = $paymentQuery->get()->map(fn ($row) => (array) $row)->all();
        $totalPaid = 0.0;
        foreach ($payments as $p) {
            $totalPaid += (float) ($p['paid_amount'] ?? 0);
        }

        $discRow = (array) DB::table('discount_usages')
            ->selectRaw('COALESCE(SUM(discount_amount),0) AS total_disc')
            ->where('invoice_id', $invoiceId)
            ->first();
        $totalDisc = (float) ($discRow['total_disc'] ?? 0);

        $refundRow = (array) DB::table('refunds')
            ->selectRaw('COALESCE(SUM(refund_paid_amount),0) AS total_refund_paid')
            ->where('invoice_id', $invoiceId)
            ->whereIn('status', ['Partial', 'Paid'])
            ->first();
        $totalRefundPaid = (float) ($refundRow['total_refund_paid'] ?? 0);

        $newBalance = max(0.0, $newTotal - $totalDisc - $totalPaid - $totalRefundPaid);
        $newStatus = $newBalance <= 0.00001 ? 'Paid' : ($totalPaid > 0 ? 'Partially Paid' : 'Unpaid');

        $updateData = [
            'total_amount' => $newTotal,
            'paid_amount' => $totalPaid,
            'balance' => $newBalance,
            'status' => $newStatus,
            'has_discount' => $totalDisc > 0.0 ? 1 : 0,
        ];

        if (Schema::hasColumn('invoices', 'discount')) {
            $updateData['discount'] = $totalDisc;
        }

        Invoice::query()->whereKey($invoiceId)->update($updateData);
    }

    public function updateEnrollmentStatusIfPaid(int $invoiceId, string $schoolYear): int
    {
        $invoice = Invoice::query()->find($invoiceId);
        if (!$invoice) {
            return 0;
        }

        $total = (float) ($invoice->total_amount ?? 0);
        $balance = (float) ($invoice->balance ?? 0);
        if (!($total > 0 && $balance < $total)) {
            return 0;
        }

        $parentId = (int) ($invoice->parent_id ?? 0);
        if ($parentId <= 0 || $schoolYear === '') {
            return 0;
        }

        $paidEnrollmentIds = [];
        if (Schema::hasTable('invoice_items') && Schema::hasColumn('invoice_items', 'enrollment_id')) {
            $rows = DB::table('invoice_items')
                ->select('enrollment_id')
                ->where('invoice_id', $invoiceId)
                ->whereNotNull('enrollment_id')
                ->get()
                ->map(fn ($row) => (array) $row)
                ->all();

            foreach ($rows as $r) {
                $eid = (int) ($r['enrollment_id'] ?? 0);
                if ($eid > 0) {
                    $paidEnrollmentIds[] = $eid;
                }
            }
        }

        $query = Enrollment::query()
            ->select('id')
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->where(function ($q) {
                $q->where('enrollment_status', 'payment pending')
                    ->orWhere('enrollment_status', 'Payment pending')
                    ->orWhere('enrollment_status', 'Payment Pending')
                    ->orWhere('enrollment_status', 'PAYMENT PENDING')
                    ->orWhereRaw("LOWER(TRIM(REPLACE(enrollment_status, CHAR(160), ' '))) = 'payment pending'");
            });

        if (!empty($paidEnrollmentIds)) {
            $query->whereIn('id', array_values(array_unique($paidEnrollmentIds)));
        }

        $toUpdateIds = $query->get()->pluck('id')->map(fn ($id) => (int) $id)->all();
        if (empty($toUpdateIds)) {
            return 0;
        }

        $affected = Enrollment::query()
            ->whereIn('id', $toUpdateIds)
            ->update([
                'enrollment_status' => 'enrolled',
                'enrollment_date' => $this->formatLocalDate('Y-m-d'),
            ]);

        return (int) $affected;
    }

    public function buildPaymentEventData(
        int $invoiceId,
        string $transactionIdOrRef,
        float $amount,
        string $paymentMethod,
        string $paymentDate,
        ?string $checkNumber,
        int $installmentSeq,
        float $preBalance,
        float $postBalance,
        string $schoolYear,
        string $semester
    ): array {
        $invoice = Invoice::query()->find($invoiceId);
        if (!$invoice) {
            throw new \RuntimeException("Invoice {$invoiceId} not found");
        }

        $parentRow = (array) DB::table('users')
            ->select('id', 'firstname', 'lastname', 'email')
            ->where('id', $invoice->parent_id)
            ->first();

        if (empty($parentRow)) {
            throw new \RuntimeException("Parent not found for invoice {$invoiceId}");
        }

        $studentRows = DB::table('students as s')
            ->select('s.id', 's.firstname', 's.lastname', 'sc.class_section_id')
            ->leftJoin('student_class as sc', 'sc.student_id', '=', 's.id')
            ->where('s.parent_id', $invoice->parent_id)
            ->where('sc.school_year', $schoolYear)
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        $data = [
            'user_id' => (int) $parentRow['id'],
            'email' => $parentRow['email'],
            'firstname' => $parentRow['firstname'],
            'lastname' => $parentRow['lastname'],
            'school_year' => $schoolYear,
            'semester' => $semester,
            'invoice_id' => $invoiceId,
            'transaction_id' => $transactionIdOrRef,
            'payment_date' => $paymentDate,
            'amount' => $amount,
            'method' => $paymentMethod,
            'check_number' => $checkNumber,
            'installment_seq' => $installmentSeq,
            'invoice_total' => (float) $invoice->total_amount,
            'pre_balance' => $preBalance,
            'post_balance' => $postBalance,
            'portalLink' => url('parent/invoices/' . $invoiceId),
        ];

        return [$data, $studentRows];
    }

    private function parseGradeLevel(string $grade, int $gradeFee): int
    {
        if (is_numeric($grade)) {
            return (int) $grade;
        }
        $g = strtoupper(trim($grade));
        $g = preg_replace('/\\s+/', ' ', $g);
        $g = str_replace(['.', '_', '-'], ['', '', ' '], $g);

        $kg = ['K', 'KG', 'K G', 'KINDER', 'KINDERGARTEN'];
        if (in_array($g, $kg, true)) {
            return 1;
        }

        $pk = ['PK', 'P K', 'PREK', 'PRE K', 'PRE KINDER', 'PREKINDER'];
        if (in_array($g, $pk, true)) {
            return -1;
        }

        if (preg_match('/^Y(?:OUTH)?\\s*(\\d+)?$/', $g, $m)) {
            $n = isset($m[1]) && $m[1] !== '' ? max(1, (int) $m[1]) : 1;
            return $gradeFee + $n;
        }

        if (preg_match('/^(?:GR?ADE\\s*)?(\\d{1,2})\\s*([A-Z]*)$/', $g, $m)) {
            return (int) $m[1];
        }

        return 999;
    }

    private function formatLocalDate(string $format): string
    {
        if (function_exists('local_date') && function_exists('utc_now')) {
            return (string) local_date(utc_now(), $format);
        }
        return now()->format($format);
    }
}
