<?php

namespace App\Services\ExtraCharges;

use App\Models\AdditionalCharge;
use App\Models\Invoice;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ExtraChargesChargeService
{
    public function __construct(
        private ExtraChargesContextService $context,
        private ExtraChargesInvoiceService $invoiceService
    ) {}

    public function createCharge(array $payload): array
    {
        $invoiceId = $payload['invoice_id'] ?? null;
        $chargeType = $payload['charge_type'];
        $amountAbs = round(abs((float) $payload['amount']), 2);
        $signedAmount = $chargeType === 'add' ? $amountAbs : -$amountAbs;
        $schoolYear = $payload['school_year'] ?? $this->context->getSchoolYear();
        $semester = $payload['semester'] ?? $this->context->getSemester();

        $data = [
            'parent_id' => (int) $payload['parent_id'],
            'invoice_id' => $invoiceId,
            'school_year' => $schoolYear,
            'semester' => $semester,
            'charge_type' => $chargeType,
            'title' => trim($payload['title']),
            'description' => trim((string) ($payload['description'] ?? '')),
            'amount' => $signedAmount,
            'due_date' => $payload['due_date'] ?? null,
            'status' => $invoiceId ? 'applied' : 'pending',
            'created_by' => (int) ($payload['created_by'] ?? 0),
            'created_at' => $payload['created_at'] ?? $this->utcNow(),
        ];

        $invoiceBefore = $invoiceId ? Invoice::getInvoicesByParentId((int) $payload['parent_id'], $schoolYear) : [];
        $invoiceAfter = [];

        DB::beginTransaction();

        try {
            $chargeId = AdditionalCharge::query()->create($data)->id;

            if ($invoiceId) {
                $this->invoiceService->applyToInvoice((int) $invoiceId, $chargeType, $amountAbs);
            }

            $invoiceAfter = $invoiceId ? Invoice::getInvoicesByParentId((int) $payload['parent_id'], $schoolYear) : [];

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Extra charge create failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Failed to save charge.'];
        }

        $parentUser = User::getUserInfoById((int) $payload['parent_id']);
        $this->triggerExtraChargeEvent($parentUser, $data, (int) $chargeId, $invoiceId, $invoiceBefore, $invoiceAfter);

        return [
            'ok' => true,
            'id' => (int) $chargeId,
            'invoice_id' => $invoiceId ? (int) $invoiceId : null,
            'parent_id' => (int) $payload['parent_id'],
        ];
    }

    public function updateCharge(AdditionalCharge $charge, array $payload): bool
    {
        $chargeType = $payload['charge_type'] ?? $charge->charge_type ?? 'add';
        $amountRaw = isset($payload['amount']) ? (float) $payload['amount'] : (float) $charge->amount;
        $newAmount = $chargeType === 'add' ? abs($amountRaw) : -abs($amountRaw);
        $delta = $newAmount - (float) $charge->amount;

        DB::beginTransaction();

        try {
            $charge->title = trim($payload['title'] ?? $charge->title);
            $charge->description = trim($payload['description'] ?? $charge->description);
            $charge->amount = $newAmount;
            $charge->due_date = $payload['due_date'] ?? $charge->due_date;
            $charge->charge_type = $chargeType;
            if (array_key_exists('updated_by', $payload)) {
                $charge->updated_by = $payload['updated_by'];
            }
            $charge->save();

            if ($charge->status === 'applied' && !empty($charge->invoice_id) && abs($delta) > 0.00001) {
                $this->invoiceService->adjustDelta((int) $charge->invoice_id, $delta);
            }

            DB::commit();
            return true;
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Extra charge update failed: ' . $e->getMessage());
            return false;
        }
    }

    public function voidCharge(AdditionalCharge $charge): bool
    {
        DB::beginTransaction();

        try {
            $invoiceId = (int) ($charge->invoice_id ?? 0);
            $amountAbs = round(abs((float) ($charge->amount ?? 0)), 2);
            $chargeType = (string) ($charge->charge_type ?? 'add');
            $status = (string) ($charge->status ?? 'pending');

            if ($status === 'applied' && $invoiceId > 0 && $amountAbs > 0) {
                $this->invoiceService->reverseOnInvoice($invoiceId, $chargeType, $amountAbs);
            }

            $charge->status = 'void';
            $charge->save();

            DB::commit();
            return true;
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Extra charge void failed: ' . $e->getMessage());
            return false;
        }
    }

    public function reverseCharge(AdditionalCharge $charge): bool
    {
        $invoiceId = (int) ($charge->invoice_id ?? 0);
        $amountAbs = round(abs((float) ($charge->amount ?? 0)), 2);
        $chargeType = (string) ($charge->charge_type ?? 'add');
        $status = (string) ($charge->status ?? 'pending');

        if ($status !== 'applied' || $invoiceId <= 0 || $amountAbs <= 0) {
            return false;
        }

        DB::beginTransaction();

        try {
            $this->invoiceService->reverseOnInvoice($invoiceId, $chargeType, $amountAbs);

            $charge->status = 'pending';
            $charge->invoice_id = null;
            $charge->save();

            DB::commit();
            return true;
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Extra charge reverse failed: ' . $e->getMessage());
            return false;
        }
    }

    private function triggerExtraChargeEvent(?array $parentUser, array $payload, int $chargeId, ?int $invoiceId, array $invoiceBefore, array $invoiceAfter): void
    {
        if (!$parentUser) {
            return;
        }

        $first = $parentUser['first_name'] ?? $parentUser['firstname'] ?? '';
        $last = $parentUser['last_name'] ?? $parentUser['lastname'] ?? '';
        $parentName = trim($first . ' ' . $last);

        $before = $this->normalizeRow($invoiceBefore);
        $after = $this->normalizeRow($invoiceAfter);

        $invoiceTotal = $after ? (float) ($after['total_amount'] ?? 0) : 0.0;
        $preBalance = $before ? (float) ($before['balance'] ?? 0) : 0.0;
        $postBalance = $after ? (float) ($after['balance'] ?? 0) : 0.0;

        $eventData = [
            'user_id' => (int) ($parentUser['id'] ?? 0),
            'email' => $parentUser['email'] ?? null,
            'firstname' => $first,
            'lastname' => $last,
            'parentName' => $parentName,
            'school_year' => $payload['school_year'],
            'semester' => $payload['semester'],
            'invoice_id' => $invoiceId,
            'invoice_number' => $after['invoice_number'] ?? ($invoiceId ? 'INV-' . $invoiceId : null),
            'charge_id' => $chargeId,
            'charge_title' => $payload['title'],
            'charge_desc' => $payload['description'],
            'charge_type' => $payload['charge_type'],
            'amount_signed' => $payload['amount'],
            'amount_abs' => abs((float) $payload['amount']),
            'due_date' => $payload['due_date'],
            'created_at' => $payload['created_at'],
            'pre_balance' => $preBalance,
            'post_balance' => $postBalance,
            'invoice_total' => $invoiceTotal,
            'portal_link' => url('/login'),
            'invoice_link' => $invoiceId ? url('parent/invoices/view/' . $invoiceId) : url('parent/invoices'),
        ];

        $students = [];

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

    private function normalizeRow($row): ?array
    {
        if (!$row) {
            return null;
        }
        if (is_array($row) && isset($row[0]) && is_array($row[0])) {
            return $row[0];
        }
        return is_array($row) ? $row : null;
    }

    private function utcNow(): string
    {
        if (function_exists('utc_now')) {
            return (string) utc_now();
        }
        return now('UTC')->toDateTimeString();
    }
}
