<?php

namespace App\Filament\Pages\Reports;

use App\Models\Catalog\Product;
use App\Models\Inventory\StockBatch;
use App\Models\Scopes\BranchScope;
use App\Services\BatchService;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

/**
 * Ported from ReportController::inventory(). Stock comes from
 * Product::withStockFor($branchId) — the same branch-aware aggregate the rest
 * of the app uses — and the low/out-of-stock classification uses the same
 * threshold rule (low = 0 < stock <= threshold; out = stock <= 0). The
 * threshold is a live-bound prop rendered above the table (extraFilters),
 * rows are a native Filament table, and search is native (name/sku).
 *
 * Price and valuation come from the receipt lots, not the catalog entry:
 * each row's Sale Price is the range across its sellable lots (one number
 * when they agree), and the Total stock value is Σ(lot.available_quantity ×
 * lot.sale_price) — every consignment valued at the price it was received
 * to sell at, rather than one product-wide number.
 */
class InventoryReport extends ReportPage
{
    protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedArchiveBox;

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

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

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

    public int $threshold = 10;

    /** @var array<int, array{price_min: float, price_max: float, lot_count: int}>|null */
    private ?array $priceRanges = null;

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

    public function csvFilename(): string
    {
        return 'inventory-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">Low stock threshold</span>
            <input type="number" min="0" wire:model.live="threshold"
                   class="fi-input rounded-lg border-gray-300 text-sm dark:bg-gray-800"
                   style="width: 8rem">
        </label>';
    }

    public function columns(): array
    {
        return [
            'name' => 'Name',
            'sku' => 'SKU',
            'category' => 'Category',
            'sub_category' => 'Sub-Category',
            'stock' => 'Stock',
            'unit' => 'Unit',
            'sale_price' => 'Sale Price',
        ];
    }

    protected function reportQuery(): Builder
    {
        return Product::query()
            ->with(['mainCategory:id,name', 'subCategory:id,name', 'baseUnit:id,code,name'])
            ->withStockFor($this->branchFilter())
            ->orderBy('name');
    }

    public function table(Table $table): Table
    {
        return $this->baseTable($table)
            ->columns([
                TextColumn::make('name')->label('Name')->searchable()->sortable(),
                TextColumn::make('sku')->label('SKU')->searchable()->sortable(),
                TextColumn::make('mainCategory.name')->label('Category')->placeholder('—'),
                TextColumn::make('subCategory.name')->label('Sub-Category')->placeholder('—'),
                TextColumn::make('stock')->label('Stock')
                    ->numeric()
                    ->sortable()
                    ->color(fn (Product $record): ?string => $this->stockColor((float) ($record->stock ?? 0))),
                TextColumn::make('baseUnit.code')->label('Unit')->placeholder('EACH'),
                TextColumn::make('price')
                    ->label('Sale Price')
                    ->placeholder('—')
                    ->state(fn (Product $record): ?string => $this->priceLabel((int) $record->id)),
            ]);
    }

    protected function stockColor(float $stock): ?string
    {
        return match (true) {
            $stock <= 0 => 'danger',
            $stock <= $this->threshold => 'warning',
            default => null,
        };
    }

    /**
     * The row's price range across its sellable lots at the report branch,
     * loaded once per render (one grouped query), not once per row.
     */
    private function priceRangeFor(int $productId): ?array
    {
        if ($this->priceRanges === null) {
            $ids = $this->filteredQuery()->pluck('products.id')->map(fn ($id) => (int) $id)->all();
            $this->priceRanges = app(BatchService::class)->priceRangesFor($ids, $this->branchFilter());
        }

        return $this->priceRanges[$productId] ?? null;
    }

    private function priceLabel(int $productId): ?string
    {
        $range = $this->priceRangeFor($productId);
        if (! $range) {
            return null;
        }

        $min = (float) $range['price_min'];
        $max = (float) $range['price_max'];

        return $min === $max ? number_format($min, 2) : number_format($min, 2).' – '.number_format($max, 2);
    }

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

        return [
            'name' => $product->name,
            'sku' => $product->sku,
            'category' => $product->mainCategory?->name,
            'sub_category' => $product->subCategory?->name,
            'stock' => (float) ($product->stock ?? 0),
            'unit' => $product->baseUnit?->code ?? 'EACH',
            'sale_price' => $this->priceLabel((int) $product->id) ?? '',
        ];
    }

    public function summary(): array
    {
        $items = $this->filteredQuery()->get()->map(function (Product $p): array {
            $stock = (float) ($p->stock ?? 0);

            return [
                'stock' => $stock,
                'is_low' => $stock <= $this->threshold && $stock > 0,
                'is_out' => $stock <= 0,
            ];
        });

        $ids = $this->filteredQuery()->pluck('products.id')->map(fn ($id) => (int) $id)->all();
        $branchId = $this->branchFilter();

        // Value every consignment at its own lot price, on the same branch
        // semantics as the stock column (explicit branch = that branch only,
        // null = accessible branches via BranchScope).
        $valuation = $ids === []
            ? 0.0
            : (float) StockBatch::query()
                ->whereIn('product_id', $ids)
                ->when($branchId !== null, fn ($q) => $q
                    ->withoutGlobalScope(BranchScope::class)
                    ->where('branch_id', $branchId))
                ->active()
                ->where('available_quantity', '>', 0)
                ->whereNotNull('sale_price')
                ->sum(DB::raw('available_quantity * sale_price'));

        return [
            'Total products' => (string) $items->count(),
            'Total stock value' => 'Rs. '.number_format($valuation, 2),
            'Low stock' => (string) $items->where('is_low', true)->count(),
            'Out of stock' => (string) $items->where('is_out', true)->count(),
        ];
    }

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

        return [
            'label' => $product->name.($product->sku ? ' ['.$product->sku.']' : ''),
            'value' => ($product->baseUnit?->code ?? 'EACH').' '.number_format((float) ($product->stock ?? 0), 2),
        ];
    }
}
