<?php

namespace App\Filament\Resources;

use App\Filament\Exports\InvoiceExporter;
use App\Filament\Resources\Concerns\AssertsBranchAccess;
use App\Filament\Resources\Concerns\HasModulePermissions;
use App\Filament\Resources\Concerns\HasStandardTableFeatures;
use App\Filament\Resources\InvoiceResource\Pages\CreateInvoice;
use App\Filament\Resources\InvoiceResource\Pages\CreateLegacyInvoice;
use App\Filament\Resources\InvoiceResource\Pages\EditInvoice;
use App\Filament\Resources\InvoiceResource\Pages\ListInvoices;
use App\Filament\Resources\InvoiceResource\Pages\ViewInvoice;
use App\Filament\Support\Chip;
use App\Filament\Support\Perm;
use App\Filament\Support\ProductSelect;
use App\Filament\Support\RelationSelect;
use App\Filament\Support\StatusLookup;
use App\Models\Catalog\Product;
use App\Models\Lookups\PaymentMethod;
use App\Models\Lookups\Status;
use App\Models\Returns\CreditNote;
use App\Models\Sales\Invoice;
use App\Services\CurrentContext;
use App\Services\VatSettings;
use App\Support\Quantity;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

/**
 * Ported from App\Http\Controllers\Isms\InvoiceController + InvoiceService
 * (docs/migration-plan.md Phase 5).
 *
 * Behaviour preserved from the source controller:
 *  - index scoping: without `isms_invoices.view_all` a user only sees invoices
 *    they created themselves (`created_by`); the listing includes soft-deleted
 *    rows (`withTrashed()`), which the detail pages do not.
 *  - show/edit/delete/applyCredit all run assertOwnershipOrViewAll() — expressed
 *    here as record-level canView()/canEdit()/canDelete() overrides, so a
 *    non-owner gets the same 403 rather than a silent 404.
 *  - the create page reproduces checkoutComplete() (the path the source UI
 *    actually drives), not store(). See the Decisions Log: `POST /isms/invoices`
 *    (store()) is routed but called by nothing in resources/js — dead code, so
 *    no separate surface was built for it.
 *  - a line's price is never trusted from the client; InvoiceService::create()
 *    reprices every product line from the catalog itself.
 */
class InvoiceResource extends Resource
{
    use AssertsBranchAccess;
    use HasModulePermissions;
    use HasStandardTableFeatures;

    protected static ?string $model = Invoice::class;

    protected static string $permissionModuleKey = 'isms_invoices';

    protected static string $exporter = InvoiceExporter::class;

    protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;

    protected static string|\UnitEnum|null $navigationGroup = 'Sales';

    protected static ?int $navigationSort = 10;

    protected static ?string $recordTitleAttribute = 'invoice_number';

    /**
     * The source's `isms_invoices.view_all` gate: without it, a user is limited
     * to the invoices they created.
     */
    public static function canViewAll(): bool
    {
        return Auth::user()?->hasPermissionTo('isms_invoices.view_all') ?? false;
    }

    /**
     * InvoiceController::assertOwnershipOrViewAll() — reps may only act on
     * invoices they created themselves.
     */
    public static function ownsOrViewAll(Model $record): bool
    {
        return static::canViewAll() || $record->created_by === Auth::id();
    }

    public static function canView(Model $record): bool
    {
        return static::hasModulePermission('view') && static::ownsOrViewAll($record);
    }

    /**
     * Producing the printed invoice document (InvoicePrintController, the A4 /
     * 80mm actions on ViewInvoice, and the POS receipt modal). Two ways in,
     * because two modules legitimately put the same paper on the counter:
     *
     *   - the invoice module's own print actions — `isms_invoices.print` on an
     *     invoice the actor is allowed to see at all;
     *   - the receipt handed over at the register — `pos.checkout` on an invoice
     *     the cashier rang up themselves.
     *
     * The second clause is why this method exists. The print route used to
     * require `isms_invoices.view`, which POS-only roles deliberately do not
     * hold (the seeded Manager role has `pos.checkout` and no `isms_invoices.*`
     * at all), so a cashier could take the money and then be handed
     * `can_print: false` instead of a receipt. `isms_invoices.print` itself was
     * meanwhile declared in the catalog and granted to the Sales Person role but
     * checked by nothing.
     */
    public static function canPrint(Model $record): bool
    {
        if (Perm::can('isms_invoices', 'print') && static::canView($record)) {
            return true;
        }

        return Perm::can('pos', 'checkout') && $record->created_by === Auth::id();
    }

