<?php

namespace Ignite\Core\Tests\Feature\Services;

use Ignite\Core\Entities\ReportDownload;
use Ignite\Core\Jobs\ProcessReportDownload;
use Ignite\Core\Models\Grid\QueryTable;
use Ignite\Core\Services\Reports\ReportDownloadRequest;
use Ignite\Core\Services\Reports\ReportDownloadService;
use Ignite\Core\Tests\TestCase;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;

class ReportDownloadServiceTest extends TestCase
{
    /** @var string */
    protected $storageRoot;

    protected function getPackageProviders($app)
    {
        return array_merge(parent::getPackageProviders($app), [
            \Yajra\DataTables\DataTablesServiceProvider::class,
            \Yajra\DataTables\HtmlServiceProvider::class,
            \Yajra\DataTables\ButtonsServiceProvider::class,
        ]);
    }

    protected function getEnvironmentSetUp($app)
    {
        parent::getEnvironmentSetUp($app);

        // SQLite keeps these integration tests self-contained and avoids relying on
        // a developer's external MySQL state.
        $app['config']->set('database.connections.testing', [
            'driver' => 'sqlite',
            'database' => ':memory:',
            'prefix' => '',
        ]);
    }

    protected function setUp(): void
    {
        parent::setUp();

        $this->storageRoot = sys_get_temp_dir().'/ignite-core-report-download-tests-'.getmypid();
        config()->set('filesystems.disks.local', [
            'driver' => 'local',
            'root' => $this->storageRoot,
        ]);
        config()->set('core.report_download.disk', 'local');
        config()->set('core.report_download.directory', 'report-downloads');
        config()->set('core.report_download.progress_batch_size', 1);
        config()->set('core.report_download.xlsx.cache_directory', $this->storageRoot.'/cache');

        Schema::create('core_user', function (Blueprint $table) {
            $table->increments('user_id');
            $table->string('email');
            $table->string('first')->nullable();
            $table->string('last')->nullable();
            $table->unsignedTinyInteger('status')->default(1);
            $table->unsignedTinyInteger('internal')->default(0);
            $table->timestamps();
        });
        Schema::create('report_download_test_rows', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
        });

        require_once __DIR__.'/../../../database/migrations/2026_08_13_000000_create_core_report_download_table.php';
        (new \CreateCoreReportDownloadTable())->up();

