<?php

namespace App\Filament\Resources;

use App\Filament\Exports\CreditNoteExporter;
use App\Filament\Resources\Concerns\HasModulePermissions;
use App\Filament\Resources\Concerns\HasStandardTableFeatures;
use App\Filament\Resources\CreditNoteResource\Pages\CreateCreditNote;
use App\Filament\Resources\CreditNoteResource\Pages\ListCreditNotes;
use App\Filament\Resources\CreditNoteResource\Pages\ViewCreditNote;
use App\Filament\Support\Chip;
use App\Filament\Support\Perm;
use App\Filament\Support\RelationSelect;
use App\Filament\Support\StatusLookup;
use App\Models\Lookups\Status;
use App\Models\Returns\CreditNote;
use App\Models\User;
use App\Services\AuditLog;
use App\Services\CreditNoteService;
use App\Services\CurrentContext;
use App\Services\CustomerCreditService;
use App\Services\PosSettings;
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
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;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * Ported from App\Http\Controllers\Isms\CreditNoteController + CreditNoteService
 * (docs/migration-plan.md Phase 6).
 *
 * Every credit-note route in the source carries `customers_enabled` on top of
 * its permission, so a walk-in-only tenant gets a clean 404 rather than a UI
 * that opens dead screens. That middleware has no Filament equivalent, so the
 * same rule is applied in canViewAny()/canCreate() via PosSettings::hasCustomers()
 * — a credit note is meaningless without a customer to hold it.
 *
 * Transitions (draft → issued → claimed | void) go exclusively through
 * CreditNoteService, which owns race-safe numbering and the customer credit
 * pool; only the manual claim path lives in the controller, and is reproduced
 * here unchanged.
 */
class CreditNoteResource extends Resource
{
    use HasModulePermissions;
    use HasStandardTableFeatures;

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

    protected static string $permissionModuleKey = 'isms_credit_notes';

    protected static string $exporter = CreditNoteExporter::class;

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

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

    protected static ?string $navigationLabel = 'Credit Notes';

    protected static ?int $navigationSort = 50;

    protected static ?string $recordTitleAttribute = 'cn_number';

    /** The `customers_enabled` middleware, expressed as a Filament gate. */
    protected static function customersEnabled(): bool
    {
        return app(PosSettings::class)->hasCustomers();
    }

    public static function canViewAny(): bool
    {
        return static::customersEnabled() && static::hasModulePermission('access');
    }

    public static function canCreate(): bool
    {
        return static::customersEnabled() && static::hasModulePermission('create');
    }

    public static function canViewAll(): bool
    {
        return Auth::user()?->hasPermissionTo('isms_credit_notes.view_all') ?? false;
    }

    public static function ownsOrViewAll(Model $record): bool
    {
        return static::canViewAll() || $record->created_by === Auth::id();
    }

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

    public static function canEdit(Model $record): bool
    {
        // No update route exists in the source — a note only ever moves through
        // its issue/claim/void transitions.
        return false;
    }

    public static function canDelete(Model $record): bool
    {
        // Notes are voided, never deleted.
        return false;
    }

    public static function canDeleteAny(): bool
    {
        return false;
    }

    public static function form(Schema $schema): Schema
    {
        return $schema->components([
            Section::make()->schema([
                RelationSelect::make(
                    name: 'customer_id',
                    relationship: 'customer',
                    titleAttribute: 'business_name',
                    modifyQueryUsing: fn ($query) => $query->where('is_active', true),
                )->label('Customer')->required(),
                TextInput::make('amount')
                    ->numeric()->required()->minValue(0.01)->maxValue(99999999.99)->prefix('Rs.'),
                // Manual notes start life as draft or issued; claimed/void states are
                // only ever reached through the proper transitions.
                Select::make('status_code')
                    ->label('Status')
                    ->options(fn (): array => collect(StatusLookup::options(Status::CTX_CREDIT_NOTE))
                        ->only([Status::DRAFT, Status::ISSUED])
                        ->all())
                    ->required()
                    ->default(Status::DRAFT),
                Textarea::make('note')->maxLength(1000)->columnSpanFull(),
            ])->columns(3),
        ]);
    }

