<?php

namespace App\Services\Discounts;

use App\Models\DiscountVoucher;
use App\Models\Invoice;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class DiscountApplyService
{
    private DiscountContextService $context;
    private DiscountInvoiceService $invoiceService;

    public function __construct(DiscountContextService $context, DiscountInvoiceService $invoiceService)
    {
        $this->context = $context;
        $this->invoiceService = $invoiceService;
    }

    public function applyVoucher(int $voucherId, array $parentIds, bool $allowAdditional, int $actorId): array
    {
        $schoolYear = $this->context->getSchoolYear();
        $semester = $this->context->getSemester();

        $voucher = DiscountVoucher::query()->find($voucherId);
        if (!$voucher) {
            return ['ok' => false, 'message' => 'Voucher not found.'];
        }

        $voucherDescription = trim((string) ($voucher->description ?? ''));
        $voucherDescription = preg_replace(
            '/^Auto-generated on \\d{4}-\\d{2}-\\d{2}\\s*[—-]\\s*reason:\\s*/i',
            '',
            $voucherDescription
        );

        $maxUsesRaw = $voucher->max_uses;
        $maxUses = ($maxUsesRaw === null || $maxUsesRaw === '') ? null : (int) $maxUsesRaw;
        $timesUsed = (int) ($voucher->times_used ?? 0);
        $remainingUses = ($maxUses === null) ? PHP_INT_MAX : ($maxUses - $timesUsed);
        if ($remainingUses <= 0) {
            return ['ok' => false, 'message' => 'This voucher has reached its maximum allowed uses.'];
        }

        if (!$allowAdditional) {
            $rows = DB::table('discount_usages as du')
                ->select('i.parent_id')
                ->join('invoices as i', 'i.id', '=', 'du.invoice_id')
                ->whereIn('i.parent_id', $parentIds)
                ->where('i.school_year', $schoolYear)
                ->groupBy('i.parent_id')
                ->get()
                ->map(fn ($row) => (array) $row)
                ->all();

            $blockedParentIds = array_map(static fn ($r) => (int) ($r['parent_id'] ?? 0), $rows);
            if (!empty($blockedParentIds)) {
                $parentIds = array_values(array_diff(array_map('intval', $parentIds), $blockedParentIds));
            }
            if (empty($parentIds)) {
                return [
                    'ok' => false,
                    'message' => 'All selected parents already have discounts. Enable "Allow additional discounts" to apply another.',
                ];
            }
        }

        $paymentDate = function_exists('utc_now') ? utc_now() : now('UTC')->toDateTimeString();
        $fullyCoveredInvoiceIds = [];
        $touchedInvoiceIds = [];
        $appliedCount = 0;

        DB::beginTransaction();

        try {
            foreach ($parentIds as $parentId) {
                $invoices = Invoice::getInvoicesByUserId((int) $parentId, $schoolYear);
                if (empty($invoices)) {
                    Log::warning('Discount apply: parent has no invoices', [
                        'parent_id' => (int) $parentId,
                        'voucher_id' => $voucherId,
                    ]);
                    continue;
                }

                foreach ($invoices as $invoice) {
                    if ($remainingUses <= 0) {
                        break 2;
                    }

                    $invoiceId = (int) ($invoice['id'] ?? 0);
                    if ($invoiceId <= 0) {
                        continue;
                    }

                    $initialPreBalance = (float) $this->invoiceService->getCurrentInvoiceBalance($invoiceId, $schoolYear);
                    if ($initialPreBalance <= 0) {
                        continue;
                    }

                    if (!$allowAdditional) {
                        $exists = DB::table('discount_usages as du')
                            ->join('invoices as i', 'du.invoice_id', '=', 'i.id')
                            ->where('du.voucher_id', $voucherId)
                            ->where('du.invoice_id', $invoiceId)
                            ->where('i.school_year', $schoolYear)
                            ->count();
                        if ($exists) {
                            continue;
                        }
                    }

                    $rawDiscount = $voucher->discount_type === 'percent'
                        ? round(((float) ($invoice['total_amount'] ?? 0) * (float) $voucher->discount_value) / 100, 2)
                        : (float) $voucher->discount_value;

                    $discount = min($rawDiscount, $initialPreBalance);
                    if ($discount <= 0) {
                        continue;
                    }

                    $now = function_exists('utc_now') ? utc_now() : now('UTC')->toDateTimeString();
                    DB::table('discount_usages')->insert([
                        'voucher_id' => $voucherId,
                        'invoice_id' => $invoiceId,
                        'parent_id' => (int) $parentId,
                        'discount_amount' => $discount,
                        'description' => $voucherDescription,
                        'school_year' => $schoolYear,
                        'updated_by' => $actorId,
                        'used_at' => $now,
                        'created_at' => $now,
                        'updated_at' => $now,
                        'semester' => $semester,
                    ]);

                    $newBalance = max(0.0, round($initialPreBalance - $discount, 2));
                    DB::table('invoices')
                        ->where('id', $invoiceId)
                        ->update([
                            'balance' => $newBalance,
                            'has_discount' => 1,
                            'updated_at' => $now,
                        ]);

                    $postBalance = round($initialPreBalance - $discount, 2);
                    if ($postBalance < 0) {
                        $postBalance = 0.0;
                    }

                    $currentBalance = (float) $this->invoiceService->getCurrentInvoiceBalance($invoiceId, $schoolYear);

                    DB::table('discount_vouchers')
                        ->where('id', $voucherId)
                        ->update([
                            'times_used' => DB::raw('COALESCE(times_used,0) + 1'),
                        ]);

                    $this->triggerPaymentReceived(
                        $invoiceId,
                        (string) $voucherId,
                        $discount,
                        $paymentDate,
                        $initialPreBalance,
                        $postBalance,
                        $schoolYear,
                        $semester
                    );

                    $touchedInvoiceIds[] = $invoiceId;
                    $appliedCount++;

                    $epsilon = 0.01;
                    $fullyCovered = (abs($discount - $initialPreBalance) <= $epsilon)
                        || ($postBalance <= $epsilon)
                        || ($currentBalance <= $epsilon);

                    if ($fullyCovered) {
                        $fullyCoveredInvoiceIds[] = $invoiceId;
                    }

                    $remainingUses--;

                    if ($remainingUses <= 0 && $maxUses !== null) {
                        DB::table('discount_vouchers')
                            ->where('id', $voucherId)
                            ->update([
                                'is_active' => 0,
                                'updated_at' => $now,
                            ]);
                        break 2;
                    }
                }
            }

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();
            return ['ok' => false, 'message' => 'Voucher application failed. Transaction rolled back.'];
        }

        if ($appliedCount === 0) {
            return [
                'ok' => false,
                'message' => 'No discounts were applied. The voucher may already be used for these invoices or balances are zero.',
            ];
        }

        if ($maxUses !== null) {
            $row = (array) DB::table('discount_vouchers')
                ->select('times_used')
                ->where('id', $voucherId)
                ->first();
            $usedNow = (int) ($row['times_used'] ?? 0);
            if ($usedNow >= $maxUses) {
                DB::table('discount_vouchers')
                    ->where('id', $voucherId)
                    ->update([
                        'is_active' => 0,
                        'updated_at' => function_exists('utc_now') ? utc_now() : now('UTC')->toDateTimeString(),
                    ]);
            }
        }

        foreach (array_unique($touchedInvoiceIds) as $iid) {
            try {
                $this->invoiceService->recalculateInvoice($iid, $schoolYear);
            } catch (\Throwable $e) {
                Log::error('recalculateInvoice failed for invoice {id}: {err}', [
                    'id' => $iid,
                    'err' => $e->getMessage(),
                ]);
            }
        }

        $updatedTotal = 0;
        foreach (array_unique($fullyCoveredInvoiceIds) as $iid) {
            try {
                $updatedTotal += $this->invoiceService->updateEnrollmentStatusIfPaid($iid, $schoolYear);
            } catch (\Throwable $e) {
                Log::error('updateEnrollmentStatusIfPaid failed for invoice {id}: {err}', [
                    'id' => $iid,
                    'err' => $e->getMessage(),
                ]);
            }
        }

        return [
            'ok' => true,
            'message' => $updatedTotal > 0
                ? 'Voucher applied successfully and enrollments updated.'
                : 'Voucher applied successfully.',
            'appliedCount' => $appliedCount,
            'updatedEnrollments' => $updatedTotal,
            'touchedInvoices' => array_values(array_unique($touchedInvoiceIds)),
        ];
    }

    private function triggerPaymentReceived(
        int $invoiceId,
        string $voucherId,
        float $amount,
        string $paymentDate,
        float $preBalance,
        float $postBalance,
        string $schoolYear,
        string $semester
    ): void {
        try {
            [$eventData, $studentData] = $this->invoiceService->buildPaymentEventData(
                $invoiceId,
                $voucherId,
                $amount,
                'discount',
                $paymentDate,
                '',
                0,
                $preBalance,
                $postBalance,
                $schoolYear,
                $semester
            );

            if (class_exists(\CodeIgniter\Events\Events::class)) {
                \CodeIgniter\Events\Events::trigger('paymentReceived', $eventData, $studentData);
            } elseif (function_exists('event')) {
                event('paymentReceived', [$eventData, $studentData]);
            }
        } catch (\Throwable $e) {
            Log::warning('paymentReceived hook failed: ' . $e->getMessage());
        }
    }
}
