<?php

declare(strict_types=1);

namespace App\Filament\Pages;

use App\Business\DataImport\DataImportFileReader;
use App\Business\DataImport\DataImportHandlerRegistry;
use App\Business\DataImport\DataImportOrchestrator;
use App\Business\DataImport\DataImportTemplateDownloadService;
use App\Business\DataImport\Enums\DataImportBatchStatus;
use App\Business\DataImport\Enums\DataImportType;
use App\Filament\Concerns\InteractsWithDberpPermissions;
use App\Filament\Resources\DberpDataImportBatches\DberpDataImportBatchResource;
use App\Models\DberpDataImportBatch;
use App\Services\DberpPermissionService;
use BackedEnum;
use Closure;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Form;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Auth\Access\Response;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use UnitEnum;

/**
 * @property-read Schema $form
 */
final class DataInitializationImportPage extends Page implements HasTable
{
    use InteractsWithDberpPermissions;
    use Tables\Concerns\InteractsWithTable {
        makeTable as makeBaseTable;
    }

    protected static ?string $slug = 'data-initialization-import';

    protected static ?int $navigationSort = 30;

    protected string $view = 'filament.pages.data-initialization-import-page';

    /**
     * @var array<string, mixed>|null
     */
    public ?array $data = [];

    public ?int $precheckRowCount = null;

    public ?string $precheckFilename = null;

    public ?string $precheckImportType = null;

    public ?int $precheckBatchId = null;

    public ?string $precheckBatchStatus = null;

    public ?int $precheckValidatedRowCount = null;

    public ?int $precheckFailedRowCount = null;

    /**
     * @var list<string>
     */
    public array $precheckErrorMessages = [];

    public static function getNavigationGroup(): string|UnitEnum|null
    {
        return __('dberp.nav.system');
    }

    public static function getNavigationLabel(): string
    {
        return __('dberp.labels.data_initialization_import');
    }

    public static function getNavigationIcon(): string|BackedEnum|Htmlable|null
    {
        return null;
    }

    public static function canAccess(): bool
    {
        return self::canViewImportWorkflow() || self::canViewBatchHistory();
    }

    public static function canViewImportWorkflow(): bool
    {
        return self::allowsPermission(DberpPermissionService::VIEW_DATA_INITIALIZATION_IMPORT);
    }

    public static function canViewBatchHistory(): bool
    {
        return self::allowsPermission(DberpPermissionService::VIEW_DATA_IMPORT_BATCH);
    }

    public function getTitle(): string|Htmlable
    {
        return __('dberp.data_import.page_title');
    }

    public function getSubheading(): string|Htmlable|null
    {
        return __('dberp.data_import.page_intro');
    }

    public function mount(): void
    {
        if (self::canViewImportWorkflow()) {
            $this->form->fill([
                'import_type' => DataImportType::Unit->value,
                'upload_file' => [],
            ]);
        }

        if (self::canViewBatchHistory()) {
            $this->mountInteractsWithTable();
        }
    }