    public static function table(Table $table): Table
    {
        return static::standardTableDefaults($table)
            ->recordUrl(static::standardRecordUrl())
            ->emptyStateHeading('No credit notes yet')
            ->emptyStateDescription('Notes issued to customers will appear here.')
            ->modifyQueryUsing(fn (Builder $query): Builder => $query
                ->when(! static::canViewAll(), fn (Builder $q) => $q->where('created_by', Auth::id())))
            ->columns([
                Chip::documentNumber('cn_number', 'CN #')->searchable()->sortable(),
                TextColumn::make('customer.business_name')->label('Customer')->searchable()->placeholder('—'),
                TextColumn::make('status.name')
                    ->label('Status')
                    ->badge()
                    ->color(fn (CreditNote $record): string => StatusLookup::colorOf(Status::CTX_CREDIT_NOTE, $record->status?->code ?? '')),
                TextColumn::make('amount')->label('Face value')->money(fn () => app(CurrentContext::class)->currencyCode())->sortable(),
                TextColumn::make('applied_amount')->label('Applied')->money(fn () => app(CurrentContext::class)->currencyCode())->placeholder('—'),
                // The figure that actually answers "can this still be spent,
                // and for how much?" — a note is drawn down over time, so face
                // value alone tells you nothing about what is left on it.
                TextColumn::make('remaining')
                    ->label('Remaining')
                    ->money(fn () => app(CurrentContext::class)->currencyCode())
                    ->state(fn (CreditNote $record): float => $record->remainingAmount())
                    ->color(fn (CreditNote $record): string => $record->isFullyApplied() ? 'gray' : 'success')
                    ->weight('bold'),
                TextColumn::make('return.return_number')->label('From return')->placeholder('—'),
                TextColumn::make('claimedInvoice.invoice_number')->label('Claimed on')->placeholder('—'),
                TextColumn::make('creator.name')->label('Issued by')->placeholder('—')->toggleable(),
                TextColumn::make('created_at')->dateTime()->sortable(),
            ])
            ->defaultSort('created_at', 'desc')
            ->filters([
                SelectFilter::make('status')
                    ->options(fn (): array => StatusLookup::options(Status::CTX_CREDIT_NOTE))
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $code) => $q->whereHas('status', fn ($qq) => $qq->where('code', $code)),
                    )),
                SelectFilter::make('rep')
                    ->label('Issued by')
                    ->visible(fn (): bool => static::canViewAll())
                    ->options(fn (): array => User::query()
                        ->whereIn('id', CreditNote::query()->whereNotNull('created_by')->distinct()->pluck('created_by'))
                        ->orderBy('name')
                        ->pluck('name', 'id')
                        ->all())
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $rep) => $q->where('created_by', $rep),
                    )),
                Filter::make('cn_number')
                    ->schema([TextInput::make('value')->label('CN #')])
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $value) => $q->where('cn_number', 'like', "%{$value}%"),
                    )),
            ])
            ->filtersLayout(FiltersLayout::AboveContentCollapsible)
            ->filtersFormColumns(2)
            ->headerActions(static::standardExportActions())
            ->toolbarActions(static::standardExportBulkActions())
            ->recordActions(static::transitionActions());
    }

    /**
     * The issue / claim / void transitions, shared by the table row actions and
     * the view page's header actions so both behave identically.
     *
     * @return array<int, Action>
     */
    public static function transitionActions(): array
    {
        return [
            Action::make('view')
                ->url(fn (CreditNote $record): string => ViewCreditNote::getUrl(['record' => $record]))
                ->visible(fn (CreditNote $record): bool => static::canView($record)),

            Action::make('issue')
                ->visible(fn (CreditNote $record): bool => Perm::can('isms_credit_notes', 'issue')
                    && static::customersEnabled()
                    && $record->status?->code === Status::DRAFT)
                ->requiresConfirmation()
                ->action(function (CreditNote $record): void {
                    try {
                        app(CreditNoteService::class)->issue($record);
                        Notification::make()->title('Credit note issued.')->success()->send();
                    } catch (RuntimeException $e) {
                        Notification::make()->title($e->getMessage())->danger()->send();
                    }
                }),

            Action::make('claim')
                ->visible(fn (CreditNote $record): bool => Perm::can('isms_credit_notes', 'claim')
                    && static::customersEnabled()
                    && $record->status?->code === Status::ISSUED)
                ->requiresConfirmation()
                ->action(fn (CreditNote $record) => static::claim($record)),

            Action::make('void')
                ->color('danger')
                ->visible(fn (CreditNote $record): bool => Perm::can('isms_credit_notes', 'void')
                    && static::customersEnabled()
                    && ! in_array($record->status?->code, [Status::CLAIMED, Status::VOID], true))
                ->requiresConfirmation()
                ->schema([
                    TextInput::make('void_reason')->maxLength(200),
                ])
                ->action(function (CreditNote $record, array $data): void {
                    try {
                        app(CreditNoteService::class)->void($record, $data['void_reason'] ?? '');
                        Notification::make()->title('Credit note voided.')->success()->send();
                    } catch (RuntimeException $e) {
                        Notification::make()->title($e->getMessage())->danger()->send();
                    }
                }),
        ];
    }

    /**
     * Ported verbatim from CreditNoteController::claim() — a manual claim with
     * no invoice (e.g. settled outside a sale). Draws the amount from the
     * customer's credit pool so the note cannot be redeemed twice.
     */
    public static function claim(CreditNote $creditNote): void
    {
        if ($creditNote->status?->code !== Status::ISSUED) {
            Notification::make()->title('Only issued credit notes can be claimed.')->danger()->send();

            return;
        }

        try {
            DB::transaction(function () use ($creditNote): void {
                if ($creditNote->customer && (float) $creditNote->amount > 0) {
                    app(CustomerCreditService::class)->debit(
                        customer: $creditNote->customer,
                        amount: (float) $creditNote->amount,
                        reference: $creditNote,
                        note: "Credit note {$creditNote->cn_number} claimed manually.",
                    );
                }

                $creditNote->update([
                    'status_id' => Status::idOf(Status::CTX_CREDIT_NOTE, Status::CLAIMED),
                    'claimed_at' => now(),
                ]);

                AuditLog::record('credit_note.claimed', $creditNote, ['via' => 'manual']);
            });
        } catch (RuntimeException $e) {
            Notification::make()->title($e->getMessage())->danger()->send();

            return;
        }

        Notification::make()->title('Credit note marked as claimed.')->success()->send();
    }

    public static function getPages(): array
    {
        return [
            'index' => ListCreditNotes::route('/'),
            'create' => CreateCreditNote::route('/create'),
            'view' => ViewCreditNote::route('/{record}'),
        ];
    }
}
