<?php

declare(strict_types=1);

use App\Models\ReportJob;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;

uses(DatabaseTransactions::class);

beforeEach(function () {
    $this->tenant = Tenant::factory()->create();
    $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
    Storage::fake('local');
});

function doneReportJob(int $tenantId, int $userId, array $overrides = []): ReportJob
{
    return ReportJob::create(array_merge([
        'tenant_id' => $tenantId,
        'user_id' => $userId,
        'type' => 'deals_export',
        'parameters' => ['format' => 'csv', 'date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
        'status' => ReportJob::STATUS_DONE,
        'file_path' => null,
        'file_size' => null,
        'finished_at' => Carbon::now(),
        'expires_at' => Carbon::now()->addDays(30),
    ], $overrides));
}

function signedDownloadUrl(ReportJob $job, ?Carbon $expiry = null): string
{
    return URL::temporarySignedRoute(
        'reports.download',
        $expiry ?? Carbon::now()->addHours(24),
        ['id' => $job->id, 'tenant' => $job->tenant_id],
    );
}

test('download: success → 200 + attachment файла', function () {
    $path = "reports/{$this->tenant->id}/1.csv";
    Storage::disk('local')->put($path, "col\r\nval\r\n");
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => $path]);

    $response = $this->get(signedDownloadUrl($job));

    $response->assertOk();
    $response->assertDownload("report-{$job->id}.csv");
});

test('download: невалидная подпись → 403', function () {
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => 'reports/x/1.csv']);

    $this->get("/api/reports/jobs/{$job->id}/file?tenant={$this->tenant->id}&expires=9999999999&signature=deadbeef")
        ->assertStatus(403);
});

test('download: просроченная подпись → 403', function () {
    $path = "reports/{$this->tenant->id}/2.csv";
    Storage::disk('local')->put($path, 'data');
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => $path]);

    $this->get(signedDownloadUrl($job, Carbon::now()->subHour()))->assertStatus(403);
});

test('download: file_path=NULL (истёк) → 410', function () {
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => null]);

    $this->get(signedDownloadUrl($job))->assertStatus(410);
});

test('download: файл отсутствует на диске → 404', function () {
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => 'reports/missing/9.csv']);

    $this->get(signedDownloadUrl($job))->assertStatus(404);
});

test('download: несуществующий job → 404', function () {
    $ghost = new ReportJob(['tenant_id' => $this->tenant->id]);
    $ghost->id = 999999;
    $this->get(signedDownloadUrl($ghost))->assertStatus(404);
});

test('toResource (GET /jobs/{id}): done-job содержит download_url', function () {
    $path = "reports/{$this->tenant->id}/3.csv";
    Storage::disk('local')->put($path, 'data');
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => $path]);

    $response = $this->actingAs($this->user)->getJson("/api/reports/jobs/{$job->id}");

    $response->assertOk();
    $url = $response->json('job.download_url');
    expect($url)->toContain("/api/reports/jobs/{$job->id}/file");
    expect($url)->toContain('signature=');
});

test('toResource: pending-job → download_url = null', function () {
    $job = ReportJob::create([
        'tenant_id' => $this->tenant->id,
        'user_id' => $this->user->id,
        'type' => 'deals_export',
        'parameters' => ['format' => 'csv', 'date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
        'status' => ReportJob::STATUS_PENDING,
    ]);

    $response = $this->actingAs($this->user)->getJson("/api/reports/jobs/{$job->id}");

    expect($response->json('job.download_url'))->toBeNull();
});

test('download: expires_at в прошлом → 410 (до cron-очистки file_path)', function () {
    $path = "reports/{$this->tenant->id}/4.csv";
    Storage::disk('local')->put($path, 'data');
    $job = doneReportJob($this->tenant->id, $this->user->id, [
        'file_path' => $path,
        'expires_at' => Carbon::now()->subDay(),
    ]);

    $this->get(signedDownloadUrl($job))->assertStatus(410);
});

test('toResource: expired done-job → download_url = null', function () {
    $path = "reports/{$this->tenant->id}/5.csv";
    Storage::disk('local')->put($path, 'data');
    $job = doneReportJob($this->tenant->id, $this->user->id, [
        'file_path' => $path,
        'expires_at' => Carbon::now()->subDay(),
    ]);

    $response = $this->actingAs($this->user)->getJson("/api/reports/jobs/{$job->id}");

    expect($response->json('job.download_url'))->toBeNull();
});

test('download: подмена tenant в signed URL ломает подпись → 403', function () {
    $path = "reports/{$this->tenant->id}/6.csv";
    Storage::disk('local')->put($path, 'data');
    $job = doneReportJob($this->tenant->id, $this->user->id, ['file_path' => $path]);

    $url = signedDownloadUrl($job);
    $otherTenant = Tenant::factory()->create();
    // Подменяем tenant=<own> на tenant=<other> в готовом signed URL — подпись
    // покрывает query-параметры, поэтому ValidateSignature вернёт 403.
    $tampered = str_replace(
        "tenant={$this->tenant->id}",
        "tenant={$otherTenant->id}",
        $url,
    );

    $this->get($tampered)->assertStatus(403);
});