    public function form(Schema $schema): Schema
    {
        if (! self::canViewImportWorkflow()) {
            return $schema->components([]);
        }

        $maxUploadKilobytes = max(512, (int) config('dberp.data_import.max_upload_kilobytes', 10_240));

        return $schema
            ->components([
                Form::make([
                    Section::make(__('dberp.data_import.sections.template_and_precheck'))
                        ->description(__('dberp.data_import.template_precheck_intro'))
                        ->schema([
                            Select::make('import_type')
                                ->label(__('dberp.fields.import_type'))
                                ->options(DataImportType::supportedOptions())
                                ->required()
                                ->native(false)
                                ->live(),
                            FileUpload::make('upload_file')
                                ->label(__('dberp.fields.import_upload_file'))
                                ->helperText(__('dberp.data_import.upload_file_hint', [
                                    'max_mb' => number_format($maxUploadKilobytes / 1024, 1, '.', ''),
                                    'max_rows' => (int) config('dberp.data_import.max_rows', 5000),
                                ]))
                                ->disk('local')
                                ->directory((string) config('dberp.data_import.precheck_upload_directory', 'dberp/import-precheck/temp'))
                                ->visibility('private')
                                ->acceptedFileTypes([
                                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                                    'text/csv',
                                    'text/plain',
                                    'application/csv',
                                ])
                                ->maxSize($maxUploadKilobytes)
                                ->maxFiles(1)
                                ->visible(fn (): bool => self::canExecuteImport()),
                        ])
                        ->footer([
                            Actions::make([
                                Action::make('downloadTemplate')
                                    ->label(__('dberp.data_import.actions.download_template'))
                                    ->icon('heroicon-o-arrow-down-tray')
                                    ->action(fn (): BinaryFileResponse => $this->downloadTemplate())
                                    ->visible(fn (): bool => self::canDownloadTemplate()),
                                Action::make('precheckFile')
                                    ->label(__('dberp.data_import.actions.precheck_file'))
                                    ->icon('heroicon-o-document-magnifying-glass')
                                    ->action(fn (): mixed => $this->precheckFile())
                                    ->visible(fn (): bool => self::canExecuteImport()),
                                Action::make('executeImport')
                                    ->label(__('dberp.data_import.actions.execute_import'))
                                    ->icon('heroicon-o-arrow-up-tray')
                                    ->color('success')
                                    ->requiresConfirmation()
                                    ->modalHeading(__('dberp.data_import.execute_confirm_heading'))
                                    ->modalDescription(__('dberp.data_import.execute_confirm_body'))
                                    ->action(fn (): mixed => $this->executeImport())
                                    ->visible(fn (): bool => self::canExecuteImport() && $this->canExecuteCurrentBatch()),
                            ]),
                        ]),
                ])
                    ->livewireSubmitHandler(null),
            ])
            ->statePath('data');
    }

    public function table(Table $table): Table
    {
        return $table;
    }

    protected function makeTable(): Table
    {
        $table = $this->makeBaseTable()
            ->query(fn (): Builder => DberpDataImportBatch::query()->with('operator'));

        DberpDataImportBatchResource::configureTable($table);

        return $table->recordUrl(null);
    }

    public function infolist(Schema $schema): Schema
    {
        return DberpDataImportBatchResource::infolist($schema);
    }

    public function getDefaultActionSchemaResolver(Action $action): ?Closure
    {
        return match (true) {
            $action instanceof ViewAction => fn (Schema $schema): Schema => $this->infolist($schema->columns(2)),
            default => null,
        };
    }

    protected function getMountedActionSchemaModel(): Model|string|null
    {
        return DberpDataImportBatch::class;
    }

    public function getDefaultActionModel(Action $action): ?string
    {
        return $action instanceof ViewAction ? DberpDataImportBatch::class : null;
    }

    public function getDefaultActionAuthorizationResponse(Action $action): ?Response
    {
        return match (true) {
            $action instanceof ViewAction => DberpDataImportBatchResource::getViewAuthorizationResponse($action->getRecord()),
            $action instanceof DeleteAction => DberpDataImportBatchResource::getDeleteAuthorizationResponse($action->getRecord()),
            default => null,
        };
    }

    public function handleImportBatchDeleted(int $batchId): void
    {
        if ($this->precheckBatchId === $batchId) {
            $this->resetPrecheckSummary();
        }

        $this->refreshBatchHistoryTable();
    }

    public function downloadTemplate(): BinaryFileResponse
    {
        $importTypeValue = (string) ($this->importWorkflowFormState()['import_type'] ?? '');

        if ($importTypeValue === '') {
            throw ValidationException::withMessages([
                'data.import_type' => (string) __('dberp.data_import.errors.import_type_required'),
            ]);
        }

        return app(DataImportTemplateDownloadService::class)->download(
            DataImportType::from($importTypeValue),
        );
    }

