<?php

declare(strict_types=1);

use App\Jobs\GenerateReportJob;
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\Bus;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Tests\Concerns\SharesSupplierPdo;

uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);

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

function makeJob(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_PENDING,
    ], $overrides));
}

// ------ retry ------

test('POST /retry: 404 unknown', function () {
    $this->postJson('/api/reports/jobs/999999/retry')->assertStatus(404);
});

test('POST /retry: 404 чужой', function () {
    $other = Tenant::factory()->create();
    $otherUser = User::factory()->create(['tenant_id' => $other->id]);
    $job = makeJob($other->id, $otherUser->id, ['status' => ReportJob::STATUS_FAILED]);
    $this->postJson("/api/reports/jobs/{$job->id}/retry")->assertStatus(404);
});

test('POST /retry: 403 не владелец (тот же tenant)', function () {
    $other = User::factory()->create(['tenant_id' => $this->tenant->id]);
    $job = makeJob($this->tenant->id, $other->id, ['status' => ReportJob::STATUS_FAILED]);
    $this->postJson("/api/reports/jobs/{$job->id}/retry")->assertStatus(403);
});

test('POST /retry: 422 если status != failed', function () {
    $job = makeJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE]);
    $response = $this->postJson("/api/reports/jobs/{$job->id}/retry");
    $response->assertStatus(422);
    expect($response->json('errors.status.0'))->toContain('done');
});

test('POST /retry: success — создаёт NEW job + dispatch + retry_count=1', function () {
    Bus::fake();
    $original = makeJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_FAILED]);

    $response = $this->postJson("/api/reports/jobs/{$original->id}/retry");
    $response->assertStatus(201);
    expect($response->json('job.id'))->not->toBe($original->id);
    expect($response->json('job.status'))->toBe('pending');
    expect($response->json('job.retry_count'))->toBe(1);
    expect($response->json('job.parameters.retry_of'))->toBe($original->id);

    Bus::assertDispatched(GenerateReportJob::class);
});

test('POST /retry: 422 после 3 попыток (CTO-6)', function () {
    Bus::fake();
    $job = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_FAILED,
        'parameters' => ['format' => 'csv', 'date_from' => '2026-04-01', 'date_to' => '2026-04-30', 'retry_count' => 2],
    ]);

    $response = $this->postJson("/api/reports/jobs/{$job->id}/retry");
    $response->assertStatus(422);
    expect($response->json('errors._retry.0'))->toContain('Максимум 3');
});

test('POST /retry: 422 если старше 7 дней', function () {
    Bus::fake();
    $job = makeJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_FAILED]);
    DB::table('report_jobs')->where('id', $job->id)->update(['created_at' => Carbon::now()->subDays(8)]);

    $response = $this->postJson("/api/reports/jobs/{$job->id}/retry");
    $response->assertStatus(422);
    expect($response->json('errors._retry.0'))->toContain('7 дней');
});

test('POST /retry: 422 если квота 3 уже исчерпана', function () {
    Bus::fake();
    $failed = makeJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_FAILED]);
    foreach (range(1, 3) as $_) {
        makeJob($this->tenant->id, $this->user->id);
    }

    $response = $this->postJson("/api/reports/jobs/{$failed->id}/retry");
    $response->assertStatus(422);
    expect($response->json('errors._quota.0'))->toContain('Максимум 3');
});

// ------ cancel ------

test('POST /cancel: 404 unknown', function () {
    $this->postJson('/api/reports/jobs/999999/cancel')->assertStatus(404);
});

test('POST /cancel: 422 если не pending', function () {
    $job = makeJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE]);
    $response = $this->postJson("/api/reports/jobs/{$job->id}/cancel");
    $response->assertStatus(422);
});

test('POST /cancel: success → status=failed + Отменено пользователем', function () {
    $job = makeJob($this->tenant->id, $this->user->id);
    $response = $this->postJson("/api/reports/jobs/{$job->id}/cancel");
    $response->assertStatus(200);
    expect($response->json('job.status'))->toBe('failed');
    expect($response->json('job.error_message'))->toBe('Отменено пользователем.');
    expect($response->json('job.finished_at'))->not->toBeNull();
});

test('POST /cancel: 403 не владелец', function () {
    $other = User::factory()->create(['tenant_id' => $this->tenant->id]);
    $job = makeJob($this->tenant->id, $other->id);
    $this->postJson("/api/reports/jobs/{$job->id}/cancel")->assertStatus(403);
});

