<?php

declare(strict_types=1);

use App\Jobs\GenerateReportJob;
use App\Models\BalanceTransaction;
use App\Models\Project;
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;

uses(DatabaseTransactions::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 makeReportJob(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));
}

test('GET /api/reports/jobs: 401 без auth', function () {
    auth()->logout();
    $this->getJson('/api/reports/jobs')->assertStatus(401);
});

test('GET /api/reports/jobs: пустой', function () {
    $response = $this->getJson('/api/reports/jobs');
    $response->assertOk();
    expect($response->json('jobs'))->toBe([]);
    expect($response->json('total'))->toBe(0);
    expect($response->json('quota.max_active'))->toBe(3);
    expect($response->json('quota.active'))->toBe(0);
});

test('GET /api/reports/jobs: возвращает только свои', function () {
    $other = Tenant::factory()->create();
    $otherUser = User::factory()->create(['tenant_id' => $other->id]);
    makeReportJob($this->tenant->id, $this->user->id);
    makeReportJob($other->id, $otherUser->id);

    $response = $this->getJson('/api/reports/jobs');
    expect($response->json('jobs'))->toHaveCount(1);
    expect($response->json('total'))->toBe(1);
});

test('GET /api/reports/jobs: ORDER BY created_at DESC', function () {
    $j1 = makeReportJob($this->tenant->id, $this->user->id);
    DB::table('report_jobs')->where('id', $j1->id)->update(['created_at' => Carbon::now()->subHours(2)]);
    $j2 = makeReportJob($this->tenant->id, $this->user->id);
    DB::table('report_jobs')->where('id', $j2->id)->update(['created_at' => Carbon::now()->subHour()]);
    $j3 = makeReportJob($this->tenant->id, $this->user->id);

    $response = $this->getJson('/api/reports/jobs');
    $ids = array_column($response->json('jobs'), 'id');
    expect($ids)->toBe([$j3->id, $j2->id, $j1->id]);
});

test('GET /api/reports/jobs?status=done: фильтр по статусу', function () {
    makeReportJob($this->tenant->id, $this->user->id);
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE, 'file_path' => 'reports/x/1.csv']);

    $response = $this->getJson('/api/reports/jobs?status=done');
    expect($response->json('jobs'))->toHaveCount(1);
    expect($response->json('jobs.0.status'))->toBe('done');
});

test('GET /api/reports/jobs: counts по 4 статусам', function () {
    makeReportJob($this->tenant->id, $this->user->id);
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_PROCESSING]);
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE]);
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_FAILED]);

    $response = $this->getJson('/api/reports/jobs');
    expect($response->json('counts'))->toBe([
        'pending' => 1, 'processing' => 1, 'done' => 1, 'failed' => 1,
    ]);
    expect($response->json('quota.active'))->toBe(2); // pending + processing
});

test('GET /api/reports/jobs: limit + offset', function () {
    foreach (range(1, 5) as $_) {
        makeReportJob($this->tenant->id, $this->user->id);
    }
    $response = $this->getJson('/api/reports/jobs?limit=2&offset=1');
    expect($response->json('jobs'))->toHaveCount(2);
    expect($response->json('total'))->toBe(5);
});

test('GET /api/reports/jobs?status=invalid: 422', function () {
    $this->getJson('/api/reports/jobs?status=invalid')->assertStatus(422);
});

test('GET /api/reports/jobs/{id}: success + поля', function () {
    $job = makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE, 'file_path' => 'reports/1/2.csv', 'file_size' => 512]);
    $response = $this->getJson("/api/reports/jobs/{$job->id}");
    $response->assertOk();
    expect($response->json('job.id'))->toBe($job->id);
    expect($response->json('job.status'))->toBe('done');
    expect($response->json('job.file_size'))->toBe(512);
});

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

test('GET /api/reports/jobs/{id}: 404 чужой', function () {
    $other = Tenant::factory()->create();
    $otherUser = User::factory()->create(['tenant_id' => $other->id]);
    $job = makeReportJob($other->id, $otherUser->id);
    $this->getJson("/api/reports/jobs/{$job->id}")->assertStatus(404);
});

test('POST /api/reports/jobs: 422 без полей', function () {
    $this->postJson('/api/reports/jobs', [])->assertStatus(422);
});

test('POST /api/reports/jobs: 422 неизвестный type', function () {
    $this->postJson('/api/reports/jobs', [
        'type' => 'unknown', 'format' => 'csv',
        'parameters' => ['date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
    ])->assertStatus(422);
});

test('POST /api/reports/jobs: 422 date_to < date_from', function () {
    $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'csv',
        'parameters' => ['date_from' => '2026-04-30', 'date_to' => '2026-04-01'],
    ])->assertStatus(422);
});

