<?php

namespace App\Filament\Resources\InvoiceResource\Pages;

use App\Filament\Resources\InvoiceResource;
use App\Filament\Support\Perm;
use App\Filament\Support\StatusLookup;
use App\Http\Controllers\Concerns\AppliesCheckoutCredit;
use App\Models\Lookups\PaymentMethod;
use App\Models\Lookups\Status;
use App\Models\Returns\CreditNote;
use App\Models\Sales\Invoice;
use App\Models\Sales\InvoiceItem;
use App\Services\AuditLog;
use App\Services\CreditNoteService;
use App\Services\CurrentContext;
use App\Services\CustomerCreditService;
use App\Services\InvoiceService;
use App\Services\PaymentService;
use App\Services\PosSettings;
use App\Services\VatSettings;
use App\Support\Quantity;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Components\ViewEntry;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ViewRecord;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
use RuntimeException;

/**
 * Ported from InvoiceController::show() plus the actions the source's
 * show.tsx drives: addPayment(), applyCredit(), updateStatus(), destroy().
 *
 * Per docs/migration-plan.md §7 this is an Infolist-based Resource page (the
 * 1576-line show.tsx is drill-down/read, not live editing), matching the
 * ViewPurchaseOrder/ViewGrn pattern already established in Phases 3–4.
 */
class ViewInvoice extends ViewRecord
{
    use AppliesCheckoutCredit;

    protected static string $resource = InvoiceResource::class;

    public function infolist(Schema $schema): Schema
    {
        return $schema->components([
            Section::make()->schema([
                TextEntry::make('invoice_number')->label('Invoice #'),
                TextEntry::make('customer.business_name')->label('Customer')->placeholder('Walk-in'),
                TextEntry::make('branch.name')->label('Branch')->placeholder('—'),
                TextEntry::make('channel')->badge(),
                TextEntry::make('orderStatus.name')
                    ->label('Order status')
                    ->badge()
                    ->color(fn (Invoice $record): string => StatusLookup::colorOf(Status::CTX_INVOICE_ORDER, $record->orderStatus?->code ?? '')),
                TextEntry::make('paymentStatus.name')
                    ->label('Payment status')
                    ->badge()
                    ->color(fn (Invoice $record): string => StatusLookup::colorOf(Status::CTX_INVOICE_PAYMENT, $record->paymentStatus?->code ?? '')),
                TextEntry::make('rep.name')->label('Sales rep')->placeholder('—'),
                TextEntry::make('creator.name')->label('Created by')->placeholder('—'),
                TextEntry::make('created_at')->dateTime(),
                // Present when this invoice replaced an earlier, voided one via
                // POS "amend sale" — surfaced so the correction is never silently
                // invisible (source show.tsx does the same).
                TextEntry::make('amendedFrom.invoice_number')
                    ->label('Amended from')
                    ->placeholder('—')
                    ->visible(fn (Invoice $record): bool => $record->amended_from_invoice_id !== null),
                TextEntry::make('note')->columnSpanFull()->placeholder('—'),

                // The ink seal, shown in the UI exactly as it punches onto the
                // printed invoice — the same shared partial both print layouts
                // use, so the screen and the paper never disagree.
                ViewEntry::make('paid_seal')
                    ->view('filament.infolists.paid-seal')
                    ->columnSpanFull()
                    ->visible(fn (Invoice $record): bool => $record->isFullyPaid()),
            ])->columns(3),

            Section::make('Items')->schema([
                RepeatableEntry::make('items')
                    ->schema([
                        TextEntry::make('product_name')->label('Product'),
                        TextEntry::make('quantity')
                            ->formatStateUsing(fn ($state, InvoiceItem $record): string => Quantity::format($state, $record->unit)),
                        TextEntry::make('unit_price')->money(fn () => app(CurrentContext::class)->currencyCode()),
                        TextEntry::make('discount_amount')->money(fn () => app(CurrentContext::class)->currencyCode()),
                        TextEntry::make('vat_amount')->money(fn () => app(CurrentContext::class)->currencyCode())
                            ->visible(fn (): bool => app(VatSettings::class)->enabled()),
                        TextEntry::make('line_total')->money(fn () => app(CurrentContext::class)->currencyCode()),
                    ])
                    ->columns(6)
                    ->columnSpanFull(),
            ]),

            Section::make('Totals')->schema([
                TextEntry::make('subtotal')->money(fn () => app(CurrentContext::class)->currencyCode()),
                TextEntry::make('discount')->money(fn () => app(CurrentContext::class)->currencyCode()),
                TextEntry::make('vat_total')->label('VAT')->money(fn () => app(CurrentContext::class)->currencyCode())
                    ->visible(fn (): bool => app(VatSettings::class)->enabled()),
                TextEntry::make('net_total')->money(fn () => app(CurrentContext::class)->currencyCode())->weight('bold'),
                TextEntry::make('paid_amount')->money(fn () => app(CurrentContext::class)->currencyCode()),
                TextEntry::make('tendered_amount')->label('Tendered')->money(fn () => app(CurrentContext::class)->currencyCode())
                    ->visible(fn (Invoice $record): bool => (float) $record->tendered_amount > 0),
                TextEntry::make('change_amount')->label('Change given')->money(fn () => app(CurrentContext::class)->currencyCode())
                    ->visible(fn (Invoice $record): bool => (float) $record->change_amount > 0),
                TextEntry::make('balance')->money(fn () => app(CurrentContext::class)->currencyCode())->weight('bold'),
            ])->columns(3),

            Section::make('Payments')->schema([
                RepeatableEntry::make('claims')
                    ->label('')
                    ->schema([
                        TextEntry::make('transaction.transaction_number')->label('Transaction'),
                        TextEntry::make('transaction.method.name')->label('Method')->placeholder('—'),
                        TextEntry::make('amount')->money(fn () => app(CurrentContext::class)->currencyCode()),
                        TextEntry::make('transaction.status.name')->label('Status')->badge()->placeholder('—'),
                        // Deleted payments stay visible (marked DELETED) rather
                        // than silently vanishing — matching show.tsx.
                        TextEntry::make('transaction.deleted_at')->label('Deleted')->dateTime()->placeholder('—'),
                    ])
                    ->columns(5)
                    ->columnSpanFull(),
            ]),
        ]);
    }