    public function precheckFile(): void
    {
        $this->resetPrecheckSummary();

        $state = $this->importWorkflowFormState();
        $importTypeValue = (string) ($state['import_type'] ?? '');
        $uploadFile = $state['upload_file'] ?? null;

        if ($importTypeValue === '') {
            throw ValidationException::withMessages([
                'data.import_type' => (string) __('dberp.data_import.errors.import_type_required'),
            ]);
        }

        $resolvedUpload = $this->resolveUploadedFile($uploadFile);

        if ($resolvedUpload === null) {
            throw ValidationException::withMessages([
                'data.upload_file' => (string) __('dberp.data_import.errors.upload_file_required'),
            ]);
        }

        $importType = DataImportType::from($importTypeValue);
        $absolutePath = $resolvedUpload['absolute_path'];
        $storedPath = $resolvedUpload['stored_path'];
        $sourceFilename = $resolvedUpload['source_filename'];

        try {
            if (app(DataImportHandlerRegistry::class)->supports($importType)) {
                $batch = app(DataImportOrchestrator::class)->precheck(
                    importType: $importType,
                    absolutePath: $absolutePath,
                    sourceFilename: $sourceFilename,
                    operatorUserId: (int) auth()->id(),
                );

                $this->applyBatchPrecheckSummary($batch);
                $this->refreshBatchHistoryTable();

                if ($batch->status === DataImportBatchStatus::Prechecked) {
                    Notification::make()
                        ->title(__('dberp.data_import.precheck_passed_title'))
                        ->body((string) __('dberp.data_import.precheck_passed_body', [
                            'validated' => (int) $batch->success_rows,
                            'total' => (int) $batch->total_rows,
                        ]))
                        ->success()
                        ->send();

                    return;
                }

                Notification::make()
                    ->title(__('dberp.data_import.precheck_failed_title'))
                    ->body((string) __('dberp.data_import.precheck_failed_body', [
                        'failed' => (int) $batch->failed_rows,
                        'total' => (int) $batch->total_rows,
                    ]))
                    ->danger()
                    ->send();

                return;
            }

            $result = app(DataImportFileReader::class)->read(
                importType: $importType,
                absolutePath: $absolutePath,
                sourceFilename: $sourceFilename,
            );

            $this->precheckRowCount = $result->totalRows;
            $this->precheckFilename = $result->sourceFilename;
            $this->precheckImportType = $result->importType->label();

            Notification::make()
                ->title(__('dberp.data_import.precheck_file_only_title'))
                ->body((string) __('dberp.data_import.precheck_file_only_body', [
                    'rows' => $result->totalRows,
                ]))
                ->success()
                ->send();
        } finally {
            if ($storedPath !== null) {
                Storage::disk('local')->delete($storedPath);
            }

            $this->form->fill([
                'import_type' => $importTypeValue !== '' ? $importTypeValue : ($state['import_type'] ?? DataImportType::Unit->value),
                'upload_file' => [],
            ]);
        }
    }

    public function executeImport(): void
    {
        if ($this->precheckBatchId === null) {
            throw ValidationException::withMessages([
                'batch' => (string) __('dberp.data_import.errors.batch_not_ready_for_execution'),
            ]);
        }

        $batch = DberpDataImportBatch::query()->find($this->precheckBatchId);

        if ($batch === null) {
            throw ValidationException::withMessages([
                'batch' => (string) __('dberp.data_import.errors.batch_not_found'),
            ]);
        }

        $executedBatch = app(DataImportOrchestrator::class)->execute(
            batch: $batch,
            operatorUserId: (int) auth()->id(),
        );

        $this->applyBatchPrecheckSummary($executedBatch);
        $this->refreshBatchHistoryTable();

        if ($executedBatch->status === DataImportBatchStatus::Completed) {
            Notification::make()
                ->title(__('dberp.data_import.execute_success_title'))
                ->body((string) __('dberp.data_import.execute_success_body', [
                    'imported' => (int) $executedBatch->success_rows,
                ]))
                ->success()
                ->send();

            return;
        }

        Notification::make()
            ->title(__('dberp.data_import.execute_failed_title'))
            ->body((string) __('dberp.data_import.execute_failed_body', [
                'failed' => (int) $executedBatch->failed_rows,
            ]))
            ->danger()
            ->send();
    }

    public function canExecuteCurrentBatch(): bool
    {
        return $this->precheckBatchId !== null
            && $this->precheckBatchStatus === DataImportBatchStatus::Prechecked->value;
    }