// ------ destroy ------

test('DELETE /jobs/{id}: 404 unknown', function () {
    $this->deleteJson('/api/reports/jobs/999999')->assertStatus(404);
});

test('DELETE /jobs/{id}: 422 если pending (не terminal)', function () {
    $job = makeJob($this->tenant->id, $this->user->id);
    $this->deleteJson("/api/reports/jobs/{$job->id}")->assertStatus(422);
});

test('DELETE /jobs/{id}: 403 не владелец', function () {
    $other = User::factory()->create(['tenant_id' => $this->tenant->id]);
    $job = makeJob($this->tenant->id, $other->id, ['status' => ReportJob::STATUS_DONE]);
    $this->deleteJson("/api/reports/jobs/{$job->id}")->assertStatus(403);
});

test('DELETE /jobs/{id}: success → row + file deleted', function () {
    Storage::disk('local')->put('reports/test/123.csv', 'data');
    $job = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_DONE, 'file_path' => 'reports/test/123.csv',
    ]);

    $this->deleteJson("/api/reports/jobs/{$job->id}")->assertStatus(200);
    Storage::disk('local')->assertMissing('reports/test/123.csv');
    expect(ReportJob::find($job->id))->toBeNull();
});

test('DELETE /jobs/{id}: success даже если file_path=NULL (expired)', function () {
    $job = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_DONE, 'file_path' => null,
    ]);
    $this->deleteJson("/api/reports/jobs/{$job->id}")->assertStatus(200);
    expect(ReportJob::find($job->id))->toBeNull();
});

// ------ index includes is_expired + retry_count ------

test('GET /jobs: response содержит is_expired + retry_count + retry_max', function () {
    makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_DONE,
        'expires_at' => Carbon::now()->subDay(),  // expired
        'parameters' => ['format' => 'csv', 'date_from' => '2026-04-01', 'date_to' => '2026-04-30', 'retry_count' => 2],
    ]);

    $response = $this->getJson('/api/reports/jobs');
    expect($response->json('jobs.0.is_expired'))->toBeTrue();
    expect($response->json('jobs.0.retry_count'))->toBe(2);
    expect($response->json('jobs.0.retry_max'))->toBe(3);
});

// ------ cleanup-expired cron ------

test('reports:cleanup-expired: удаляет file_path expired done-jobs', function () {
    Storage::disk('local')->put('reports/exp/1.csv', 'a');
    Storage::disk('local')->put('reports/exp/2.csv', 'b');

    $expired = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_DONE,
        'file_path' => 'reports/exp/1.csv',
        'expires_at' => Carbon::now()->subDay(),
    ]);
    $alive = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_DONE,
        'file_path' => 'reports/exp/2.csv',
        'expires_at' => Carbon::now()->addDay(),
    ]);

    $this->artisan('reports:cleanup-expired')->assertExitCode(0);

    Storage::disk('local')->assertMissing('reports/exp/1.csv');
    Storage::disk('local')->assertExists('reports/exp/2.csv');

    expect(ReportJob::find($expired->id)->file_path)->toBeNull();
    expect(ReportJob::find($expired->id)->status)->toBe('done');  // CTO-10: статус НЕ меняется
    expect(ReportJob::find($alive->id)->file_path)->toBe('reports/exp/2.csv');
});

test('reports:cleanup-expired --dry-run: ничего не удаляет', function () {
    Storage::disk('local')->put('reports/exp/3.csv', 'c');
    $job = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_DONE,
        'file_path' => 'reports/exp/3.csv',
        'expires_at' => Carbon::now()->subDay(),
    ]);

    $this->artisan('reports:cleanup-expired --dry-run')->assertExitCode(0);

    Storage::disk('local')->assertExists('reports/exp/3.csv');
    expect(ReportJob::find($job->id)->file_path)->toBe('reports/exp/3.csv');
});

test('reports:cleanup-expired: failed-jobs игнорируются (даже если expired)', function () {
    $job = makeJob($this->tenant->id, $this->user->id, [
        'status' => ReportJob::STATUS_FAILED,
        'file_path' => 'reports/x/1.csv',
        'expires_at' => Carbon::now()->subDay(),
    ]);

    $this->artisan('reports:cleanup-expired')->assertExitCode(0);
    expect(ReportJob::find($job->id)->file_path)->toBe('reports/x/1.csv');  // НЕ затронут
});
