<?php

declare(strict_types=1);

namespace Maestro\Core\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Maestro\Core\DataTables\DataTableQueryApplier;
use Maestro\Core\DataTables\DataTableRegistry;
use Maestro\Core\Events\DataTableExportReady;
use Maestro\Core\Models\DataTableExport;
use Maestro\Core\Support\AuthUserResolver;

use function activity;

/**
 * Queued CSV build for a DataTable export. Use queue name <code>exports</code>.
 */
final class ExportDataTableJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private readonly string $exportId
    ) {}

    public function displayName(): string
    {
        return 'DataTable file export';
    }

    public function handle(DataTableRegistry $registry, DataTableQueryApplier $applier): void
    {
        $export = DataTableExport::query()->find($this->exportId);
        if (! $export) {
            return;
        }

        if ($export->status !== DataTableExport::STATUS_PENDING && $export->status !== DataTableExport::STATUS_PROCESSING) {
            return;
        }

        $export->update(['status' => DataTableExport::STATUS_PROCESSING]);

        try {
            $resource = $registry->resolve($export->resource_key);
            $payload = $export->payload;
            $userClass = AuthUserResolver::eloquentModelClass();
            $user = $userClass::query()->find($export->user_id);
            $request = new Request;
            $request->setUserResolver(fn () => $user);
            if (is_array($payload)) {
                $request->merge($payload);
            }

            $query = $resource->baseQuery($request);
            $columns = $resource->columns();

            $exportColumns = $applier->orderedExportColumns($columns, $payload);
            if ($exportColumns === []) {
                $export->update([
                    'status' => DataTableExport::STATUS_FAILED,
                    'error_message' => 'No valid columns to export.',
                ]);

                return;
            }

            $query = $applier->applyExport($query, $columns, $payload, $resource);

            $filename = 'export-'.$export->resource_key.'-'.date('Ymd-His').'-'.substr($export->id, 0, 8).'.csv';
            $path = $filename;

            $disk = Storage::disk('exports');

            $stream = fopen('php://temp', 'w+b');
            if ($stream === false) {
                $export->update([
                    'status' => DataTableExport::STATUS_FAILED,
                    'error_message' => 'Could not allocate export stream.',
                ]);

                return;
            }

            fputcsv($stream, $exportColumns, ';');
            $query->chunk(100, function ($models) use ($stream, $resource, $exportColumns): void {
                foreach ($models as $model) {
                    $row = $resource->transformRow($model);
                    $line = [];
                    foreach ($exportColumns as $col) {
                        $val = $row[$col] ?? '';
                        $line[] = is_scalar($val) ? (string) $val : '';
                    }
                    fputcsv($stream, $line, ';');
                }
            });
            rewind($stream);
            $disk->put($path, $stream);
            fclose($stream);

            $ttlMinutes = config('datatable.export_download_ttl_minutes', 15);
            $export->update([
                'status' => DataTableExport::STATUS_READY,
                'file_path' => $path,
                'download_expires_at' => now()->addMinutes($ttlMinutes),
                'error_message' => null,
            ]);

            if ($user) {
                activity('data_export')
                    ->causedBy($user)
                    ->event('export')
                    ->withProperties([
                        'resource_key' => $export->resource_key,
                        'export_id' => $export->id,
                        'status' => DataTableExport::STATUS_READY,
                    ])
                    ->log('Data export to CSV ready for: '.$export->resource_key);
            }

            event(new DataTableExportReady(
                (string) $export->user_id,
                (string) $export->id,
                (string) $export->resource_key,
            ));
        } catch (\Throwable $e) {
            $export->update([
                'status' => DataTableExport::STATUS_FAILED,
                'error_message' => $e->getMessage(),
            ]);
            throw $e;
        }
    }
}