    public static function canDownloadTemplate(): bool
    {
        return self::allowsPermission(DberpPermissionService::VIEW_DATA_INITIALIZATION_IMPORT);
    }

    public static function canExecuteImport(): bool
    {
        return self::allowsPermission(DberpPermissionService::EXECUTE_DATA_INITIALIZATION_IMPORT);
    }

    private function resetPrecheckSummary(): void
    {
        $this->precheckRowCount = null;
        $this->precheckFilename = null;
        $this->precheckImportType = null;
        $this->precheckBatchId = null;
        $this->precheckBatchStatus = null;
        $this->precheckValidatedRowCount = null;
        $this->precheckFailedRowCount = null;
        $this->precheckErrorMessages = [];
    }

    private function applyBatchPrecheckSummary(DberpDataImportBatch $batch): void
    {
        $batch->loadMissing('rows');

        $this->precheckBatchId = (int) $batch->getKey();
        $this->precheckBatchStatus = $batch->status->value;
        $this->precheckRowCount = (int) $batch->total_rows;
        $this->precheckFilename = (string) ($batch->source_filename ?? '');
        $this->precheckImportType = $batch->import_type->label();
        $this->precheckValidatedRowCount = (int) $batch->success_rows;
        $this->precheckFailedRowCount = (int) $batch->failed_rows;

        $errors = is_array($batch->error_summary['messages'] ?? null)
            ? $batch->error_summary['messages']
            : [];

        if ($errors === []) {
            foreach ($batch->rows as $row) {
                if (! is_array($row->error_messages)) {
                    continue;
                }

                foreach ($row->error_messages as $message) {
                    $errors[] = (string) $message;

                    if (count($errors) >= 20) {
                        break 2;
                    }
                }
            }
        }

        $this->precheckErrorMessages = $errors;
    }

    private function refreshBatchHistoryTable(): void
    {
        if (! self::canViewBatchHistory()) {
            return;
        }

        $this->resetTable();
    }

    /**
     * @return array<string, mixed>
     */
    private function importWorkflowFormState(): array
    {
        $state = $this->form->getState();

        return is_array($state) ? $state : [];
    }

    /**
     * @return array{absolute_path: string, stored_path: ?string, source_filename: string}|null
     */
    private function resolveUploadedFile(mixed $uploadFile): ?array
    {
        if (is_array($uploadFile)) {
            foreach ($uploadFile as $path) {
                if ($path instanceof TemporaryUploadedFile) {
                    $uploadFile = $path;

                    break;
                }
            }
        }

        if ($uploadFile instanceof TemporaryUploadedFile) {
            $absolutePath = $uploadFile->getRealPath();

            if ($absolutePath === false || $absolutePath === '') {
                return null;
            }

            return [
                'absolute_path' => $absolutePath,
                'stored_path' => null,
                'source_filename' => $uploadFile->getClientOriginalName(),
            ];
        }

        $storedPath = $this->resolveStoredUploadPath($uploadFile);

        if ($storedPath === null) {
            return null;
        }

        return [
            'absolute_path' => Storage::disk('local')->path($storedPath),
            'stored_path' => $storedPath,
            'source_filename' => basename($storedPath),
        ];
    }

    private function resolveStoredUploadPath(mixed $uploadFile): ?string
    {
        if (is_string($uploadFile) && $uploadFile !== '') {
            return $uploadFile;
        }

        if (! is_array($uploadFile)) {
            return null;
        }

        foreach ($uploadFile as $path) {
            if ($path instanceof TemporaryUploadedFile) {
                continue;
            }

            if (is_string($path) && $path !== '') {
                return $path;
            }
        }

        return null;
    }

    /**
     * @return array<int|string, string|Htmlable>
     */
    public function getBreadcrumbs(): array
    {
        return [
            __('dberp.nav.system'),
            __('dberp.nav.data_management'),
            $this->getTitle(),
        ];
    }

    /**
     * @return list<array{value: string, label: string}>
     */
    public function firstStageImportTypes(): array
    {
        $types = [];

        foreach (DataImportType::firstStageTypes() as $type) {
            $types[] = [
                'value' => $type->value,
                'label' => $type->label(),
            ];
        }

        return $types;
    }
}