    protected function getHeaderActions(): array
    {
        return [
            Action::make('edit')
                ->url(fn (Invoice $record): string => InvoiceResource::getUrl('edit', ['record' => $record]))
                ->visible(fn (Invoice $record): bool => InvoiceResource::canEdit($record)),

            // Both stocks, both opening the same shared layouts the Master
            // Control live preview renders. A counter runs the 80mm roll and the
            // office runs A4 off the same invoice, so neither is a "default"
            // hidden behind the other.
            Action::make('printA4')
                ->label('Print A4')
                ->icon(Heroicon::OutlinedPrinter)
                ->url(fn (Invoice $record): string => route('invoices.print', [
                    'invoice' => $record,
                    'paper' => 'a4',
                ]))
                ->openUrlInNewTab()
                ->visible(fn (Invoice $record): bool => InvoiceResource::canPrint($record)),

            Action::make('printThermal')
                ->label('Print 80mm')
                ->icon(Heroicon::OutlinedReceiptPercent)
                ->url(fn (Invoice $record): string => route('invoices.print', [
                    'invoice' => $record,
                    'paper' => 'thermal80',
                ]))
                ->openUrlInNewTab()
                ->visible(fn (Invoice $record): bool => InvoiceResource::canPrint($record)),

            Action::make('addPayment')
                ->label('Add payment')
                ->visible(fn (Invoice $record): bool => Perm::can('isms_invoices', 'add_payment')
                    && InvoiceResource::ownsOrViewAll($record))
                ->schema([
                    Select::make('method')
                        ->options(fn (): array => InvoiceResource::paymentMethodOptions())
                        ->required(),
                    TextInput::make('amount')
                        ->numeric()->required()->minValue(0.01)->prefix('Rs.')
                        // A payment can never exceed what is actually owed — the
                        // service enforces the same cap authoritatively.
                        ->maxValue(fn (Invoice $record): float => max(0.01, (float) $record->balance))
                        ->helperText(fn (Invoice $record): string => 'Outstanding balance: Rs. '.number_format((float) $record->balance, 2)),
                    TextInput::make('reference')->maxLength(100),
                    TextInput::make('cheque_number')->maxLength(50),
                    TextInput::make('cheque_bank')->maxLength(100),
                    TextInput::make('cheque_branch')->maxLength(100),
                    DatePicker::make('cheque_date'),
                    Textarea::make('note')->maxLength(300),
                ])
                ->action(fn (Invoice $record, array $data) => $this->addPayment($record, $data)),

            Action::make('applyCredit')
                ->label('Apply credit note')
                ->visible(fn (Invoice $record): bool => Perm::can('isms_invoices', 'add_payment')
                    && InvoiceResource::ownsOrViewAll($record)
                    // routes/web.php gates this one with `customers_enabled`
                    // on top of the permission.
                    && app(PosSettings::class)->hasCustomers()
                    && $record->customer_id !== null)
                ->schema([
                    Select::make('credit_note_ids')
                        ->label('Credit notes')
                        ->multiple()
                        ->required()
                        // Spendable, not merely issued: a part-used note stays
                        // issued and must be offered at what is LEFT on it, not
                        // its face value.
                        ->options(fn (Invoice $record): array => CreditNote::query()
                            ->where('customer_id', $record->customer_id)
                            ->spendable()
                            ->orderBy('id')
                            ->get()
                            ->mapWithKeys(fn (CreditNote $cn): array => [$cn->id => $cn->pickerLabel()])
                            ->all()),
                    TextInput::make('amount')
                        ->label('Amount to apply')
                        ->numeric()->minValue(0.01)
                        ->helperText(fn (Invoice $record): string => 'Pay any amount up to what the selected notes hold. Leave blank to apply the maximum. Outstanding on this invoice: Rs. '
                            .number_format((float) $record->balance, 2).'.'),
                ])
                ->action(fn (Invoice $record, array $data) => $this->applyCredit($record, $data)),

            Action::make('updateStatus')
                ->label('Update status')
                ->visible(fn (Invoice $record): bool => Perm::can('isms_invoices', 'update_order_status'))
                ->schema([
                    Select::make('status_code')
                        ->label('Order status')
                        ->options(fn (): array => StatusLookup::options(Status::CTX_INVOICE_ORDER))
                        ->required(),
                    Textarea::make('note')->maxLength(500),
                ])
                ->action(function (Invoice $record, array $data): void {
                    InvoiceResource::updateOrderStatus($record, $data['status_code']);
                    Notification::make()->title('Status updated.')->success()->send();
                }),

            Action::make('delete')
                ->label('Delete')
                ->color('danger')
                ->requiresConfirmation()
                ->visible(fn (Invoice $record): bool => InvoiceResource::canDelete($record))
                ->action(function (Invoice $record) {
                    try {
                        app(InvoiceService::class)->delete($record);
                    } catch (\Throwable $e) {
                        Notification::make()->title($e->getMessage())->danger()->send();

                        return null;
                    }

                    Notification::make()->title('Invoice deleted and stock/payments reversed.')->success()->send();

                    return redirect(InvoiceResource::getUrl('index'));
                }),
        ];
    }

