<?php

namespace App\Filament\Tables\Actions;

use App\Rules\DuplicateImportColumnsRule;
use App\Support\ExcelImportHelper;
use Filament\Actions\Imports\ImportColumn;
use Filament\Actions\Imports\Jobs\ImportCsv;
use Filament\Actions\Imports\Models\Import;
use Filament\Forms;
use Filament\Notifications\Actions\Action as NotificationAction;
use Filament\Notifications\Notification;
use Filament\Support\ChunkIterator;
use Filament\Tables\Actions\ImportAction as BaseImportAction;
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Number;
use Illuminate\Validation\ValidationException;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

/**
 * Import action that accepts CSV and Excel (xlsx, xls) and shows column mapping
 * so you can connect your file columns to system fields without changing the file.
 */
class ImportTestsAction extends BaseImportAction
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->form($this->makeForm());
        $this->action($this->makeAction());
    }

    protected function makeForm(): \Closure
    {
        return function (): array {
            $action = $this;
            return array_merge([
                Forms\Components\FileUpload::make('file')
                    ->label(__('filament-actions::import.modal.form.file.label'))
                    ->placeholder('Upload CSV or Excel (.xlsx, .xls)')
                    ->acceptedFileTypes([
                        'text/csv', 'text/x-csv', 'application/csv', 'application/x-csv',
                        'text/comma-separated-values', 'text/plain',
                        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                        'application/vnd.ms-excel',
                    ])
                    ->rules($this->getFileValidationRules())
                    ->afterStateUpdated(function (Forms\Components\FileUpload $component, $livewire, Forms\Set $set, ?TemporaryUploadedFile $state) use ($action) {
                        if (! $state instanceof TemporaryUploadedFile) {
                            return;
                        }
                        try {
                            $livewire->validateOnly($component->getStatePath());
                        } catch (ValidationException $e) {
                            $component->state([]);
                            throw $e;
                        }
                        $path = $state->getRealPath();
                        [$headers, ] = ExcelImportHelper::readHeadersAndRows($path, $action->getHeaderOffset() ?? 0);
                        if (empty($headers)) {
                            return;
                        }
                        $lower = array_map(fn ($h) => strtolower($h), $headers);
                        $lowerToOriginal = array_combine($lower, $headers);
                        $set('columnMap', array_reduce($action->getImporter()::getColumns(), function (array $carry, ImportColumn $column) use ($lowerToOriginal, $lower) {
                            $guesses = $column->getGuesses();
                            $carry[$column->getName()] = $lowerToOriginal[Arr::first(array_intersect($lower, $guesses)) ?? ''] ?? null;
                            return $carry;
                        }, []));
                    })
                    ->storeFiles(false)
                    ->visibility('private')
                    ->required()
                    ->helperText('CSV or Excel. Map your columns to system fields below so the file does not need to match the sample exactly.')
                    ->hiddenLabel(),

                Forms\Components\Fieldset::make(__('filament-actions::import.modal.form.columns.label'))
                    ->columns(1)
                    ->inlineLabel()
                    ->schema(function (Forms\Get $get) use ($action): array {
                        $file = Arr::first((array) ($get('file') ?? []));
                        if (! $file instanceof TemporaryUploadedFile) {
                            return [];
                        }
                        $path = $file->getRealPath();
                        [$headers, ] = ExcelImportHelper::readHeadersAndRows($path, $action->getHeaderOffset() ?? 0);
                        if (empty($headers)) {
                            return [];
                        }
                        $options = array_combine($headers, $headers);
                        return array_map(
                            fn (ImportColumn $column): Forms\Components\Select => $column->getSelect()->options($options),
                            $action->getImporter()::getColumns(),
                        );
                    })
                    ->statePath('columnMap')
                    ->visible(fn (Forms\Get $get): bool => Arr::first((array) ($get('file') ?? [])) instanceof TemporaryUploadedFile),
            ], $this->getImporter()::getOptionsFormComponents());
        };
    }

    protected function makeAction(): \Closure
    {
        return function (array $data): void {
            /** @var TemporaryUploadedFile $file */
            $file = $data['file'];
            $path = $file->getRealPath();
            [$headers, $dataRows] = ExcelImportHelper::readHeadersAndRows($path, $this->getHeaderOffset() ?? 0);

            $totalRows = count($dataRows);
            $maxRows = $this->getMaxRows() ?? $totalRows;
            if ($maxRows < $totalRows) {
                Notification::make()
                    ->title(__('filament-actions::import.notifications.max_rows.title'))
                    ->body(trans_choice('filament-actions::import.notifications.max_rows.body', $maxRows, ['count' => Number::format($maxRows)]))
                    ->danger()
                    ->send();
                return;
            }

            $import = app(Import::class);
            $import->user()->associate(auth()->user());
            $import->file_name = $file->getClientOriginalName();
            $import->file_path = $path;
            $import->importer = $this->getImporter();
            $import->total_rows = $totalRows;
            $import->save();
            $import->unsetRelation('user');

            $options = array_merge($this->getOptions(), Arr::except($data, ['file', 'columnMap']));
            $columnMap = $data['columnMap'];
            $chunks = iterator_to_array((new ChunkIterator(new \ArrayIterator($dataRows), $this->getChunkSize()))->get());
            $importer = $import->getImporter(columnMap: $columnMap, options: $options);

            $jobs = collect($chunks)->map(fn (array $chunk): object => app(ImportCsv::class, [
                'import' => $import,
                'rows' => base64_encode(serialize($chunk)),
                'columnMap' => $columnMap,
                'options' => $options,
            ]))->all();

            event(new \Filament\Actions\Imports\Events\ImportStarted($import, $columnMap, $options));

            // Run import on sync so it executes immediately (no queue worker needed)
            $connection = $importer->getJobConnection() ?: 'sync';
            $queue = $importer->getJobQueue() ?: 'default';

            Bus::batch($jobs)
                ->allowFailures()
                ->onConnection($connection)
                ->onQueue($queue)
                ->when(filled($importer->getJobBatchName()), fn (PendingBatch $b) => $b->name($importer->getJobBatchName()))
                ->finally(function () use ($columnMap, $import, $options) {
                    $import->touch('completed_at');
                    event(new \Filament\Actions\Imports\Events\ImportCompleted($import, $columnMap, $options));
                    if ($import->user) {
                        $failedCount = $import->getFailedRowsCount();
                        Notification::make()
                            ->title($import->importer::getCompletedNotificationTitle($import))
                            ->body($import->importer::getCompletedNotificationBody($import))
                            ->when(! $failedCount, fn (Notification $n) => $n->success())
                            ->when($failedCount && $failedCount < $import->total_rows, fn (Notification $n) => $n->warning())
                            ->when($failedCount === $import->total_rows, fn (Notification $n) => $n->danger())
                            ->when($failedCount, fn (Notification $n) => $n->actions([
                                NotificationAction::make('downloadFailedRowsCsv')
                                    ->label(trans_choice('filament-actions::import.notifications.completed.actions.download_failed_rows_csv.label', $failedCount, ['count' => Number::format($failedCount)]))
                                    ->color('danger')
                                    ->url(route('filament.imports.failed-rows.download', ['import' => $import], absolute: false), shouldOpenInNewTab: true)
                                    ->markAsRead(),
                            ]))
                            ->sendToDatabase($import->user, isEventDispatched: true);
                    }
                })
                ->dispatch();

            if (config('queue.default') !== 'sync') {
                Notification::make()
                    ->title($this->getSuccessNotificationTitle())
                    ->body(trans_choice('filament-actions::import.notifications.started.body', $import->total_rows, ['count' => Number::format($import->total_rows)]))
                    ->success()
                    ->send();
            }
        };
    }

    public function getFileValidationRules(): array
    {
        return [
            'extensions:csv,txt,xlsx,xls',
            new DuplicateImportColumnsRule($this->getHeaderOffset() ?? 0),
        ];
    }
}
