<?php

namespace App\Filament\Resources;

use App\Filament\Exports\CashMovementExporter;
use App\Filament\Resources\CashMovementResource\Pages\ListCashMovements;
use App\Filament\Resources\Concerns\HasModulePermissions;
use App\Filament\Resources\Concerns\HasStandardTableFeatures;
use App\Filament\Support\ReportDateRange;
use App\Filament\Support\ThermalReceipt;
use App\Models\Sales\CashMovement;
use App\Models\Sales\Till;
use App\Models\Scopes\BranchScope;
use App\Services\CurrentContext;
use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Contracts\HasTable;
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;

/**
 * Ported from App\Http\Controllers\Isms\CashMovementController
 * (docs/migration-plan.md Phase 5). Read-only: the source controller exposes
 * `index` and nothing else — cash movements are only ever written by
 * CashMovementService (POS withdraw/cash-in, automatic sale/refund takes), so
 * there is deliberately no create/edit/delete surface here to port.
 *
 * The source's own visibility rule is reproduced in getEloquentQuery():
 * movements are filtered to sessions whose branch is in the current user's
 * accessible branch list, and capped at the same 500 rows.
 */
class CashMovementResource extends Resource
{
    use HasModulePermissions;
    use HasStandardTableFeatures;

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

    protected static string $permissionModuleKey = 'pos_cash_movements';

    protected static string $exporter = CashMovementExporter::class;

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

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

    protected static ?string $navigationLabel = 'Cash Movements Report';

    protected static ?int $navigationSort = 20;

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

    public static function canEdit(Model $record): bool
    {
        return false;
    }

    public static function canDelete(Model $record): bool
    {
        return false;
    }

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

    /**
     * CashMovement is NOT BranchScoped (it hangs off a PosSession, which is),
     * so the branch restriction is applied explicitly here — exactly as the
     * source controller does via whereHas('session', ...) against
     * CurrentContext::branches().
     */
    public static function getEloquentQuery(): Builder
    {
        $context = app(CurrentContext::class);

        return parent::getEloquentQuery()
            ->whereHas('session', function (Builder $q) use ($context): void {
                $context->restrictToAccessibleBranches($q, 'pos_sessions.branch_id');
            })
            ->with(['session.till:id,name', 'session.branch:id,name', 'reason:id,name', 'creator:id,name']);
    }