    public static function canEdit(Model $record): bool
    {
        return static::hasModulePermission('edit') && static::ownsOrViewAll($record);
    }

    public static function canDelete(Model $record): bool
    {
        return static::hasModulePermission('delete') && static::ownsOrViewAll($record);
    }

    /**
     * The full checkout form — the Filament expression of the source's
     * two-step create.tsx → checkout.tsx flow (which only split across two
     * pages because Inertia needed a session hand-off between them; the
     * submitted payload and the resulting InvoiceService::create() call are
     * identical).
     */
    public static function form(Schema $schema): Schema
    {
        return $schema->components([
            Section::make('Order')->schema([
                RelationSelect::make(
                    name: 'customer_id',
                    relationship: 'customer',
                    titleAttribute: 'business_name',
                    modifyQueryUsing: fn ($query) => $query->where('is_active', true),
                    createPermission: 'isms_customers.create',
                )->label('Customer')->required(),
                RelationSelect::make(
                    name: 'branch_id',
                    relationship: 'branch',
                    titleAttribute: 'name',
                    modifyQueryUsing: fn ($query) => $query->where('is_active', true),
                )->label('Branch')->required(),
                TextInput::make('discount')
                    ->label('Order discount')
                    ->numeric()->minValue(0)->maxValue(99999999.99)
                    ->default(0)->prefix('Rs.')
                    ->visible(fn (): bool => Perm::can('isms_invoices', 'discount')),
                Textarea::make('note')->maxLength(500)->columnSpanFull(),
            ])->columns(3),

            Section::make('Items')->schema([
                Repeater::make('items')
                    ->schema([
                        ProductSelect::standalone(
                            modifyQueryUsing: fn ($query) => $query->where('is_active', true),
                        )->label('Product')->required()->live()->columnSpanFull(),
                        TextInput::make('quantity')
                            ->numeric()->required()
                            ->minValue(0.01)->maxValue(1000000)
                            ->default(1)
                            ->suffix(fn (Get $get): ?string => Quantity::unitLabel(Product::find($get('product_id'))?->baseUnit)),
                        TextInput::make('unit_price')
                            ->numeric()->required()
                            ->minValue(0)->maxValue(99999999.99)
                            ->prefix('Rs.')
                            ->helperText('Repriced from the catalog on save.'),
                        TextInput::make('vat_rate')
                            ->numeric()->minValue(0)->maxValue(100)
                            ->default(0)->suffix('%')
                            ->visible(fn (): bool => app(VatSettings::class)->enabled()),
                    ])
                    ->columns(3)
                    ->minItems(1)
                    ->addActionLabel('Add item')
                    ->columnSpanFull(),
            ]),

            Section::make('Payments')->schema([
                Repeater::make('payments')
                    ->schema([
                        Select::make('method')
                            ->options(fn (): array => static::paymentMethodOptions())
                            ->required(),
                        TextInput::make('amount')
                            ->numeric()->required()
                            ->minValue(0.01)->prefix('Rs.')
                            // A payment can never exceed what the invoice is
                            // worth — the service enforces the same cap
                            // authoritatively on save.
                            ->maxValue(fn (Get $get): float => max(0.01, static::estimatePayable($get)))
                            ->helperText(fn (Get $get): string => 'Invoice total: Rs. '.number_format(static::estimatePayable($get), 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'),
                    ])
                    // This Section is one of two side by side on the page, so 3
                    // columns of fields (some with longer labels like "Cheque
                    // bank") only had roughly half the viewport to work with.
                    ->columns(2)
                    ->defaultItems(0)
                    ->addActionLabel('Add payment')
                    ->columnSpanFull(),
            ])->collapsed(),

            Section::make('Credit, vouchers & loyalty')->schema([
                Select::make('credit_note_ids')
                    ->label('Apply credit notes')
                    ->multiple()
                    ->options(fn (): array => static::issuedCreditNoteOptions())
                    ->helperText('Only issued notes belonging to the selected customer are applied.'),
                TextInput::make('voucher_codes')
                    ->label('Voucher codes')
                    ->helperText('Comma-separated.')
                    ->maxLength(255),
                TextInput::make('redeem_points')
                    ->label('Redeem loyalty points')
                    ->numeric()->minValue(0)->default(0),
            ])->columns(3)->collapsed(),
        ]);
    }

    /**
     * The active built-in methods (whatever the tenant hasn't toggled off in
     * master control) plus any active tenant-defined custom method.
     *
     * @return array<string, string>
     */
    public static function paymentMethodOptions(): array
    {
        return PaymentMethod::checkoutOptions();
    }

    /**
     * @return array<int, string>
     */
    public static function issuedCreditNoteOptions(): array
    {
        return CreditNote::query()
            // Spendable, not merely issued: a part-used note stays issued and
            // must be offered at what is LEFT on it, not its face value.
            ->spendable()
            ->orderBy('id')
            ->get()
            ->mapWithKeys(fn (CreditNote $cn): array => [$cn->id => $cn->pickerLabel()])
            ->all();
    }

    /**
     * The invoice total as the form can estimate it from the items grid —
     * the same maths InvoiceService applies on save (lines are repriced from
     * the catalog, so this is a UX cap; the service is the authority).
     */
    public static function estimatePayable(Get $get): float
    {
        $vatEnabled = app(VatSettings::class)->enabled();
        $subtotal = 0.0;
        $vatTotal = 0.0;

        foreach ($get('../../items') ?? [] as $item) {
            $gross = (float) ($item['unit_price'] ?? 0) * (float) ($item['quantity'] ?? 0);
            $subtotal += $gross;
            if ($vatEnabled) {
                $vatTotal += $gross * ((float) ($item['vat_rate'] ?? 0) / 100);
            }
        }

        return round(max(0, $subtotal - (float) ($get('../../discount') ?? 0) + $vatTotal), 2);
    }

    public static function table(Table $table): Table
    {
        return static::standardTableDefaults($table)
            ->recordUrl(static::standardRecordUrl())
            ->emptyStateHeading('No invoices yet')
            ->emptyStateDescription('Invoices raised at the till or from sales orders will appear here.')

            // withTrashed() matches the source index's ->withTrashed(); the
            // ownership filter matches its ->when(! $canViewAll, ...).
            ->modifyQueryUsing(fn (Builder $query): Builder => $query
                ->withTrashed()
                ->when(! static::canViewAll(), fn (Builder $q) => $q->where('created_by', Auth::id())))
            ->columns([
                Chip::documentNumber('invoice_number', 'Invoice #')->searchable()->sortable(),
                TextColumn::make('customer.business_name')->label('Customer')->searchable()->placeholder('Walk-in'),
                TextColumn::make('branch.name')->label('Branch')->placeholder('—'),
                TextColumn::make('channel')->badge()->color('gray'),
                TextColumn::make('orderStatus.name')
                    ->label('Order status')
                    ->badge()
                    ->color(fn (Invoice $record): string => StatusLookup::colorOf(Status::CTX_INVOICE_ORDER, $record->orderStatus?->code ?? '')),
                TextColumn::make('paymentStatus.name')
                    ->label('Payment')
                    ->badge()
                    ->color(fn (Invoice $record): string => StatusLookup::colorOf(Status::CTX_INVOICE_PAYMENT, $record->paymentStatus?->code ?? '')),
                TextColumn::make('net_total')->money(fn () => app(CurrentContext::class)->currencyCode())->sortable(),
                TextColumn::make('paid_amount')->money(fn () => app(CurrentContext::class)->currencyCode())->sortable(),
                TextColumn::make('balance')->money(fn () => app(CurrentContext::class)->currencyCode())->sortable(),
                TextColumn::make('creator.name')->label('Created by')->placeholder('—')->toggleable(),
                TextColumn::make('deleted_at')->label('Deleted')->dateTime()->placeholder('—')->toggleable(isToggledHiddenByDefault: true),
                TextColumn::make('created_at')->dateTime()->sortable(),
            ])
            ->defaultSort('created_at', 'desc')
            ->filters([
                Filter::make('invoice_number')
                    ->schema([TextInput::make('value')->label('Invoice #')])
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $value) => $q->where('invoice_number', 'like', "%{$value}%"),
                    )),
                Filter::make('customer')
                    ->schema([TextInput::make('value')->label('Customer')])
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $value) => $q->whereHas('customer', fn ($qq) => $qq->where('business_name', 'like', "%{$value}%")),
                    )),
                SelectFilter::make('channel')->options([
                    'till' => 'Till',
                    'sales_order' => 'Sales Order',
                    'manual' => 'Manual',
                    'legacy' => 'Legacy',
                ]),
                SelectFilter::make('order_status')
                    ->label('Order status')
                    ->options(fn (): array => StatusLookup::options(Status::CTX_INVOICE_ORDER))
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $code) => $q->whereHas('orderStatus', fn ($qq) => $qq->where('code', $code)),
                    )),
                SelectFilter::make('payment_status')
                    ->label('Payment status')
                    ->options(fn (): array => StatusLookup::options(Status::CTX_INVOICE_PAYMENT))
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $code) => $q->whereHas('paymentStatus', fn ($qq) => $qq->where('code', $code)),
                    )),
                Filter::make('net_total')
                    ->schema([
                        TextInput::make('from')->label('Total from')->numeric()->prefix('Rs.'),
                        TextInput::make('to')->label('Total to')->numeric()->prefix('Rs.'),
                    ])
                    ->query(fn (Builder $query, array $data): Builder => $query
                        ->when($data['from'] ?? null, fn (Builder $q, $v) => $q->where('net_total', '>=', $v))
                        ->when($data['to'] ?? null, fn (Builder $q, $v) => $q->where('net_total', '<=', $v))),
                Filter::make('created_at')
                    ->schema([
                        DatePicker::make('date_from'),
                        DatePicker::make('date_to'),
                    ])
                    ->query(fn (Builder $query, array $data): Builder => $query
                        ->when($data['date_from'] ?? null, fn (Builder $q, $d) => $q->whereDate('created_at', '>=', $d))
                        ->when($data['date_to'] ?? null, fn (Builder $q, $d) => $q->whereDate('created_at', '<=', $d))),
            ])
            ->filtersLayout(FiltersLayout::AboveContentCollapsible)
            ->filtersFormColumns(2)
            ->headerActions(static::standardExportActions())
            ->toolbarActions(static::standardExportBulkActions())
            ->recordActions([
                Action::make('view')
                    ->url(fn (Invoice $record): string => ViewInvoice::getUrl(['record' => $record]))
                    ->visible(fn (Invoice $record): bool => static::canView($record) && ! $record->trashed()),
                Action::make('updateStatus')
                    ->label('Update status')
                    ->visible(fn (Invoice $record): bool => Perm::can('isms_invoices', 'update_order_status') && ! $record->trashed())
                    ->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 {
                        static::updateOrderStatus($record, $data['status_code']);
                    }),
            ]);
    }

    /**
     * Ported from InvoiceController::updateStatus() — the status must exist in
     * the `invoice_order` context (firstOrFail in the source).
     */
    public static function updateOrderStatus(Invoice $invoice, string $statusCode): void
    {
        $status = Status::where('context', Status::CTX_INVOICE_ORDER)
            ->where('code', $statusCode)
            ->firstOrFail();

        $invoice->update(['order_status_id' => $status->id]);
    }

    public static function getPages(): array
    {
        return [
            'index' => ListInvoices::route('/'),
            'create' => CreateInvoice::route('/create'),
            'legacy' => CreateLegacyInvoice::route('/legacy/create'),
            'view' => ViewInvoice::route('/{record}'),
            'edit' => EditInvoice::route('/{record}/edit'),
        ];
    }
}
