<?php

namespace App\Livewire\Admin;

use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\Payment;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

/**
 * Correct a single issued invoice, with a reason on the record.
 *
 * Branch handling is entirely the models' now: Participant and Invoice both
 * carry BranchScope, so a super-admin working inside a branch can only search,
 * list and correct that branch's rows, and the one way to reach another
 * branch's invoice from here is the deliberate, permission-gated all-branches
 * mode — which is the mode this tool exists to be used from. No hand-written
 * branch clause is left in the component; a second copy of the rule is a second
 * thing to keep in step.
 */
#[Layout('layouts.app')]
#[Title('تصحيح فاتورة')]
class InvoiceCorrectionWizard extends Component
{
    public int $currentStep = 1;

    // Step 1: Find participant
    public string $search = '';

    // Locked, all three: these are resolved server-side from a branch-scoped
    // query and then trusted by applyCorrection(), which edits money. A plain
    // public property would let the browser skip selectParticipant() entirely
    // and name any id it liked.
    #[Locked]
    public ?int $selectedParticipantId = null;
    #[Locked]
    public string $selectedParticipantName = '';

    // Step 2: Select invoice
    #[Locked]
    public ?int $selectedInvoiceId = null;
    public array $invoiceDetails = [];

    // Step 3: Correction details
    public string $correctionType = 'adjust_amount'; // adjust_amount | split_installment | change_description
    public string $newAmountInput = '';
    public string $newDescription = '';
    public string $correctionReason = '';
    public bool $createRemainderInvoice = true;
    public string $remainderDueDate = '';
    public string $remainderDescription = '';

    // Step 4: Results
    public bool $completed = false;
    public array $correctionResult = [];

    public function mount(): void
    {
        $this->authorize('super_admin.access');
        $this->remainderDueDate = now()->addMonth()->startOfMonth()->toDateString();
    }

    public function selectParticipant(int $id): void
    {
        // The id arrives from the browser, so it is resolved through the
        // branch-scoped model rather than trusted, and the display name is read
        // off the record instead of being passed alongside it — the name ends
        // up in the correction audit log, and a caller-supplied one would be
        // whatever the browser felt like sending.
        $participant = Participant::with('person')->find($id);

        if (! $participant) {
            session()->flash('error', __('المشترك غير موجود'));

            return;
        }

        $this->selectedParticipantId = $participant->id;
        $this->selectedParticipantName = $participant->person?->name_ar ?? '';
        $this->search = '';
        $this->currentStep = 2;
    }

    /**
     * Every invoice this wizard may touch.
     *
     * Invoice carries BranchScope, so another branch's row cannot come back
     * here at all. The billable clause is the second half of the same rule:
     * this wizard corrects the *selected participant's* invoices, and the
     * correction audit log names that participant.
     */
    private function correctableInvoices(): Builder
    {
        return Invoice::query()
            ->where('billable_type', Participant::class)
            ->where('billable_id', $this->selectedParticipantId);
    }

    public function selectInvoice(int $invoiceId): void
    {
        $invoice = $this->correctableInvoices()->with('items', 'payments')->find($invoiceId);
        if (!$invoice) {
            return;
        }

        $this->selectedInvoiceId = $invoiceId;
        $this->invoiceDetails = [
            'id' => $invoice->id,
            'number' => $invoice->number,
            'status' => $invoice->status->value,
            'total_amount' => $invoice->total_amount,
            'paid_amount' => $invoice->paid_amount,
            'due_amount' => $invoice->due_amount,
            'notes' => $invoice->notes,
            'issue_date' => $invoice->issue_date?->format('Y-m-d'),
            'items' => $invoice->items->map(fn ($item) => [
                'id' => $item->id,
                'description' => $item->description,
                'unit_price' => $item->unit_price,
                'quantity' => $item->quantity,
                'total_amount' => $item->total_amount,
            ])->toArray(),
            'payments' => $invoice->payments->map(fn ($p) => [
                'id' => $p->id,
                'amount' => $p->amount,
                'method' => $p->method,
                'payment_date' => $p->payment_date,
                'status' => $p->status,
            ])->toArray(),
        ];

        $this->newAmountInput = number_format($invoice->total_amount / 100, 2, '.', '');
        $this->newDescription = $invoice->items->first()?->description ?? '';
        $this->currentStep = 3;
    }

    public function previousStep(): void
    {
        if ($this->currentStep === 3) {
            $this->selectedInvoiceId = null;
            $this->invoiceDetails = [];
            $this->currentStep = 2;
        } elseif ($this->currentStep === 2) {
            $this->selectedParticipantId = null;
            $this->selectedParticipantName = '';
            $this->currentStep = 1;
        }
    }

    public function getNewAmountPiastersProperty(): int
    {
        return max(0, (int) round((float) $this->newAmountInput * 100));
    }

    public function getOriginalAmountProperty(): int
    {
        return $this->invoiceDetails['total_amount'] ?? 0;
    }

    public function getDifferenceProperty(): int
    {
        return $this->originalAmount - $this->newAmountPiasters;
    }

