<?php

namespace App\Livewire\Admin;

use App\Domain\Financial\Enums\InvoiceStatus;
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\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Enums\EnrollmentStatus;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Support\BillingCycle;
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;

/**
 * Back-fill invoices for participants who were enrolled without any.
 *
 * Not a cross-branch screen, and deliberately so: Participant, Enrollment and
 * Invoice all carry BranchScope, so the worklist, the per-participant config
 * and the invoices this writes all stay inside the branch the operator is in.
 * There is no opt-out anywhere in here, and there should not be — this writes
 * money, and a settlement raised against another branch's ledger is not a
 * report that can be re-run.
 *
 * The branch each invoice lands on is still passed explicitly (from the
 * enrolment's group, falling back to the participant), rather than left to the
 * creating-model stamp: services take an explicit ?int $branchId precisely so
 * they do not have to guess.
 */
#[Layout('layouts.app')]
#[Title('تسوية المشتركين')]
class ReconciliationWizard extends Component
{
    public int $currentStep = 1;

    // Step 1: list of participants with zero invoices.
    //
    // Locked: the ids are put here by selectAll()/toggleParticipant() and then
    // driven straight into invoice and payment creation. Every one of them is
    // re-resolved through the branch-scoped model below, but a money-writing
    // loop should not be reading a raw browser array in the first place.
    #[Locked]
    public array $selectedParticipantIds = [];

    // Step 2: per-participant config
    #[Locked]
    public ?int $activeParticipantId = null;
    public array $participantConfigs = [];
    // config structure: [participant_id => ['mode' => 'two_invoices'|'one_invoice', 'pay_now' => bool, 'payment_method' => 'cash']]

    // Step 3: results
    public array $results = [];
    public bool $completed = false;

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

    public function selectAll(array $ids): void
    {
        $this->selectedParticipantIds = $ids;
    }

    public function toggleParticipant(int $id): void
    {
        if (in_array($id, $this->selectedParticipantIds)) {
            $this->selectedParticipantIds = array_values(array_diff($this->selectedParticipantIds, [$id]));
        } else {
            $this->selectedParticipantIds[] = $id;
        }
    }

    public function proceedToConfig(): void
    {
        if (empty($this->selectedParticipantIds)) {
            session()->flash('error', __('يرجى اختيار مشترك واحد على الأقل'));
            return;
        }

        foreach ($this->selectedParticipantIds as $id) {
            if (!isset($this->participantConfigs[$id])) {
                $this->participantConfigs[$id] = [
                    'mode' => 'two_invoices',
                    'pay_now' => true,
                    'payment_method' => 'cash',
                ];
            }
        }

        $this->activeParticipantId = $this->selectedParticipantIds[0];
        $this->currentStep = 2;
    }

    public function setActiveParticipant(int $id): void
    {
        $this->activeParticipantId = $id;
    }

    public function applyToAll(string $mode, bool $payNow, string $method): void
    {
        foreach ($this->selectedParticipantIds as $id) {
            $this->participantConfigs[$id] = [
                'mode' => $mode,
                'pay_now' => $payNow,
                'payment_method' => $method,
            ];
        }
    }