test('POST /api/reports/jobs: создаёт row + dispatch GenerateReportJob', function () {
    Bus::fake();

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'csv',
        'parameters' => ['date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
    ]);

    $response->assertStatus(201);
    expect($response->json('job.type'))->toBe('deals_export');
    expect($response->json('job.parameters.format'))->toBe('csv');
    expect($response->json('job.status'))->toBe('pending');

    Bus::assertDispatched(GenerateReportJob::class, function (GenerateReportJob $job) use ($response) {
        return $job->reportJobId === $response->json('job.id');
    });
});

test('POST /api/reports/jobs: квота 3 одновременных → 422 на 4-м', function () {
    Bus::fake();
    foreach (range(1, 3) as $_) {
        makeReportJob($this->tenant->id, $this->user->id);
    }

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'csv',
        'parameters' => ['date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
    ]);

    $response->assertStatus(422);
    expect($response->json('errors._quota.0'))->toContain('Максимум 3');
    Bus::assertNotDispatched(GenerateReportJob::class);
});

test('POST /api/reports/jobs: квота не считает done/failed', function () {
    Bus::fake();
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE]);
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_DONE]);
    makeReportJob($this->tenant->id, $this->user->id, ['status' => ReportJob::STATUS_FAILED]);
    makeReportJob($this->tenant->id, $this->user->id);  // pending = 1 active

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'csv',
        'parameters' => ['date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
    ]);

    $response->assertStatus(201);
});

test('POST /api/reports/jobs: квота изолирована per tenant', function () {
    Bus::fake();
    $other = Tenant::factory()->create();
    $otherUser = User::factory()->create(['tenant_id' => $other->id]);
    foreach (range(1, 3) as $_) {
        makeReportJob($other->id, $otherUser->id);  // 3 у чужого тенанта
    }

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'csv',
        'parameters' => ['date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
    ]);

    $response->assertStatus(201);
});

test('POST /api/reports/jobs (sync queue): pending → done с file', function () {
    config()->set('queue.default', 'sync');

    // Партиции deals создаются ahead=2 от текущего месяца — используем NOW для гарантии.
    $now = Carbon::now()->startOfMonth()->addDays(10);
    $project = Project::factory()->create(['tenant_id' => $this->tenant->id]);
    DB::table('deals')->insert([
        'tenant_id' => $this->tenant->id,
        'project_id' => $project->id,
        'phone' => '+79991234567',
        'contact_name' => 'Тест',
        'status' => 'new',
        'received_at' => $now,
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'csv',
        'parameters' => [
            'date_from' => $now->copy()->startOfMonth()->toDateString(),
            'date_to' => $now->copy()->endOfMonth()->toDateString(),
        ],
    ]);

    $response->assertStatus(201);
    $jobId = $response->json('job.id');
    $job = ReportJob::find($jobId);
    expect($job->status)->toBe('done');
    expect($job->file_path)->toBe("reports/{$this->tenant->id}/{$jobId}.csv");
    expect($job->file_size)->toBeGreaterThan(0);
    expect($job->finished_at)->not->toBeNull();
    expect($job->expires_at)->not->toBeNull();

    Storage::disk('local')->assertExists($job->file_path);
    $content = Storage::disk('local')->get($job->file_path);
    expect($content)->toContain('+79991234567');
    expect($content)->toContain("\u{FEFF}");  // BOM для Excel
});

test('POST /api/reports/jobs (sync queue): xlsx → done с XLSX-файлом', function () {
    config()->set('queue.default', 'sync');

    $now = Carbon::now()->startOfMonth()->addDays(10);
    $project = Project::factory()->create(['tenant_id' => $this->tenant->id]);
    DB::table('deals')->insert([
        'tenant_id' => $this->tenant->id,
        'project_id' => $project->id,
        'phone' => '+79992223344',
        'contact_name' => 'XLSX Тест',
        'status' => 'new',
        'received_at' => $now,
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'xlsx',
        'parameters' => [
            'date_from' => $now->copy()->startOfMonth()->toDateString(),
            'date_to' => $now->copy()->endOfMonth()->toDateString(),
        ],
    ]);

    $response->assertStatus(201);
    $job = ReportJob::find($response->json('job.id'));
    expect($job->status)->toBe('done');
    expect($job->file_path)->toEndWith('.xlsx');
    expect($job->file_size)->toBeGreaterThan(2048);

    Storage::disk('local')->assertExists($job->file_path);
    $content = Storage::disk('local')->get($job->file_path);
    // XLSX = ZIP (PK\x03\x04 magic bytes).
    expect(substr($content, 0, 4))->toBe("PK\x03\x04");
});

