<?php

namespace App\Livewire\Receptionist;

use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Models\Installment;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Concerns\ManagesDiscounts;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Enums\EnrollmentStatus;
use App\Domain\Training\Enums\RenewalPolicy;
use App\Domain\Training\Models\Enrollment;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('تحصيل مدفوعات')]
class CollectPaymentWizard extends Component
{
    use UsesBranchScope, ManagesDiscounts;

    public ?int $branchId = null;

    /** Enrollment whose renewal price is currently being composed. */
    public ?int $pricing_enrollment_id = null;

    public int $currentStep = 1;
    public int $totalSteps = 5;

    // Step 1: Search
    public string $search = '';
    public string $searchMode = 'participant'; // 'participant' or 'invoice'
    public ?int $selected_participant_id = null;
    public ?string $selected_participant_name = null;
    public bool $selected_participant_is_free = false;

    // Step 2: Invoice selection
    public ?int $selected_invoice_id = null;

    // Step 3: Payment details
    public string $payment_amount_display = '';
    public string $payment_method = 'cash';
    public string $payment_notes = '';

    // Admin price override
    public bool $priceOverrideEnabled = false;
    public string $priceOverrideInput = '';
    public string $priceOverrideReason = '';

    // Result
    public bool $completed = false;
    public ?string $receipt_number = null;
    public ?string $last_payment_uuid = null;
    public int $paid_amount = 0;
    public ?string $participant_uuid = null;
    public ?string $invoice_uuid = null;

    public function mount(?string $participant = null): void
    {
        $this->authorize('invoices.create');
        $this->branchId = $this->getActiveBranchIdOrFail();

        $participantUuid = $participant ?? request()->query('participant');

        if ($participantUuid) {
            $p = Participant::with('person')->where('uuid', $participantUuid)->first();
            if ($p) {
                $this->selected_participant_id = $p->id;
                $this->selected_participant_name = $p->person?->name_ar ?? $p->person?->name ?? '';
                $this->selected_participant_is_free = (bool) $p->is_free;
                $this->generatePendingRenewals();
                $this->currentStep = 2;
            }
        }
    }

    public function rules(): array
    {
        return match ($this->currentStep) {
            1 => $this->searchMode === 'participant'
                ? ['selected_participant_id' => 'required|exists:participants,id']
                : ['selected_invoice_id' => 'required|exists:invoices,id'],
            2 => [
                'selected_invoice_id' => 'required|exists:invoices,id',
            ],
            3 => [
                'payment_amount_display' => 'required|numeric|min:0.01',
                'payment_method' => 'required|in:cash,card,bank_transfer,wallet,online,cheque,other',
                'payment_notes' => 'nullable|string|max:500',
            ],
            default => [],
        };
    }

    public function messages(): array
    {
        return [
            'selected_participant_id.required' => 'يرجى اختيار المشترك',
            'selected_participant_id.exists' => 'المشترك المختار غير موجود',
            'selected_invoice_id.required' => 'يرجى اختيار الفاتورة',
            'selected_invoice_id.exists' => 'الفاتورة المختارة غير موجودة',
            'payment_amount_display.required' => 'المبلغ مطلوب',
            'payment_amount_display.numeric' => 'المبلغ يجب أن يكون رقمًا',
            'payment_amount_display.min' => 'المبلغ يجب أن يكون أكبر من صفر',
            'payment_method.required' => 'يرجى اختيار طريقة الدفع',
            'payment_method.in' => 'طريقة الدفع غير صالحة',
        ];
    }

    public function switchSearchMode(string $mode): void
    {
        $this->searchMode = $mode;
        $this->search = '';
        $this->selected_participant_id = null;
        $this->selected_participant_name = null;
        $this->selected_invoice_id = null;
    }