    public function applyCorrection(): void
    {
        $this->authorize('super_admin.access');

        $this->validate([
            'correctionReason' => 'required|min:10',
            'newAmountInput' => 'required|numeric|min:0',
        ], [
            'correctionReason.required' => 'سبب التصحيح مطلوب',
            'correctionReason.min' => 'سبب التصحيح يجب أن يكون 10 أحرف على الأقل',
            'newAmountInput.required' => 'المبلغ الجديد مطلوب',
            'newAmountInput.numeric' => 'المبلغ يجب أن يكون رقم',
            'newAmountInput.min' => 'المبلغ لا يمكن أن يكون سالب',
        ]);

        $actor = auth()->user();
        $invoice = $this->correctableInvoices()->with('items', 'payments')->find($this->selectedInvoiceId);

        if (!$invoice) {
            session()->flash('error', 'الفاتورة غير موجودة');
            return;
        }

        $newAmount = $this->newAmountPiasters;
        $originalAmount = $invoice->total_amount;
        $difference = $originalAmount - $newAmount;

        if ($newAmount === $originalAmount && $this->correctionType !== 'change_description') {
            session()->flash('error', 'المبلغ الجديد مطابق للمبلغ الأصلي');
            return;
        }

        // Money that came in does not go away because a number on an invoice
        // changed. This used to clamp paid_amount down to the new total and
        // rewrite the payment rows themselves — so cutting an 8,000 invoice
        // that had 5,000 collected against it down to 2,500 destroyed 2,500 of
        // real money: the payment row said one thing, its double-entry
        // transaction still said another, and nobody was ever told. The
        // settlement wizard exists for this: it can move the payment to the
        // right invoice or credit it to the player's wallet, both of which
        // leave the money somewhere.
        $collected = (int) $invoice->paid_amount;

        if ($this->correctionType !== 'change_description' && $collected > $newAmount) {
            session()->flash('error', sprintf(
                'محصَّل على هذه الفاتورة %s وهو أكبر من المبلغ الجديد %s. المال المحصَّل لا يُحذف — استخدم تسوية الحساب لنقل الدفعة إلى فاتورتها الصحيحة أو إضافتها لمحفظة المشترك، ثم صحّح الفاتورة.',
                format_money($collected),
                format_money($newAmount)
            ));

            return;
        }

        try {
            $result = DB::transaction(function () use ($invoice, $actor, $newAmount, $originalAmount, $difference) {
                $oldData = [
                    'total_amount' => $invoice->total_amount,
                    'subtotal_amount' => $invoice->subtotal_amount,
                    'paid_amount' => $invoice->paid_amount,
                    'due_amount' => $invoice->due_amount,
                    'status' => $invoice->status->value,
                    'notes' => $invoice->notes,
                    'items' => $invoice->items->toArray(),
                ];

                $invoiceService = app(InvoiceService::class);
                $remainderInvoice = null;

                // Adjust the invoice amount
                $invoice->subtotal_amount = $newAmount;
                $invoice->total_amount = $newAmount;

                // paid_amount is left exactly as it is: the guard above refused
                // the correction if it exceeded the new total, so there is
                // nothing to clamp and nothing to lose.
                $invoice->due_amount = max(0, $newAmount - $invoice->paid_amount);

                // Update status based on new amounts
                if ($invoice->due_amount === 0 && $newAmount > 0) {
                    $invoice->status = InvoiceStatus::Paid;
                    $invoice->paid_at = $invoice->paid_at ?? now();
                } elseif ($invoice->paid_amount > 0 && $invoice->due_amount > 0) {
                    $invoice->status = InvoiceStatus::PartiallyPaid;
                } elseif ($newAmount === 0) {
                    $invoice->status = InvoiceStatus::Cancelled;
                    $invoice->cancelled_at = now();
                }

                // Update notes
                $correctionNote = 'تصحيح: ' . $this->correctionReason;
                $invoice->notes = $correctionNote;

                // Update metadata with audit trail
                $invoice->metadata = array_merge($invoice->metadata ?? [], [
                    'correction' => [
                        'corrected_at' => now()->toIso8601String(),
                        'corrected_by' => $actor->id,
                        'corrected_by_name' => $actor->name,
                        'original_amount' => $originalAmount,
                        'new_amount' => $newAmount,
                        'difference' => $difference,
                        'reason' => $this->correctionReason,
                        'type' => $this->correctionType,
                    ],
                ]);

                $invoice->save();

                // Update invoice items
                $mainItem = $invoice->items->first();
                if ($mainItem) {
                    $mainItem->update([
                        'unit_price' => $newAmount,
                        'total_amount' => $newAmount,
                        'description' => $this->newDescription ?: $mainItem->description,
                    ]);
                }

                // Payments are not edited here. A payment row is the record of
                // money that physically arrived, and its double-entry
                // transaction is immutable by the same rule — a correction that
                // rewrote one and not the other left the two disagreeing.

                // Create remainder invoice if needed
                if ($this->createRemainderInvoice && $difference > 0) {
                    $participant = Participant::with('person')->find($this->selectedParticipantId);

                    $remainderInvoice = $invoiceService->create([
                        'academy_id' => $invoice->academy_id,
                        // The remainder belongs to the branch that issued the
                        // invoice it corrects — not to whatever branch the
                        // super-admin happens to be viewing from.
                        'branch_id' => $invoice->branch_id,
                        'billable_type' => $invoice->billable_type,
                        'billable_id' => $invoice->billable_id,
                        'total_amount' => $difference,
                        'subtotal_amount' => $difference,
                        'discount_amount' => 0,
                        'tax_amount' => 0,
                        'service_fee_amount' => 0,
                        'due_date' => $this->remainderDueDate,
                        'contact_name' => $participant?->person?->name_ar ?? $invoice->contact_name,
                        'notes' => $this->remainderDescription ?: ('أقساط متبقية — تصحيح من ' . $invoice->number),
                        'metadata' => [
                            'correction_remainder' => true,
                            'original_invoice_id' => $invoice->id,
                            'original_invoice_number' => $invoice->number,
                            'corrected_at' => now()->toIso8601String(),
                        ],
                    ], [
                        [
                            'description' => $this->remainderDescription ?: ('المبلغ المتبقي من ' . $invoice->number),
                            'quantity' => 1,
                            'unit_price' => $difference,
                            'discount_amount' => 0,
                            'tax_amount' => 0,
                        ],
                    ], $actor);

                    $remainderInvoice->update(['status' => InvoiceStatus::Sent]);
                }

                // Audit log
                Log::channel('audit')->info('invoice_correction', [
                    'actor_id' => $actor->id,
                    'actor_name' => $actor->name,
                    'invoice_id' => $invoice->id,
                    'invoice_number' => $invoice->number,
                    'participant_id' => $this->selectedParticipantId,
                    'participant_name' => $this->selectedParticipantName,
                    'correction_type' => $this->correctionType,
                    'original_amount' => $originalAmount,
                    'new_amount' => $newAmount,
                    'difference' => $difference,
                    'reason' => $this->correctionReason,
                    'remainder_invoice_id' => $remainderInvoice?->id,
                    'remainder_invoice_number' => $remainderInvoice?->number,
                    'old_data' => $oldData,
                    'timestamp' => now()->toIso8601String(),
                ]);

                return [
                    'invoice_number' => $invoice->number,
                    'original_amount' => $originalAmount,
                    'new_amount' => $newAmount,
                    'new_status' => $invoice->status->value,
                    'remainder_invoice' => $remainderInvoice ? [
                        'number' => $remainderInvoice->number,
                        'amount' => $difference,
                    ] : null,
                ];
            });

            $this->correctionResult = $result;
            $this->completed = true;
            $this->currentStep = 4;

        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        } catch (\Throwable $e) {
            Log::error('Invoice correction failed', [
                'invoice_id' => $this->selectedInvoiceId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            session()->flash('error', 'حدث خطأ: ' . $e->getMessage());
        }
    }

    public function startOver(): void
    {
        $this->reset();
        $this->remainderDueDate = now()->addMonth()->startOfMonth()->toDateString();
        $this->currentStep = 1;
    }

    public function render()
    {
        $searchResults = collect();
        if (strlen($this->search) >= 2 && !$this->selectedParticipantId) {
            // No branch clause: Participant carries BranchScope now, and a
            // second hand-written filter beside it is one more thing to keep in
            // step with the model.
            $searchResults = Participant::query()
                ->with('person')
                ->where(function ($q) {
                    $search = $this->search;
                    $q->where('participant_number', 'ilike', "%{$search}%")
                      ->orWhereHas('person', function ($pq) use ($search) {
                          $pq->where('name_ar', 'ilike', "%{$search}%")
                             ->orWhere('name', 'ilike', "%{$search}%")
                             ->orWhere('phone', 'like', "%{$search}%");
                      });
                })
                ->limit(10)
                ->get();
        }

        $invoices = collect();
        if ($this->selectedParticipantId && $this->currentStep === 2) {
            $invoices = $this->correctableInvoices()
                ->with('items')
                ->orderByDesc('created_at')
                ->get();
        }

        // Everything else already on this account, shown beside the split so a
        // second "first instalment" is visible before it is created rather
        // than after. Splitting an invoice in isolation is how one boy ended up
        // billed 2,500 twice for the same instalment and then 5,500 for "the
        // rest" of a card that only ever cost 8,000.
        $otherInvoices = collect();
        if ($this->selectedInvoiceId && $this->currentStep === 3 && $this->createRemainderInvoice) {
            $otherInvoices = $this->correctableInvoices()
                ->with('items')
                ->whereKeyNot($this->selectedInvoiceId)
                ->whereNotIn('status', ['cancelled', 'draft'])
                ->orderByDesc('issue_date')
                ->limit(15)
                ->get();
        }

        return view('livewire.admin.invoice-correction-wizard', [
            'searchResults' => $searchResults,
            'invoices' => $invoices,
            'otherInvoices' => $otherInvoices,
        ]);
    }
}
