<?php

namespace App\Filament\Pages\Reports;

use App\Filament\Support\ReportDateRange;
use App\Filament\Support\ThermalReceipt;
use App\Http\Controllers\Concerns\ResolvesBranchFilter;
use App\Services\CurrentContext;
use Barryvdh\DomPDF\Facade\Pdf;
use Filament\Actions\Action;
use Filament\Pages\Page;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\BaseFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use LogicException;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * Shared scaffolding for every ported report screen (docs/migration-plan.md
 * Phase 9, from App\Http\Controllers\ReportController).
 *
 * Each subclass declares its permission module key, its base query
 * (reportQuery(), with branch scoping and any report-specific prop filters),
 * the native table columns, and the CSV/PDF/receipt row mappings. This base
 * supplies:
 *
 *  - a Native Filament Data Table (server-side pagination — the 200/500-row
 *    caps the source controller used are gone, the page shows a slice and
 *    exports/summaries still cover the whole filtered dataset);
 *  - the smart Date Range Preset filter (Today by default, From/To only for
 *    Custom) via {@see ReportDateRange};
 *  - filter-aware exports — the CSV stream, the PDF document and the 80mm
 *    thermal receipt are all built from the table's CURRENT filtered/sorted
 *    query, never from an unfiltered snapshot;
 *  - the branch resolution (via the source's own ResolvesBranchFilter trait).
 *
 * Every export is gated by `{module}.export` (canExport()) — on the action's
 * visibility AND inside the handler, matching the source's
 * abort_unless($user->hasPermissionTo('reports_x.export'), 403) check.
 */
abstract class ReportPage extends Page implements HasTable
{
    use InteractsWithTable;
    use ResolvesBranchFilter;

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

    protected string $view = 'filament.pages.reports.report';

    /** Branch filter state, bound to the view's selector. */
    public ?string $branchId = null;

    /** The permission module key, e.g. `reports_sales`. */
    abstract public static function moduleKey(): string;

    /**
     * The report's base query — dates, branch and report-specific prop
     * filters, but NO table search/filter state (that is layered on by
     * Filament when the table resolves rows and exports).
     */
    abstract protected function reportQuery(): Builder;

    /**
     * Column definitions: ['key' => 'Label', ...]. Drives the CSV header row
     * and the PDF column headings.
     *
     * @return array<string, string>
     */
    abstract public function columns(): array;

    /**
     * A record shaped for CSV/PDF: values keyed by {@see columns()}.
     *
     * @return array<string, mixed>
     */
    abstract protected function exportRow(Model $record): array;

    /**
     * Headline figures rendered above the table: ['Label' => 'value', ...].
     * Computed from the FULL filtered dataset (not the current page), so the
     * cards stay consistent with what exports contain.
     *
     * @return array<string, string>
     */
    abstract public function summary(): array;

    /**
     * One line of the 80mm thermal receipt per record.
     *
     * @return array{label: string, value: string}
     */
    abstract protected function receiptLine(Model $record): array;

    /** The CSV filename, matching the source's. */
    abstract public function csvFilename(): string;

    /** Whether this report offers the date range preset filter (most do). */
    public function hasDateFilters(): bool
    {
        return true;
    }

    /** Optional extra filter controls, rendered above the table. */
    public function extraFilters(): string
    {
        return '';
    }

    public function mount(): void
    {
        $this->mountInteractsWithTable();
    }

    public static function canAccess(): bool
    {
        return Auth::user()?->hasPermissionTo(static::moduleKey().'.access') ?? false;
    }

    public static function shouldRegisterNavigation(): bool
    {
        return static::canAccess();
    }

    public function canExport(): bool
    {
        return Auth::user()?->hasPermissionTo(static::moduleKey().'.export') ?? false;
    }

    public function table(Table $table): Table
    {
        return $this->baseTable($table);
    }

    /**
     * The shared table scaffolding: query, date range filter, server-side
     * pagination and the three filter-aware export actions.
     */
    protected function baseTable(Table $table): Table
    {
        return $table
            ->query(fn (): Builder => $this->reportQuery())
            ->filters($this->tableFilters())
            ->filtersLayout(FiltersLayout::Modal)
            ->filtersFormColumns(2)
            ->paginated([10, 25, 50, 100, 'all'])
            ->defaultPaginationPageOption(25)
            ->striped()
            ->headerActions([
                $this->csvExportAction(),
                $this->pdfExportAction(),
                $this->receiptAction(),
            ]);
    }

    /**
     * Table filters for this report. The date range preset is included for
     * every report that has one; subclasses append their own.
     *
     * @return array<BaseFilter>
     */
    protected function tableFilters(): array
    {
        return $this->hasDateFilters() ? [ReportDateRange::filter()] : [];
    }

    /**
     * The FULL filtered+sorted query behind the table — the single source of
     * truth for exports, receipts and summaries, so everything the user
     * downloads matches exactly what the table is showing (date range, search
     * and status filters included).
     */
    public function filteredQuery(): Builder
    {
        $query = $this->getFilteredSortedTableQuery();

        if (! $query) {
            throw new LogicException('['.static::class.'] must define a table query before filtered rows can be exported.');
        }

        return $query;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function rows(): array
    {
        return $this->filteredQuery()
            ->get()
            ->map(fn (Model $record): array => $this->exportRow($record))
            ->all();
    }

    public function exportCsv(): StreamedResponse
    {
        abort_unless($this->canExport(), 403);

        $columns = $this->columns();
        $rows = $this->rows();

        return response()->streamDownload(function () use ($columns, $rows): void {
            $out = fopen('php://output', 'w');
            fputcsv($out, array_values($columns));
            foreach ($rows as $row) {
                fputcsv($out, array_map(
                    fn (string $key) => $row[$key] ?? '',
                    array_keys($columns),
                ));
            }
            fclose($out);
        }, $this->csvFilename(), ['Content-Type' => 'text/csv; charset=UTF-8']);
    }

    public function exportPdf(): StreamedResponse
    {
        abort_unless($this->canExport(), 403);

        $columns = $this->columns();
        $rows = $this->filteredQuery()
            ->get()
            ->map(fn (Model $record): array => $this->exportRow($record))
            ->all();

        $html = view('filament.exports.report-pdf', [
            'title' => static::getNavigationLabel(),
            'headings' => array_values($columns),
            'rows' => $rows,
            'summary' => $this->summary(),
            'period' => $this->dateRangeDescription(),
            'generatedAt' => now()->format('d M Y, H:i'),
        ])->render();

        $pdf = Pdf::loadHTML($html)->setPaper('a4', 'landscape');

        return response()->streamDownload(
            fn () => print ($pdf->output()),
            $this->exportFileName('pdf'),
            ['Content-Type' => 'application/pdf'],
        );
    }

    public function exportFileName(string $extension): string
    {
        return Str::slug(static::getNavigationLabel()).'-'.now()->format('Y-m-d-His').'.'.$extension;
    }

    /**
     * "Period" description for PDF headers and receipt printouts.
     */
    public function dateRangeDescription(): string
    {
        if (! $this->hasDateFilters()) {
            return 'All records';
        }

        return ReportDateRange::describe($this->getTableFilterState('date') ?? []);
    }

    protected function receiptAction(): Action
    {
        return ThermalReceipt::action(
            'printReceipt',
            fn (): array => $this->receiptPayload(),
            fn (): bool => $this->canExport(),
        );
    }

    protected function csvExportAction(): Action
    {
        return Action::make('exportCsv')
            ->label('Export CSV')
            ->icon('heroicon-m-document-arrow-down')
            ->color('gray')
            ->authorize(fn (): bool => $this->canExport())
            ->action(fn (): StreamedResponse => $this->exportCsv());
    }

    protected function pdfExportAction(): Action
    {
        return Action::make('exportPdf')
            ->label('Export PDF')
            ->icon('heroicon-m-document-arrow-down')
            ->color('gray')
            ->authorize(fn (): bool => $this->canExport())
            ->action(fn (): StreamedResponse => $this->exportPdf());
    }

    /**
     * @return array<string, mixed>
     */
    protected function receiptPayload(): array
    {
        $rows = $this->filteredQuery()
            ->get()
            ->map(fn (Model $record): array => $this->receiptLine($record))
            ->all();

        return [
            'title' => static::getNavigationLabel(),
            'meta' => 'Period: '.$this->dateRangeDescription(),
            'rows' => $rows,
            'totals' => $this->receiptTotals(),
            'footer' => 'Generated '.now()->format('d M Y, H:i'),
        ];
    }

    /**
     * Optional grand-total lines under the receipt rows. Computed from
     * {@see filteredQuery()} so it always reflects the filtered dataset.
     *
     * @return array<int, array{label: string, value: string}>
     */
    protected function receiptTotals(): array
    {
        return [];
    }

    protected function formatAmount(float|int|string|null $amount): string
    {
        return app(CurrentContext::class)->currencySymbol().' '.number_format((float) $amount, 2);
    }

    /**
     * Resolve the branch for this report from the page's own filter state,
     * reusing the source trait's rules (a concrete branch only when the user
     * can access it; anything else falls back to the All aggregate, which
     * BranchScope still limits to accessible branches).
     */
    protected function branchFilter(): ?int
    {
        // The trait reads `?branch_id` from the QUERY string specifically, so a
        // synthetic GET request carries the page's filter state into it rather
        // than merge()ing onto the live request (which lands in input, not
        // query, and would be silently ignored).
        return $this->resolveBranchFilter(
            request()->create('/', 'GET', ['branch_id' => $this->branchId]),
        );
    }

    /**
     * @return array<string, mixed>
     */
    protected function getViewData(): array
    {
        return [
            'reportSummary' => $this->summary(),
            'branchOptions' => $this->branchViewProps($this->branchFilter()),
        ];
    }
}