        DB::table('core_user')->insert([
            'user_id' => 1,
            'email' => 'report-download@example.test',
            'first' => 'Report',
            'last' => 'Requester',
            'status' => 1,
            'internal' => 0,
        ]);
        DB::table('report_download_test_rows')->insert([
            ['name' => 'Beta'],
            ['name' => 'Alpha'],
        ]);
    }

    protected function tearDown(): void
    {
        Storage::disk('local')->deleteDirectory('report-downloads');

        parent::tearDown();
    }

    /**
     * @test
     */
    public function itQueuesAndGeneratesACsvWithMeasuredProgress(): void
    {
        Queue::fake();
        $query = DB::table('report_download_test_rows')->select(['id', 'name']);
        $request = ReportDownloadRequest::forQueryBuilder(
            1,
            $query,
            'Ad Hoc Rows',
            ['id' => 'ID', 'name' => 'Name'],
            ReportDownloadRequest::FORMAT_CSV,
            [['column' => 'name', 'direction' => 'asc']]
        );

        $service = app(ReportDownloadService::class);
        $download = $service->requestReportDownload($request);

        Queue::assertPushed(ProcessReportDownload::class);
        $this->assertSame(ReportDownload::STATUS_QUEUED, $download->status);

        $completed = $service->process($download->getKey());

        $this->assertSame(ReportDownload::STATUS_COMPLETE, $completed->status);
        $this->assertSame(100, $completed->progress);
        $this->assertSame(2, $completed->row_count);
        $this->assertSame('report-downloads/'.$completed->getKey().'-ad-hoc-rows.csv', $completed->file_path);
        Storage::disk('local')->assertExists($completed->file_path);
        $this->assertSame(
            "ID,Name\n2,Alpha\n1,Beta\n",
            str_replace("\r\n", "\n", Storage::disk('local')->get($completed->file_path))
        );
    }

    /**
     * @test
     */
    public function itUsesExpectedRowCountToDecideWhetherARequestShouldQueue(): void
    {
        $service = app(ReportDownloadService::class);
        $request = ReportDownloadRequest::forQueryBuilder(
            1,
            DB::table('report_download_test_rows')->select(['id', 'name']),
            'Threshold Rows',
            ['id' => 'ID', 'name' => 'Name']
        );

        $this->assertSame(2, $service->expectedRowCount($request));

        config()->set('core.report_download.record_threshold', 3);
        $this->assertFalse($service->shouldQueue($request));

        config()->set('core.report_download.record_threshold', 2);
        $this->assertTrue($service->shouldQueue($request));
    }

    /**
     * @test
     */
    public function itGeneratesXlsxWithAFilesystemCellCache(): void
    {
        Queue::fake();
        $request = ReportDownloadRequest::forQueryBuilder(
            1,
            DB::table('report_download_test_rows')->select(['id', 'name']),
            'Spreadsheet Rows',
            ['id' => 'ID', 'name' => 'Name'],
            ReportDownloadRequest::FORMAT_XLSX,
            [['column' => 'id', 'direction' => 'asc']]
        );

        $completed = app(ReportDownloadService::class)->process(
            app(ReportDownloadService::class)->requestReportDownload($request)->getKey()
        );

        Storage::disk('local')->assertExists($completed->file_path);
        $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load(
            Storage::disk('local')->path($completed->file_path)
        );
        $this->assertSame(
            [['ID', 'Name'], ['1', 'Beta'], ['2', 'Alpha']],
            $spreadsheet->getActiveSheet()->toArray()
        );
        $spreadsheet->disconnectWorksheets();
    }

    /**
     * @test
     */
    public function itNeutralizesFormulaLikeValuesInCsvAndXlsx(): void
    {
        Queue::fake();
        $values = [
            '=1+1',
            '+SUM(A1:A2)',
            '-2+3',
            '@SUM(A1:A2)',
            " \t=1+1",
            'Normal text',
        ];
        foreach ($values as $value) {
            DB::table('report_download_test_rows')->insert(['name' => $value]);
        }

        $service = app(ReportDownloadService::class);
        $query = DB::table('report_download_test_rows')
            ->where('id', '>', 2)
            ->select(['id', 'name']);
        $columns = ['id' => 'ID', 'name' => 'Name'];
        $sort = [['column' => 'id', 'direction' => 'asc']];

        $csv = $service->process($service->requestReportDownload(
            ReportDownloadRequest::forQueryBuilder(
                1,
                clone $query,
                'Safe Formula CSV',
                $columns,
                ReportDownloadRequest::FORMAT_CSV,
                $sort
            )
        )->getKey());

        $handle = fopen(Storage::disk('local')->path($csv->file_path), 'r');
        $this->assertSame(['ID', 'Name'], fgetcsv($handle));
        $csvValues = [];
        while (($row = fgetcsv($handle)) !== false) {
            $csvValues[] = $row[1];
        }
        fclose($handle);
        $this->assertSame([
            "'=1+1",
            "'+SUM(A1:A2)",
            "'-2+3",
            "'@SUM(A1:A2)",
            "' \t=1+1",
            'Normal text',
        ], $csvValues);

        $xlsx = $service->process($service->requestReportDownload(
            ReportDownloadRequest::forQueryBuilder(
                1,
                clone $query,
                'Safe Formula XLSX',
                $columns,
                ReportDownloadRequest::FORMAT_XLSX,
                $sort
            )
        )->getKey());
        $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load(
            Storage::disk('local')->path($xlsx->file_path)
        );
        $sheet = $spreadsheet->getActiveSheet();
        foreach ($values as $index => $value) {
            $cell = $sheet->getCell('B'.($index + 2));
            $this->assertSame($value, $cell->getValue());
            $this->assertSame(\PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING, $cell->getDataType());
        }
        $spreadsheet->disconnectWorksheets();
    }

    /**
     * @test
     */
    public function itReconstructsAQueryTableWithRequestedSearchSortAndColumns(): void
    {
        Queue::fake();
        DB::table('report_download_test_rows')->insert(['name' => 'Echo']);
        $request = ReportDownloadRequest::forTable(
            1,
            ReportDownloadServiceQueryTable::class,
            'Configured Table',
            ReportDownloadRequest::FORMAT_CSV,
            [],
            [],
            [['column' => 'name', 'direction' => 'desc']],
            ['value' => 'a', 'columns' => []],
            ['name']
        );

        $service = app(ReportDownloadService::class);
        $completed = $service->process(
            $service->requestReportDownload($request)->getKey()
        );

        $this->assertSame(2, $completed->row_count);
        $this->assertSame(
            "Name\nBeta\nAlpha\n",
            str_replace("\r\n", "\n", Storage::disk('local')->get($completed->file_path))
        );
    }

    /**
     * @test
     */
    public function itHonorsCancellationAndReusesAFailedRecordForRetry(): void
    {
        Queue::fake();
        $service = app(ReportDownloadService::class);
        $request = ReportDownloadRequest::forQueryBuilder(
            1,
            DB::table('report_download_test_rows')->select(['id']),
            'Lifecycle Rows',
            ['id' => 'ID']
        );

        $cancelled = $service->cancel($service->requestReportDownload($request)->getKey());
        $this->assertSame(ReportDownload::STATUS_CANCELLED, $cancelled->status);
        $this->assertSame(
            ReportDownload::STATUS_CANCELLED,
            $service->process($cancelled->getKey())->status
        );

        $failed = $service->requestReportDownload($request);
        $failed->markFailed('Temporary download failure.');
        $retried = $service->retry($failed->getKey());

        $this->assertSame($failed->getKey(), $retried->getKey());
        $this->assertSame(ReportDownload::STATUS_QUEUED, $retried->status);
        $this->assertSame(1, $retried->retry_count);
        $this->assertNull($retried->error_message);
        Queue::assertPushed(ProcessReportDownload::class, 3);
    }
}

// Keeping this behavior-specific fixture beside its only consumer avoids adding a
// production class or a tree of disposable test-support files.
// phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses
class ReportDownloadServiceQueryTable extends QueryTable
{
    public function columns()
    {
        return [
            'id' => ['title' => 'ID', 'name' => 'id'],
        ];
    }

    /**
     * Simulate a report that adds form-backed columns beyond public columns().
     */
    protected function getColumns()
    {
        return array_merge(parent::getColumns(), [
            'name' => ['title' => 'Name', 'name' => 'name'],
        ]);
    }

    public function query()
    {
        return $this->getConnection()->table('report_download_test_rows');
    }
}
// phpcs:enable PSR1.Classes.ClassDeclaration.MultipleClasses