    public function selectInvoiceDirectly(int $id): void
    {
        $invoice = Invoice::find($id);
        if (!$invoice || $invoice->due_amount <= 0) {
            return;
        }

        if ($invoice->billable_type === Participant::class && $invoice->billable_id) {
            $participant = Participant::with('person')->find($invoice->billable_id);
            $this->selected_participant_id = $participant?->id;
            $this->selected_participant_name = $participant?->person?->name_ar ?? $invoice->contact_name ?? '';
            $this->selected_participant_is_free = (bool) ($participant?->is_free ?? false);
        } else {
            $this->selected_participant_id = null;
            $this->selected_participant_name = $invoice->contact_name ?? __('عميل عابر');
            $this->selected_participant_is_free = false;
        }

        $this->selected_invoice_id = $invoice->id;
        $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
        $this->currentStep = 3;
    }

    public function selectParticipant(int $id, string $name): void
    {
        $this->selected_participant_id = $id;
        $this->selected_participant_name = $name;
        $this->selected_participant_is_free = (bool) Participant::where('id', $id)->value('is_free');
        $this->generatePendingRenewals();
    }

    /**
     * Show what this participant may have before the invoice is written, so the
     * receptionist can change it rather than discovering it on the receipt.
     */
    public function prepareRenewalPricing(int $enrollmentId): void
    {
        $enrollment = Enrollment::with(['program', 'participant.person', 'group'])->find($enrollmentId);
        if (!$enrollment || $enrollment->participant_id !== $this->selected_participant_id) {
            return;
        }

        $this->pricing_enrollment_id = $enrollmentId;
        $this->selectedDiscountIds = [];
        $this->declinedDiscountIds = [];
        $this->clearManualDiscount();

        $this->loadDiscounts(
            priceable: $enrollment->program,
            participant: $enrollment->participant,
            branchId: $enrollment->group?->branch_id ?? $enrollment->participant->branch_id,
            extraContext: ['program_starts_on' => $enrollment->program->program_start_date ?? null],
        );
    }

    public function generateRenewalForEnrollment(int $enrollmentId): void
    {
        $enrollment = Enrollment::with(['program', 'participant.person', 'group'])->find($enrollmentId);
        if (!$enrollment || $enrollment->participant_id !== $this->selected_participant_id) {
            return;
        }

        // A manual discount must clear the actor's ceiling before it reaches an invoice.
        if ($this->manualDiscountAmount !== '' && !$this->validateManualDiscount()) {
            return;
        }

        try {
            $invoiceService = app(InvoiceService::class);
            $pricingService = app(PricingService::class);

            $participant = $enrollment->participant;
            $program = $enrollment->program;
            $group = $enrollment->group;

            $priceResult = $pricingService->calculate(
                priceable: $program,
                participant: $participant,
                branchId: $group?->branch_id ?? $participant->branch_id,
            );

            // The picker is authoritative once the operator has touched it: the
            // engine proposes, the receptionist disposes, the invoice records both.
            if ($this->pricing_enrollment_id === $enrollmentId && $this->discountCandidates) {
                $chosenDiscount = $this->selectedDiscountTotal();
                $finalAmount = max(0, $priceResult->baseAmount - $chosenDiscount);
                $discountLines = $this->discountSnapshot();
            } else {
                $chosenDiscount = $priceResult->totalDiscount;
                $finalAmount = $priceResult->finalAmount;
                $discountLines = $priceResult->appliedRules;
            }

            if ($finalAmount <= 0) {
                $this->advanceEnrollmentBillingDate($enrollment);
                session()->flash('info', __('الاشتراك مجاني - تم التجديد بدون فاتورة'));
                return;
            }

            $invoice = $invoiceService->create([
                'academy_id' => $enrollment->academy_id ?? $program->academy_id,
                'branch_id' => $group?->branch_id ?? $participant->branch_id,
                'billable_type' => $participant->getMorphClass(),
                'billable_id' => $participant->id,
                'total_amount' => $finalAmount,
                'subtotal_amount' => $priceResult->baseAmount,
                'discount_amount' => $chosenDiscount,
                'tax_amount' => 0,
                'due_date' => now()->addDays(7)->toDateString(),
                'contact_name' => $participant->person?->name_ar ?? $participant->person?->name,
                'notes' => 'تجديد اشتراك — ' . $program->name_ar,
            ], [
                [
                    'description' => "تجديد اشتراك: {$program->name_ar}",
                    'quantity' => 1,
                    'unit_price' => $priceResult->baseAmount,
                    'discount_amount' => $chosenDiscount,
                    'tax_amount' => 0,
                ],
            ], auth()->user());

            // Freeze the discount names on the invoice so the receipt stays
            // readable after the rules change.
            $invoice->update([
                'status' => InvoiceStatus::Sent,
                'metadata' => array_merge($invoice->metadata ?? [], [
                    'applied_discounts' => $discountLines,
                ]),
            ]);

            $this->advanceEnrollmentBillingDate($enrollment);

            $enrollment->update([
                'last_billed_at' => now()->toDateString(),
                'payment_status' => 'pending',
            ]);

            $this->selected_invoice_id = $invoice->id;
            $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');

        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        } catch (\Throwable $e) {
            Log::error('Renewal invoice generation failed', ['enrollment_id' => $enrollmentId, 'error' => $e->getMessage()]);
            session()->flash('error', __('خطأ في إنشاء فاتورة التجديد'));
        }
    }