    public function executeReconciliation(InvoiceService $invoiceService, PaymentService $paymentService, PricingService $pricingService): void
    {
        $this->authorize('super_admin.access');
        $this->results = [];
        $actor = auth()->user();

        foreach ($this->selectedParticipantIds as $participantId) {
            $config = $this->participantConfigs[$participantId] ?? ['mode' => 'two_invoices', 'pay_now' => true, 'payment_method' => 'cash'];

            try {
                $result = DB::transaction(function () use ($participantId, $config, $invoiceService, $paymentService, $pricingService, $actor) {
                    $participant = Participant::with('person')->find($participantId);
                    if (!$participant) {
                        throw new DomainException('المشترك غير موجود');
                    }

                    $enrollments = Enrollment::where('participant_id', $participantId)
                        ->where('status', EnrollmentStatus::Active)
                        ->with(['program', 'group'])
                        ->get();

                    $invoicesCreated = [];

                    foreach ($enrollments as $enrollment) {
                        $program = $enrollment->program;
                        if (!$program) {
                            continue;
                        }

                        $branchId = $enrollment->group?->branch_id ?? $participant->branch_id;

                        try {
                            $priceResult = $pricingService->calculate(
                                priceable: $program,
                                participant: $participant,
                                branchId: $branchId,
                            );
                        } catch (DomainException $e) {
                            continue;
                        }

                        if ($priceResult->finalAmount <= 0) {
                            continue;
                        }

                        if ($config['mode'] === 'two_invoices') {
                            // Invoice 1: last month (already paid offline)
                            $lastMonthInvoice = $invoiceService->create([
                                'academy_id' => $enrollment->academy_id,
                                'branch_id' => $branchId,
                                'billable_type' => $participant->getMorphClass(),
                                'billable_id' => $participant->id,
                                'total_amount' => $priceResult->finalAmount,
                                'subtotal_amount' => $priceResult->baseAmount,
                                'discount_amount' => $priceResult->totalDiscount,
                                'tax_amount' => 0,
                                'issue_date' => now()->subMonth()->startOfMonth()->toDateString(),
                                'due_date' => now()->subMonth()->endOfMonth()->toDateString(),
                                'contact_name' => $participant->person?->name_ar,
                                'notes' => 'تسوية — اشتراك الشهر السابق: ' . $program->name_ar,
                                // Say which month out loud. SubscriptionLine reads
                                // this before it reads dates or Arabic free text,
                                // so a settlement lands in the month it settles.
                                'metadata' => ['month' => BillingCycle::monthKey(now()->subMonth())],
                            ], [
                                [
                                    'description' => "اشتراك الشهر السابق: {$program->name_ar}",
                                    'quantity' => 1,
                                    'unit_price' => $priceResult->baseAmount,
                                    'discount_amount' => 0,
                                    'tax_amount' => 0,
                                ],
                            ], $actor);

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

                            // Record payment for last month (paid offline)
                            $paymentService->recordPayment([
                                'invoice_id' => $lastMonthInvoice->id,
                                'branch_id' => $branchId,
                                'amount' => $lastMonthInvoice->total_amount,
                                'method' => $config['payment_method'],
                                'direction' => 'inbound',
                                'currency' => 'EGP',
                                'payment_date' => now()->subMonth()->toDateString(),
                                'notes' => 'تسوية — دفع سابق خارج النظام',
                            ], $actor);

                            $invoicesCreated[] = ['invoice' => $lastMonthInvoice->fresh(), 'label' => 'الشهر السابق'];

                            // Invoice 2: current month renewal
                            $currentInvoice = $invoiceService->create([
                                'academy_id' => $enrollment->academy_id,
                                'branch_id' => $branchId,
                                'billable_type' => $participant->getMorphClass(),
                                'billable_id' => $participant->id,
                                'total_amount' => $priceResult->finalAmount,
                                'subtotal_amount' => $priceResult->baseAmount,
                                'discount_amount' => $priceResult->totalDiscount,
                                'tax_amount' => 0,
                                'issue_date' => BillingCycle::startOf()->toDateString(),
                                'due_date' => now()->addDays(7)->toDateString(),
                                'contact_name' => $participant->person?->name_ar,
                                'notes' => 'تجديد اشتراك — ' . $program->name_ar,
                                'metadata' => [
                                    'month' => BillingCycle::monthKey(),
                                    'renewal' => true,
                                    'renewal_enrollment_id' => (string) $enrollment->id,
                                ],
                            ], [
                                [
                                    'description' => "تجديد اشتراك: {$program->name_ar}",
                                    'quantity' => 1,
                                    'unit_price' => $priceResult->baseAmount,
                                    'discount_amount' => 0,
                                    'tax_amount' => 0,
                                ],
                            ], $actor);

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

                            if ($config['pay_now']) {
                                $paymentService->recordPayment([
                                    'invoice_id' => $currentInvoice->id,
                                    'branch_id' => $branchId,
                                    'amount' => $currentInvoice->total_amount,
                                    'method' => $config['payment_method'],
                                    'direction' => 'inbound',
                                    'currency' => 'EGP',
                                    'payment_date' => now()->toDateString(),
                                    'notes' => 'تسوية — دفع تجديد الشهر الحالي',
                                ], $actor);
                            }

                            $invoicesCreated[] = ['invoice' => $currentInvoice->fresh(), 'label' => 'الشهر الحالي'];

                        } else {
                            // One combined invoice for everything
                            $totalAmount = $priceResult->finalAmount * 2; // last month + current
                            $invoice = $invoiceService->create([
                                'academy_id' => $enrollment->academy_id,
                                'branch_id' => $branchId,
                                'billable_type' => $participant->getMorphClass(),
                                'billable_id' => $participant->id,
                                'total_amount' => $totalAmount,
                                'subtotal_amount' => $priceResult->baseAmount * 2,
                                'discount_amount' => $priceResult->totalDiscount * 2,
                                'tax_amount' => 0,
                                'due_date' => now()->addDays(7)->toDateString(),
                                'contact_name' => $participant->person?->name_ar,
                                'notes' => 'تسوية — اشتراك شهرين: ' . $program->name_ar,
                                // One invoice covering two cycles: name both ends
                                // so the money is split across the months it
                                // bought instead of landing entirely on this one.
                                'metadata' => [
                                    'period_start' => BillingCycle::startOf(now()->subMonth())->toDateString(),
                                    'period_end' => BillingCycle::startOf()->toDateString(),
                                ],
                            ], [
                                [
                                    'description' => "اشتراك الشهر السابق: {$program->name_ar}",
                                    'quantity' => 1,
                                    'unit_price' => $priceResult->baseAmount,
                                    'discount_amount' => 0,
                                    'tax_amount' => 0,
                                ],
                                [
                                    'description' => "تجديد الشهر الحالي: {$program->name_ar}",
                                    'quantity' => 1,
                                    'unit_price' => $priceResult->baseAmount,
                                    'discount_amount' => 0,
                                    'tax_amount' => 0,
                                ],
                            ], $actor);

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

                            if ($config['pay_now']) {
                                $paymentService->recordPayment([
                                    'invoice_id' => $invoice->id,
                                    'branch_id' => $branchId,
                                    'amount' => $invoice->total_amount,
                                    'method' => $config['payment_method'],
                                    'direction' => 'inbound',
                                    'currency' => 'EGP',
                                    'payment_date' => now()->toDateString(),
                                    'notes' => 'تسوية — دفع كامل لشهرين',
                                ], $actor);
                            }

                            $invoicesCreated[] = ['invoice' => $invoice->fresh(), 'label' => 'فاتورة موحدة'];
                        }

                        // Advance billing date so they're in the normal cycle going forward
                        $this->advanceBillingDate($enrollment);
                    }

                    return [
                        'participant_id' => $participantId,
                        'name' => $participant->person?->name_ar ?? '-',
                        'status' => 'success',
                        'invoices' => count($invoicesCreated),
                        'message' => 'تمت التسوية بنجاح',
                    ];
                });

                $this->results[] = $result;

            } catch (\Throwable $e) {
                Log::error('Reconciliation failed', ['participant_id' => $participantId, 'error' => $e->getMessage()]);
                $this->results[] = [
                    'participant_id' => $participantId,
                    'name' => Participant::with('person')->find($participantId)?->person?->name_ar ?? '-',
                    'status' => 'error',
                    'invoices' => 0,
                    'message' => $e->getMessage(),
                ];
            }
        }

        $this->completed = true;
        $this->currentStep = 3;
    }

