<?php

namespace App\Services\Invoices;

use App\Models\Enrollment;
use App\Models\EventCharges;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Refund;
use App\Models\StudentClass;
use App\Models\User;
use DateTime;
use DateTimeZone;
use Illuminate\Support\Facades\DB;

class InvoiceGenerationService
{
    public function __construct(
        private InvoiceConfigService $config,
        private InvoiceTuitionService $tuitionService
    ) {
    }

    public function generateInvoice(int $parentId, ?string $schoolYear = null): array
    {
        $schoolYear = $schoolYear ?: $this->config->getSchoolYear();
        $semester = $this->config->getSemester();

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

        if (empty($enrollments)) {
            return [
                'ok' => false,
                'message' => 'No enrollment records found.',
            ];
        }

        $registeredKids = [];
        $withdrawnKids = [];

        foreach ($enrollments as $enrollment) {
            $student = [
                'student_id' => $enrollment['student_id'],
                'parent_id' => $enrollment['parent_id'],
                'class_section_id' => $enrollment['class_section_id'],
                'enrollment_status' => $enrollment['enrollment_status'],
                'school_year' => $enrollment['school_year'],
                'semester' => $enrollment['semester'],
                'admission_status' => $enrollment['admission_status'],
                'is_withdrawn' => $enrollment['is_withdrawn'],
            ];

            if (in_array($enrollment['enrollment_status'], ['enrolled', 'payment pending'], true)) {
                $registeredKids[] = $student;
            } elseif (in_array($enrollment['enrollment_status'], ['withdrawn', 'refund pending', 'withdraw under review'], true)) {
                $withdrawnKids[] = $student;
            }
        }

        $registeredKids = array_values(array_filter($registeredKids, function ($student) use ($schoolYear) {
            $sid = (int) ($student['student_id'] ?? 0);
            return $sid > 0 && StudentClass::hasNonEventAssignment($sid, $schoolYear);
        }));

        $withdrawnKids = array_values(array_filter($withdrawnKids, function ($student) use ($schoolYear) {
            $sid = (int) ($student['student_id'] ?? 0);
            return $sid > 0 && StudentClass::hasNonEventAssignment($sid, $schoolYear);
        }));

        $tuitionFee = $this->tuitionService->calculateTuitionFee(
            $registeredKids,
            $withdrawnKids,
            $this->config->getRefundDeadline()
        );

        $eventsList = EventCharges::getChargesWithEventInfo($parentId, $schoolYear);
        $eventChargeTotal = array_sum(array_column($eventsList, 'charged'));

        $this->recalculateAndUpdateDiscount($parentId, $schoolYear, $tuitionFee, $enrollments);

        $refundPaid = (new Refund())->getTotalApprovedRefundByParentIdAndSchoolYear($parentId, $schoolYear);
        $totalPaid = Payment::getTotalPaidByParentId($parentId, $schoolYear);

        $totalAmount = max(0.0, (float) $tuitionFee) + (float) $eventChargeTotal;

        $existingInvoices = Invoice::getInvoicesByParentId($parentId, $schoolYear);
        $updated = false;
        $updatedIds = [];
        $insertId = null;

        if (!empty($existingInvoices)) {
            foreach ($existingInvoices as $invoice) {
                if (!isset($invoice['id'])) {
                    continue;
                }

                $extrasSum = $this->sumInvoiceAdditionalCharges((int) $invoice['id'], $schoolYear);
                $newTotal = round($totalAmount + $extrasSum, 2);

                $invDiscount = $this->sumInvoiceDiscounts((int) $invoice['id']);
                $invRefunds = $this->sumInvoiceRefunds((int) $invoice['id'], $schoolYear);
                $paidOnInv = (float) ($invoice['paid_amount'] ?? 0.0);

                $newBalance = $newTotal - $invDiscount - $invRefunds - $paidOnInv;
                $newStatus = $newBalance <= 0.00001
                    ? 'Paid'
                    : ($paidOnInv > 0 ? 'Partially Paid' : 'Unpaid');

                Invoice::query()
                    ->whereKey($invoice['id'])
                    ->update([
                        'total_amount' => $newTotal,
                        'paid_amount' => $paidOnInv,
                        'balance' => $newBalance,
                        'status' => $newStatus,
                        'updated_at' => utc_now(),
                    ]);

                $updatedIds[] = (int) $invoice['id'];
                $updated = true;
            }
        } else {
            $schoolId = User::getSchoolIdByUserId($parentId);
            $invoiceNumber = $schoolId
                ? ('INV-' . $schoolId . '-' . uniqid())
                : uniqid('INV-');

            $issueUtc = (new DateTime('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s');
            $dueUtc = null;
            $dueDate = $this->config->getDueDate();
            if (!empty($dueDate)) {
                $dueLocal = new DateTime($dueDate . ' 19:59:59', new DateTimeZone($this->config->getTimezone()));
                $dueLocal->setTimezone(new DateTimeZone('UTC'));
                $dueUtc = $dueLocal->format('Y-m-d H:i:s');
            }

            $insertId = Invoice::query()->insertGetId([
                'parent_id' => $parentId,
                'invoice_number' => $invoiceNumber,
                'total_amount' => $totalAmount,
                'paid_amount' => 0,
                'balance' => $totalAmount,
                'status' => 'Unpaid',
                'school_year' => $schoolYear,
                'semester' => $semester,
                'issue_date' => $issueUtc,
                'due_date' => $dueUtc,
                'created_at' => utc_now(),
                'updated_at' => utc_now(),
            ]);
        }

        return [
            'ok' => true,
            'updated' => $updated,
            'updated_ids' => $updatedIds,
            'insert_id' => $insertId ? (int) $insertId : null,
            'total_paid' => $totalPaid,
            'refund_paid' => $refundPaid,
        ];
    }

    private function recalculateAndUpdateDiscount(int $parentId, string $schoolYear, float $tuitionFee, array $enrollments): void
    {
        $eventChargeTotal = 0.0;
        try {
            $eventsList = EventCharges::getChargesWithEventInfo($parentId, $schoolYear);
            $eventChargeTotal = array_sum(array_column($eventsList, 'charged'));
        } catch (\Throwable $e) {
        }

        $invoices = Invoice::getInvoicesByParentId($parentId, $schoolYear);
        if (empty($invoices)) {
            return;
        }

        foreach ($invoices as $invoice) {
            if (!isset($invoice['id'])) {
                continue;
            }

            $invoiceId = (int) $invoice['id'];
            $discountUsage = DB::table('discount_usages as du')
                ->select('du.id', 'dv.id as voucher_id', 'dv.discount_type', 'dv.discount_value')
                ->join('discount_vouchers as dv', 'du.voucher_id', '=', 'dv.id')
                ->join('invoices as i', 'du.invoice_id', '=', 'i.id')
                ->where('du.invoice_id', $invoiceId)
                ->where('i.school_year', $schoolYear)
                ->first();

            if (!$discountUsage) {
                continue;
            }

            $extrasSum = $this->sumInvoiceAdditionalCharges($invoiceId, $schoolYear);
            $baseTotal = round($tuitionFee + $eventChargeTotal + $extrasSum, 2);

            if (($discountUsage->discount_type ?? '') === 'percent') {
                $discountAmount = round(($baseTotal * (float) $discountUsage->discount_value) / 100, 2);
            } else {
                $discountAmount = min((float) $discountUsage->discount_value, $baseTotal);
            }

            DB::table('discount_usages')
                ->where('id', $discountUsage->id)
                ->update([
                    'discount_amount' => $discountAmount,
                    'updated_at' => utc_now(),
                    'updated_by' => auth()->id(),
                ]);
        }
    }

    private function sumInvoiceAdditionalCharges(int $invoiceId, string $schoolYear): float
    {
        $extrasSum = 0.0;
        $rows = DB::table('additional_charges')
            ->select('charge_type', 'amount')
            ->where('invoice_id', $invoiceId)
            ->where('school_year', $schoolYear)
            ->where('status', 'applied')
            ->get();

        foreach ($rows as $row) {
            $amount = (float) ($row->amount ?? 0);
            $type = strtolower((string) ($row->charge_type ?? 'add'));
            if ($type === 'deduct') {
                $amount = -abs($amount);
            } else {
                $amount = abs($amount);
            }
            $extrasSum += $amount;
        }

        return $extrasSum;
    }

    private function sumInvoiceDiscounts(int $invoiceId): float
    {
        $row = DB::table('discount_usages')
            ->selectRaw('COALESCE(SUM(discount_amount),0) AS tot')
            ->where('invoice_id', $invoiceId)
            ->first();

        return (float) ($row->tot ?? 0.0);
    }

    private function sumInvoiceRefunds(int $invoiceId, string $schoolYear): float
    {
        $row = DB::table('refunds')
            ->selectRaw('COALESCE(SUM(refund_paid_amount),0) AS tot')
            ->where('invoice_id', $invoiceId)
            ->where('school_year', $schoolYear)
            ->whereIn('status', ['Partial', 'Paid'])
            ->first();

        return (float) ($row->tot ?? 0.0);
    }
}