    private function generatePendingRenewals(): void
    {
        if (!$this->selected_participant_id) {
            return;
        }

        $enrollments = Enrollment::where('participant_id', $this->selected_participant_id)
            ->where('status', EnrollmentStatus::Active)
            ->whereNotNull('next_billing_date')
            ->where('next_billing_date', '<=', now()->toDateString())
            ->whereHas('program', function ($q) {
                $q->whereIn('renewal_policy', [
                    RenewalPolicy::AutoRenew->value,
                    RenewalPolicy::ManualRenew->value,
                ])->whereNotNull('billing_cycle');
            })
            ->with(['program', 'participant.person', 'group'])
            ->get();

        foreach ($enrollments as $enrollment) {
            $existingUnpaid = Invoice::where('billable_type', Participant::class)
                ->where('billable_id', $this->selected_participant_id)
                ->whereIn('status', [InvoiceStatus::Sent, InvoiceStatus::PartiallyPaid, InvoiceStatus::Overdue, InvoiceStatus::Draft])
                ->where('notes', 'like', '%' . ($enrollment->program?->name_ar ?? 'تجديد') . '%')
                ->exists();

            if (!$existingUnpaid) {
                $this->generateRenewalForEnrollment($enrollment->id);
            }
        }
    }

    private function advanceEnrollmentBillingDate(Enrollment $enrollment): void
    {
        // All renewals are on the 1st of next month — no exceptions
        $next = now()->addMonth()->startOfMonth();

        $enrollment->update(['next_billing_date' => $next->toDateString()]);
    }

    public function updatedPriceOverrideEnabled(): void
    {
        if (!$this->priceOverrideEnabled) {
            $this->priceOverrideInput = '';
            $this->priceOverrideReason = '';
        }
    }

    public function applyAdminOverride(): void
    {
        $actor = auth()->user();
        if (!$actor->is_super_admin) {
            return;
        }

        $invoice = Invoice::find($this->selected_invoice_id);
        if (!$invoice || $this->priceOverrideInput === '') {
            return;
        }

        $newTotalPiasters = (int) round((float) $this->priceOverrideInput * 100);
        if ($newTotalPiasters < 0) {
            $this->addError('priceOverrideInput', __('المبلغ لا يمكن أن يكون سالبًا'));
            return;
        }

        $originalTotal = $invoice->total_amount;
        $originalDue = $invoice->due_amount;
        $originalDiscount = $invoice->discount_amount;

        // The override sets a new total — the difference becomes admin discount
        $adminDiscount = $originalTotal + $originalDiscount - $newTotalPiasters;
        $newDueAmount = max(0, $newTotalPiasters - $invoice->paid_amount);

        // Store audit trail in metadata
        $metadata = $invoice->metadata ?? [];
        $metadata['admin_override'] = [
            'applied_at' => now()->toIso8601String(),
            'applied_by_id' => $actor->id,
            'applied_by_name' => $actor->name,
            'original_total_piasters' => $originalTotal,
            'original_due_piasters' => $originalDue,
            'new_total_piasters' => $newTotalPiasters,
            'discount_piasters' => $adminDiscount,
            'reason' => $this->priceOverrideReason ?: null,
            'participant_id' => $this->selected_participant_id,
            'participant_name' => $this->selected_participant_name,
        ];

        $invoice->update([
            'discount_amount' => $adminDiscount,
            'total_amount' => $newTotalPiasters,
            'due_amount' => $newDueAmount,
            'metadata' => $metadata,
        ]);

        // Audit log
        Log::channel('audit')->info('admin_invoice_override', [
            'actor_id' => $actor->id,
            'actor_name' => $actor->name,
            'invoice_id' => $invoice->id,
            'invoice_number' => $invoice->number,
            'participant_name' => $this->selected_participant_name,
            'original_total' => $originalTotal,
            'new_total' => $newTotalPiasters,
            'reason' => $this->priceOverrideReason,
        ]);

        // Refresh payment amount display
        $this->payment_amount_display = number_format($newDueAmount / 100, 2, '.', '');

        // Reset override UI
        $this->priceOverrideEnabled = false;
        $this->priceOverrideInput = '';
        $this->priceOverrideReason = '';

        session()->flash('success', __('تم تعديل مبلغ الفاتورة بنجاح'));
    }

