<?php

namespace App\Filament\Pages\Reports;

use App\Models\Inventory\StockBatch;
use App\Models\Lookups\Status;
use App\Services\CurrentContext;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * Ported from ReportController::expiry(). Same bucket thresholds
 * (expired / <=7 critical / <=30 warning / <=90 watch / else ok), same FEFO
 * ordering, same "expired rows always shown regardless of the window" rule,
 * and the same value-at-risk arithmetic.
 *
 * The source computed the window (`days until expiry <= withinDays`, expired
 * always included) in PHP AFTER fetching; here it is pushed into the query so
 * the native table can paginate server-side. The per-row bucket is still
 * derived per record (it drives badge colors, the CSV and the summary).
 *
 * The `reports_expiry` module key is defined in MenuDefinition (source
 * parity, Decision #57), so `reports_expiry.access` is grantable; the
 * `withinDays` window is a live-bound prop rendered above the table.
 */
class ExpiryReport extends ReportPage
{
    protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedClock;

    protected static ?string $navigationLabel = 'Expiry Report';

    protected static ?string $slug = 'reports/expiry';

    protected static ?string $title = 'Expiry Report';

    public int $withinDays = 30;

    public static function moduleKey(): string
    {
        return 'reports_expiry';
    }

    public function csvFilename(): string
    {
        return 'expiry-report.csv';
    }

    public function hasDateFilters(): bool
    {
        return false;
    }

    public function extraFilters(): string
    {
        return '<label class="text-sm">
            <span class="mb-1 block text-gray-500">Show expiring within (days)</span>
            <input type="number" min="0" wire:model.live="withinDays"
                   class="fi-input rounded-lg border-gray-300 text-sm dark:bg-gray-800"
                   style="width: 8rem">
        </label>';
    }

    public function columns(): array
    {
        return [
            'product' => 'Product',
            'sku' => 'SKU',
            'internal_batch_number' => 'Batch #',
            'branch' => 'Branch',
            'expiry_date' => 'Expiry Date',
            'days_until_expiry' => 'Days Until Expiry',
            'bucket' => 'Status',
            'available_quantity' => 'Available Qty',
            'reserved_quantity' => 'Reserved Qty',
            'unit' => 'Unit',
            'batch_cost' => 'Batch Cost',
            'value_at_risk' => 'Value At Risk',
        ];
    }

    protected function reportQuery(): Builder
    {
        $branchId = $this->branchFilter();
        $withinDays = $this->withinDays;

        return StockBatch::query()
            ->with([
                'product:id,name,sku,main_category_id,sub_category_id,base_unit_id',
                'product.mainCategory:id,name',
                'product.subCategory:id,name',
                'product.baseUnit:id,code,name',
                'branch:id,name',
                'status:id,code,name,color',
            ])
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereNotNull('expiry_date')
            ->where(fn ($q) => $q->where('available_quantity', '>', 0)->orWhere('reserved_quantity', '>', 0))
            ->whereHas('status', fn ($q) => $q->whereIn('code', [Status::BATCH_ACTIVE, Status::BATCH_EXPIRED]))
            // The source's PHP-side window (bucket 'expired' OR days-until-expiry
            // <= withinDays) as SQL, so pagination happens on the real dataset.
            // (calendar arithmetic on the date columns, driver-agnostic — no
            // DATEDIFF/CURDATE, which SQLite's test driver lacks)
            ->whereDate('expiry_date', '<=', now()->addDays($withinDays))
            ->fefoOrder();
    }

    public function table(Table $table): Table
    {
        return $this->baseTable($table)
            ->columns([
                TextColumn::make('product.name')->label('Product')->searchable(),
                TextColumn::make('product.sku')->label('SKU')->searchable(),
                TextColumn::make('internal_batch_number')->label('Batch #')->searchable()->sortable(),
                TextColumn::make('branch.name')->label('Branch')->placeholder('—'),
                TextColumn::make('expiry_date')->label('Expiry Date')->date()->sortable(),
                TextColumn::make('days_until_expiry')
                    ->label('Days Until Expiry')
                    ->state(fn (StockBatch $record): int => $this->daysUntilExpiry($record)),
                TextColumn::make('bucket')
                    ->label('Status')
                    ->state(fn (StockBatch $record): string => $this->bucketFor($record))
                    ->badge()
                    ->color(fn (StockBatch $record): string => $this->bucketColor($this->bucketFor($record))),
                TextColumn::make('available_quantity')->label('Available Qty')->numeric()->sortable(),
                TextColumn::make('reserved_quantity')->label('Reserved Qty')->numeric(),
                TextColumn::make('unit')->label('Unit')->state(fn (StockBatch $record): string => $record->product?->baseUnit?->code ?? 'EACH'),
                TextColumn::make('batch_cost')->label('Batch Cost')->money(fn () => app(CurrentContext::class)->currencyCode())->placeholder('—'),
                TextColumn::make('value_at_risk')
                    ->label('Value At Risk')
                    ->money(fn () => app(CurrentContext::class)->currencyCode())
                    ->state(fn (StockBatch $record): float => $this->valueAtRisk($record)),
            ]);
    }

    protected function daysUntilExpiry(StockBatch $batch): int
    {
        return (int) now()->diffInDays($batch->expiry_date, false);
    }

    protected function bucketFor(StockBatch $batch): string
    {
        $daysUntil = $this->daysUntilExpiry($batch);

        return match (true) {
            $daysUntil < 0 => 'expired',
            $daysUntil <= 7 => 'critical',
            $daysUntil <= 30 => 'warning',
            $daysUntil <= 90 => 'watch',
            default => 'ok',
        };
    }

    protected function bucketColor(string $bucket): string
    {
        return match ($bucket) {
            'expired' => 'danger',
            'critical' => 'danger',
            'warning' => 'warning',
            'watch' => 'gray',
            default => 'success',
        };
    }

    protected function valueAtRisk(StockBatch $batch): float
    {
        return (float) $batch->available_quantity * (float) ($batch->batch_cost ?? 0);
    }

    protected function exportRow(Model $record): array
    {
        $batch = $record;

        return [
            'product' => $batch->product?->name,
            'sku' => $batch->product?->sku,
            'internal_batch_number' => $batch->internal_batch_number,
            'branch' => $batch->branch?->name,
            'expiry_date' => $batch->expiry_date?->toDateString(),
            'days_until_expiry' => $this->daysUntilExpiry($batch),
            'bucket' => $this->bucketFor($batch),
            'available_quantity' => (float) $batch->available_quantity,
            'reserved_quantity' => (float) $batch->reserved_quantity,
            'unit' => $batch->product?->baseUnit?->code ?? 'EACH',
            'batch_cost' => (float) ($batch->batch_cost ?? 0),
            'value_at_risk' => $this->valueAtRisk($batch),
        ];
    }

    public function summary(): array
    {
        $rows = $this->filteredQuery()->get()->map(fn (StockBatch $b): array => $this->exportRow($b));

        return [
            'Expired' => $rows->where('bucket', 'expired')->count()
                .' (Rs. '.number_format((float) $rows->where('bucket', 'expired')->sum('value_at_risk'), 2).')',
            'Critical (≤7d)' => $rows->where('bucket', 'critical')->count()
                .' (Rs. '.number_format((float) $rows->where('bucket', 'critical')->sum('value_at_risk'), 2).')',
            'Warning (≤30d)' => $rows->where('bucket', 'warning')->count()
                .' (Rs. '.number_format((float) $rows->where('bucket', 'warning')->sum('value_at_risk'), 2).')',
            'Total value at risk' => 'Rs. '.number_format((float) $rows->sum('value_at_risk'), 2),
        ];
    }

    protected function receiptLine(Model $record): array
    {
        $batch = $record;

        return [
            'label' => ($batch->product?->name ?? '—').' ['.$batch->internal_batch_number.'] · '.($batch->expiry_date?->toDateString() ?? '—'),
            'value' => number_format((float) $batch->available_quantity, 2).' @ '.$this->formatAmount($batch->batch_cost ?? 0),
        ];
    }
}