    public static function table(Table $table): Table
    {
        return static::standardTableDefaults($table)
            ->recordUrl(static::standardRecordUrl())
            ->emptyStateHeading('No cash movements yet')
            ->emptyStateDescription('Money moved in and out of a till is recorded here.')
            ->columns([
                TextColumn::make('created_at')->dateTime()->sortable(),
                TextColumn::make('type')
                    ->badge()
                    ->color(fn (?string $state): string => match ($state) {
                        CashMovement::IN => 'success',
                        CashMovement::OUT => 'warning',
                        CashMovement::CASH_REFUND => 'danger',
                        default => 'gray',
                    }),
                TextColumn::make('source')->badge()->color('gray'),
                TextColumn::make('amount')->money(fn () => app(CurrentContext::class)->currencyCode())->sortable(),
                TextColumn::make('reason.name')->label('Reason')->placeholder('—'),
                TextColumn::make('note')->placeholder('—')->wrap(),
                TextColumn::make('session.till.name')->label('Till')->placeholder('—'),
                TextColumn::make('session.branch.name')->label('Branch')->placeholder('—'),
                TextColumn::make('creator.name')->label('By')->placeholder('—'),
            ])
            ->defaultSort('created_at', 'desc')
            ->filters([
                SelectFilter::make('type')->options([
                    CashMovement::IN => 'Cash In',
                    CashMovement::OUT => 'Cash Out',
                    CashMovement::CASH_REFUND => 'Cash Refund',
                ]),
                SelectFilter::make('till_id')
                    ->label('Till')
                    // Options are already restricted to the user's own
                    // accessible branches (context->branches()) and to their
                    // accessible tills — bypass Till's own BranchScope so an
                    // ambient single-branch context doesn't additionally
                    // narrow this cross-branch filter's option list down to
                    // just that one branch. Labels carry the till's branch so
                    // identical till names stay distinguishable.
                    ->options(function (): array {
                        $context = app(CurrentContext::class);
                        $query = Till::withoutGlobalScope(BranchScope::class)
                            ->whereIn('branch_id', $context->branches()->pluck('id'))
                            ->with('branch:id,name')
                            ->orderBy('name');

                        $context->restrictToAccessibleTills($query, 'tills.id');

                        return $query->get()
                            ->mapWithKeys(fn (Till $till) => [
                                $till->id => $till->name.($till->branch?->name ? ' — '.$till->branch->name : ''),
                            ])
                            ->all();
                    })
                    ->query(fn (Builder $query, array $data): Builder => $query->when(
                        $data['value'] ?? null,
                        fn (Builder $q, $tillId) => $q->whereHas('session', fn ($s) => $s->where('till_id', $tillId)),
                    )),
                ReportDateRange::filter('created_at'),
                Filter::make('amount')
                    ->schema([
                        TextInput::make('from')->label('Amount from')->numeric()->prefix('Rs.'),
                        TextInput::make('to')->label('Amount to')->numeric()->prefix('Rs.'),
                    ])
                    ->query(fn (Builder $query, array $data): Builder => $query
                        ->when($data['from'] ?? null, fn (Builder $q, $v) => $q->where('amount', '>=', $v))
                        ->when($data['to'] ?? null, fn (Builder $q, $v) => $q->where('amount', '<=', $v))),
                SelectFilter::make('reason_id')->label('Reason')->relationship('reason', 'name'),
            ])
            ->filtersLayout(FiltersLayout::AboveContentCollapsible)
            ->filtersFormColumns(2)
            ->headerActions([...static::standardExportActions(), static::printReceiptAction()])
            ->toolbarActions(static::standardExportBulkActions());
    }

    /**
     * The shared 80mm thermal printout, built from the table's CURRENT
     * filtered query at the moment the modal opens (same "what you see is what
     * you print" contract as the report pages' Print action).
     */
    public static function printReceiptAction(): Action
    {
        return ThermalReceipt::action(
            'printReceipt',
            fn (HasTable $livewire): array => static::receiptPayload($livewire),
            fn (): bool => static::canExport(),
        );
    }

    /**
     * @return array<string, mixed>
     */
    protected static function receiptPayload(HasTable $livewire): array
    {
        $query = $livewire->getTableQueryForExport()
            ->with(['session.till:id,name', 'session.branch:id,name', 'reason:id,name', 'creator:id,name']);

        $records = $query->get();

        $currencySymbol = app(CurrentContext::class)->currencySymbol();
        $rows = $records->map(fn (CashMovement $m): array => [
            'label' => $m->created_at->format('d/m/y H:i').' '.ucfirst(str_replace('_', ' ', $m->type))
                .($m->reason?->name ? ' · '.$m->reason->name : '')
                .($m->session?->till?->name ? ' · '.$m->session->till->name : ''),
            'value' => ($m->type === CashMovement::IN ? '+' : '-')
                .$currencySymbol.' '.number_format((float) $m->amount, 2),
        ])->all();

        return [
            'title' => static::getNavigationLabel(),
            'meta' => 'Period: '.(ReportDateRange::describe($livewire->getTableFilterState('date') ?? [])),
            'rows' => $rows,
            'totals' => [
                ['label' => 'Cash in', 'value' => $currencySymbol.' '.number_format((float) $records->where('type', CashMovement::IN)->sum('amount'), 2)],
                ['label' => 'Cash out', 'value' => $currencySymbol.' '.number_format((float) $records->where('type', CashMovement::OUT)->sum('amount'), 2)],
                ['label' => 'Cash refunds', 'value' => $currencySymbol.' '.number_format((float) $records->where('type', CashMovement::CASH_REFUND)->sum('amount'), 2)],
            ],
            'footer' => 'Generated '.now()->format('d M Y, H:i'),
        ];
    }

    public static function getPages(): array
    {
        return [
            'index' => ListCashMovements::route('/'),
        ];
    }
}