    public function updatedSelectedInvoiceId(): void
    {
        if ($this->selected_invoice_id) {
            $invoice = Invoice::find($this->selected_invoice_id);
            if ($invoice) {
                $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
            }
        }
        // Reset override when changing invoice
        $this->priceOverrideEnabled = false;
        $this->priceOverrideInput = '';
        $this->priceOverrideReason = '';
    }

    public function selectInvoice(int $id): void
    {
        $this->selected_invoice_id = $id;

        // Pre-fill the full balance due
        $invoice = Invoice::find($id);
        if ($invoice) {
            $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
        }
    }

    public function payFullAmount(): void
    {
        $invoice = Invoice::find($this->selected_invoice_id);
        if ($invoice) {
            $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
        }
    }

    public function nextStep(): void
    {
        $this->validate();

        // Additional validation for step 3: amount cannot exceed balance
        if ($this->currentStep === 3) {
            $invoice = Invoice::find($this->selected_invoice_id);
            $amountPiasters = (int) round((float) $this->payment_amount_display * 100);

            if ($invoice && $amountPiasters > $invoice->due_amount) {
                $this->addError('payment_amount_display', 'المبلغ أكبر من الرصيد المستحق');
                return;
            }
        }

        $this->currentStep = min($this->currentStep + 1, $this->totalSteps);
    }

    public function previousStep(): void
    {
        $this->currentStep = max($this->currentStep - 1, 1);
    }

    public function goToStep(int $step): void
    {
        if ($step < $this->currentStep) {
            $this->currentStep = $step;
        }
    }

    public function confirm(PaymentService $service): void
    {
        try {
            $amountPiasters = (int) round((float) $this->payment_amount_display * 100);

            // PaymentService::recordPayment() handles:
            // - Creating payment record
            // - Double-entry transactions (debit cash/card, credit AR)
            // - Updating invoice.paid_amount and due_amount
            // - Transitioning invoice status if fully paid
            // - Updating cash session if cash payment

            $payment = $service->recordPayment([
                'invoice_id' => $this->selected_invoice_id,
                'branch_id' => $this->branchId,
                'amount' => $amountPiasters,
                'method' => $this->payment_method,
                'direction' => 'inbound',
                'currency' => 'EGP',
                'payment_date' => now()->toDateString(),
                'notes' => $this->payment_notes ?: null,
            ], auth()->user());

            $this->completed = true;
            $this->receipt_number = $payment->reference ?? null;
            $this->last_payment_uuid = $payment->uuid;
            $this->paid_amount = $amountPiasters;
            $this->currentStep = 5;

            // Store UUIDs for navigation links
            $participant = \App\Domain\Participant\Models\Participant::find($this->selected_participant_id);
            $this->participant_uuid = $participant?->uuid;
            $invoice = \App\Domain\Financial\Models\Invoice::find($this->selected_invoice_id);
            $this->invoice_uuid = $invoice?->uuid;

            session()->flash('success', __('تم تسجيل الدفع بنجاح'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    public function render()
    {
        $searchResults = collect();
        $invoiceSearchResults = collect();

        if (strlen($this->search) >= 2) {
            if ($this->searchMode === 'participant') {
                $searchResults = Participant::query()
                    ->with('person')
                    ->whereIn('status', ['active', 'registered', 'frozen', 'inactive'])
                    ->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}%")
                                 ->orWhere('national_id', 'like', "%{$search}%");
                          });
                    })
                    ->limit(10)
                    ->get();
            } else {
                $invoiceSearchResults = Invoice::query()
                    ->whereIn('status', [
                        InvoiceStatus::Sent,
                        InvoiceStatus::PartiallyPaid,
                        InvoiceStatus::Overdue,
                        InvoiceStatus::Draft,
                    ])
                    ->where('due_amount', '>', 0)
                    ->where(function ($q) {
                        $search = $this->search;
                        $q->where('number', 'ilike', "%{$search}%")
                          ->orWhere('contact_name', 'ilike', "%{$search}%");
                    })
                    ->orderBy('due_date')
                    ->limit(10)
                    ->get();
            }
        }