    /**
     * Ported from InvoiceController::addPayment().
     *
     * @param  array<string, mixed>  $data
     */
    protected function addPayment(Invoice $invoice, array $data): void
    {
        if ($error = PaymentMethod::validateCustomFields($data['method'], $data['custom_fields'] ?? [])) {
            Notification::make()->title($error)->danger()->send();

            return;
        }

        $split = [
            'method' => $data['method'],
            'amount' => (float) $data['amount'],
            'reference' => $data['reference'] ?? null,
            'cheque_number' => $data['cheque_number'] ?? null,
            'cheque_bank' => $data['cheque_bank'] ?? null,
            'cheque_branch_code' => $data['cheque_branch'] ?? null,
            'cheque_date' => $data['cheque_date'] ?? null,
            'note' => $data['note'] ?? null,
            'custom_fields' => $data['custom_fields'] ?? null,
        ];

        try {
            app(PaymentService::class)->settleInvoice($invoice, [$split], confirmShortfall: true);
        } catch (RuntimeException|InvalidArgumentException $e) {
            Notification::make()->title($e->getMessage())->danger()->send();

            return;
        }

        AuditLog::record('invoice.payment_added', $invoice, [
            'reference' => $invoice->invoice_number,
            'method' => $data['method'],
            'amount' => 'Rs. '.number_format((float) $data['amount'], 2),
        ]);

        Notification::make()->title('Payment recorded on '.$invoice->invoice_number.'.')->success()->send();
    }

    /**
     * Ported from InvoiceController::applyCredit() — including the guards that
     * run before validation in the source (walk-in invoice, already-paid).
     *
     * @param  array<string, mixed>  $data
     */
    protected function applyCredit(Invoice $invoice, array $data): void
    {
        if (! $invoice->customer_id) {
            Notification::make()->title('Credit notes cannot be applied to walk-in invoices.')->danger()->send();

            return;
        }

        $balance = (float) $invoice->balance;

        if ($balance <= 0.001) {
            Notification::make()->title('This invoice is already fully paid.')->danger()->send();

            return;
        }

        $credit = $this->resolveCheckoutCredit(
            $invoice->customer_id,
            array_map('intval', $data['credit_note_ids'] ?? []),
            $balance,
            app(CustomerCreditService::class),
            isset($data['amount']) && $data['amount'] !== null && $data['amount'] !== ''
                ? (float) $data['amount']
                : null,
        );

        if ($credit['notes']->isEmpty()) {
            Notification::make()->title('No applicable credit notes found for this customer.')->danger()->send();

            return;
        }

        if ($credit['applied'] <= 0.001) {
            Notification::make()->title('The customer has no available credit balance to apply.')->danger()->send();

            return;
        }

        try {
            // Booked as a `credit` payment, not as a discount. The invoice is
            // still worth what it was worth; the customer has simply settled
            // part of it with credit they already held. PaymentService draws
            // each note down by what it contributed and recomputes the
            // invoice's paid amount, balance and payment status.
            app(PaymentService::class)->settleInvoice($invoice, $credit['splits']);

            AuditLog::record('invoice.credit_applied', $invoice, [
                'reference' => $invoice->invoice_number,
                'credit_notes' => $credit['notes']->pluck('cn_number')->join(', '),
                'total_credit' => number_format($credit['applied'], 2),
            ]);
        } catch (RuntimeException $e) {
            Notification::make()->title($e->getMessage())->danger()->send();

            return;
        }

        $applied = number_format($credit['applied'], 2);
        $cnList = $credit['notes']->pluck('cn_number')->join(', ');

        Notification::make()
            ->title("Rs. {$applied} paid from credit note(s) {$cnList}.")
            ->body("Applied to {$invoice->invoice_number}.")
            ->success()
            ->send();
    }
}