test('POST /api/reports/jobs (sync queue): json → done с JSON-файлом', function () {
    config()->set('queue.default', 'sync');

    $now = Carbon::now()->startOfMonth()->addDays(10);
    $project = Project::factory()->create(['tenant_id' => $this->tenant->id]);
    DB::table('deals')->insert([
        'tenant_id' => $this->tenant->id,
        'project_id' => $project->id,
        'phone' => '+79993334455',
        'contact_name' => 'JSON Тест',
        'status' => 'new',
        'received_at' => $now,
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'json',
        'parameters' => [
            'date_from' => $now->copy()->startOfMonth()->toDateString(),
            'date_to' => $now->copy()->endOfMonth()->toDateString(),
        ],
    ]);

    $response->assertStatus(201);
    $job = ReportJob::find($response->json('job.id'));
    expect($job->status)->toBe('done');
    expect($job->file_path)->toEndWith('.json');

    $content = Storage::disk('local')->get($job->file_path);
    $decoded = json_decode($content, true);
    expect($decoded)->toHaveKey('headers');
    expect($decoded)->toHaveKey('rows');
    expect($decoded['headers'])->toContain('Телефон');
    expect($decoded['rows'])->toHaveCount(1);
    expect($decoded['rows'][0]['Телефон'])->toBe('+79993334455');
});

test('POST /api/reports/jobs (sync queue): pdf → failed (Post-MVP)', function () {
    config()->set('queue.default', 'sync');

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'deals_export', 'format' => 'pdf',
        'parameters' => ['date_from' => '2026-04-01', 'date_to' => '2026-04-30'],
    ]);

    $response->assertStatus(201);
    $job = ReportJob::find($response->json('job.id'));
    expect($job->status)->toBe('failed');
    expect($job->error_message)->toContain('Post-MVP');
});

test('POST /api/reports/jobs (sync queue): managers_summary → done с CSV', function () {
    config()->set('queue.default', 'sync');

    $now = Carbon::now()->startOfMonth()->addDays(10);
    $project = Project::factory()->create(['tenant_id' => $this->tenant->id]);
    $manager = User::factory()->create([
        'tenant_id' => $this->tenant->id, 'first_name' => 'Иван', 'last_name' => 'Петров',
    ]);
    DB::table('deals')->insert([
        'tenant_id' => $this->tenant->id,
        'project_id' => $project->id,
        'manager_id' => $manager->id,
        'phone' => '+79990001122',
        'status' => 'won',
        'received_at' => $now,
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'managers_summary', 'format' => 'csv',
        'parameters' => [
            'date_from' => $now->copy()->startOfMonth()->toDateString(),
            'date_to' => $now->copy()->endOfMonth()->toDateString(),
        ],
    ]);

    $response->assertStatus(201);
    $job = ReportJob::find($response->json('job.id'));
    expect($job->status)->toBe('done');
    expect($job->file_path)->toEndWith('.csv');

    $content = Storage::disk('local')->get($job->file_path);
    expect($content)->toContain('Менеджер');
    expect($content)->toContain('Иван Петров');
});

test('POST /api/reports/jobs (sync queue): sources_summary → done с CSV', function () {
    config()->set('queue.default', 'sync');

    $now = Carbon::now()->startOfMonth()->addDays(10);
    $project = Project::factory()->create(['tenant_id' => $this->tenant->id]);
    DB::table('deals')->insert([
        'tenant_id' => $this->tenant->id,
        'project_id' => $project->id,
        'phone' => '+79990002233',
        'status' => 'won',
        'utm_source' => 'yandex',
        'received_at' => $now,
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'sources_summary', 'format' => 'csv',
        'parameters' => [
            'date_from' => $now->copy()->startOfMonth()->toDateString(),
            'date_to' => $now->copy()->endOfMonth()->toDateString(),
        ],
    ]);

    $response->assertStatus(201);
    $job = ReportJob::find($response->json('job.id'));
    expect($job->status)->toBe('done');
    expect($job->file_path)->toEndWith('.csv');

    $content = Storage::disk('local')->get($job->file_path);
    expect($content)->toContain('Источник');
    expect($content)->toContain('yandex');
});

test('POST /api/reports/jobs (sync queue): billing_summary → done с CSV', function () {
    config()->set('queue.default', 'sync');

    BalanceTransaction::create([
        'tenant_id' => $this->tenant->id,
        'type' => 'topup',
        'amount_rub' => 5000,
        'amount_leads' => 0,
        'description' => 'test topup',
        'created_at' => Carbon::now()->startOfMonth()->addDays(5),
    ]);

    $response = $this->postJson('/api/reports/jobs', [
        'type' => 'billing_summary', 'format' => 'csv',
        'parameters' => [
            'date_from' => Carbon::now()->startOfMonth()->toDateString(),
            'date_to' => Carbon::now()->endOfMonth()->toDateString(),
        ],
    ]);

    $response->assertStatus(201);
    $job = ReportJob::find($response->json('job.id'));
    expect($job->status)->toBe('done');
    expect($job->file_path)->toEndWith('.csv');

    $content = Storage::disk('local')->get($job->file_path);
    expect($content)->toContain('Тип операции');
    expect($content)->toContain('Пополнение');
});