        // Outstanding invoices for selected participant (all branches — POS can sell cross-branch)
        $invoices = collect();
        $upcomingPayments = collect();
        if ($this->selected_participant_id) {
            $invoices = Invoice::where('billable_type', Participant::class)
                ->where('billable_id', $this->selected_participant_id)
                ->whereIn('status', [
                    InvoiceStatus::Sent,
                    InvoiceStatus::PartiallyPaid,
                    InvoiceStatus::Overdue,
                    InvoiceStatus::Draft,
                ])
                ->where('due_amount', '>', 0)
                ->orderBy('due_date')
                ->get();

            // Upcoming installments (from payment plans linked to this participant's invoices)
            $participantInvoiceIds = Invoice::where('billable_type', Participant::class)
                ->where('billable_id', $this->selected_participant_id)
                ->pluck('id');

            $nextInstallments = Installment::whereHas('paymentPlan', fn ($q) => $q->whereIn('invoice_id', $participantInvoiceIds))
                ->where('status', 'pending')
                ->orderBy('due_date')
                ->limit(3)
                ->get();

            // Upcoming enrollment renewals (next_billing_date)
            $upcomingRenewals = Enrollment::where('participant_id', $this->selected_participant_id)
                ->where('status', 'active')
                ->whereNotNull('next_billing_date')
                ->with('program')
                ->orderBy('next_billing_date')
                ->get();

            // Merge into a unified upcoming list
            foreach ($nextInstallments as $inst) {
                $upcomingPayments->push([
                    'type' => 'installment',
                    'label' => 'قسط #' . $inst->sequence,
                    'amount' => $inst->amount,
                    'due_date' => $inst->due_date, // Carbon from model cast
                ]);
            }
            foreach ($upcomingRenewals as $enr) {
                $upcomingPayments->push([
                    'type' => 'renewal',
                    'label' => 'تجديد: ' . ($enr->program?->name_ar ?? '-'),
                    'amount' => null,
                    'due_date' => $enr->next_billing_date,
                    'enrollment_id' => $enr->id,
                ]);
            }
            $upcomingPayments = $upcomingPayments->sortBy(fn ($item) => $item['due_date']->timestamp)->values();
        }

        $selectedInvoice = $this->selected_invoice_id
            ? Invoice::find($this->selected_invoice_id)
            : null;

        return view('livewire.receptionist.collect-payment-wizard', [
            'searchResults' => $searchResults,
            'invoiceSearchResults' => $invoiceSearchResults,
            'invoices' => $invoices,
            'upcomingPayments' => $upcomingPayments,
            'selectedInvoice' => $selectedInvoice,
            'isSuperAdmin' => auth()->user()?->is_super_admin ?? false,
            // The picker must say which branch it is showing — a receptionist who
            // cannot see why a discount is missing will phone the office.
            'branchName' => \App\Domain\Identity\Models\Branch::where('id', $this->branchId)->value('name_ar'),
        ]);
    }
}