    /**
     * Reconciliation settles last month AND this month, so the enrolment hands
     * over to the cycle after the current one.
     *
     * This used to advance from `now()` and snap to `billing_day`, which gave
     * the right answer only while the wizard was run inside the month it was
     * settling — and the wrong one, by a whole cycle, whenever it was not.
     * BillingCycle is the single definition of the rule.
     */
    private function advanceBillingDate(Enrollment $enrollment): void
    {
        $program = $enrollment->program;

        $next = BillingCycle::next(
            BillingCycle::startOf(),
            $program?->billing_cycle,
            $program?->program_duration_weeks
        );

        $enrollment->update([
            'next_billing_date' => $next->toDateString(),
            'last_billed_at' => BillingCycle::startOf()->toDateString(),
            'payment_status' => 'current',
        ]);
    }

    public function render()
    {
        $unpaidParticipants = collect();

        if ($this->currentStep === 1) {
            // Participants who are enrolled (active) but have ZERO invoices.
            //
            // Both sides of that read the branch now: Participant is scoped, and
            // whereDoesntHave('invoices') applies Invoice's own scope, so this
            // is "no invoices in this branch". That is the right reading — an
            // invoice follows its participant's branch (InvoiceService resolves
            // it from the billable when the caller does not say) — and it is
            // also the safe direction to be wrong in: worst case a settlement is
            // offered for review, never silently skipped.
            $unpaidParticipants = Participant::query()
                ->whereHas('enrollments', fn ($q) => $q->where('status', EnrollmentStatus::Active))
                ->whereDoesntHave('invoices')
                ->with(['person', 'enrollments' => fn ($q) => $q->where('status', 'active')->with('program')])
                ->orderBy('id')
                ->get();
        }

        $configParticipants = collect();
        if ($this->currentStep === 2) {
            $configParticipants = Participant::whereIn('id', $this->selectedParticipantIds)
                ->with(['person', 'enrollments' => fn ($q) => $q->where('status', 'active')->with('program')])
                ->get()
                ->keyBy('id');
        }

        return view('livewire.admin.reconciliation-wizard', [
            'unpaidParticipants' => $unpaidParticipants,
            'configParticipants' => $configParticipants,
        ]);
    }
}
