<?php

use App\Enums\ExportStatus;
use App\Jobs\GenerateExportJob;
use App\Models\Branch;
use App\Models\ExportJob;
use App\Models\Member;
use App\Models\User;
use App\Services\BranchAccessService;
use App\Services\MemberCsvColumns;
use App\Services\MemberFilterService;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use League\Csv\Writer;

beforeEach(function () {
    Storage::fake('local');
});

function runExportJob(int $exportJobId): void
{
    app(GenerateExportJob::class, ['exportJobId' => $exportJobId])
        ->handle(app(MemberFilterService::class), app(BranchAccessService::class));
}

test('a viewer can request an export', function () {
    Queue::fake();
    $branch = Branch::factory()->create();
    $viewer = User::factory()->branchViewer()->create(['branch_id' => $branch->id]);

    $response = $this->actingAs($viewer)->postJson('/api/v1/exports', []);

    $response->assertStatus(202)->assertJsonPath('data.status', 'pending');
    Queue::assertPushed(GenerateExportJob::class);
});

test('a data_clerk cannot request an export', function () {
    $branch = Branch::factory()->create();
    $clerk = User::factory()->dataClerk()->create(['branch_id' => $branch->id]);

    $this->actingAs($clerk)->postJson('/api/v1/exports', [])->assertStatus(403);
});

test('a filtered export produces a CSV whose row count matches the members count endpoint', function () {
    $branch = Branch::factory()->create();
    $otherBranch = Branch::factory()->create();
    Member::factory()->count(12)->create(['branch_id' => $branch->id, 'gender' => 'male']);
    Member::factory()->count(5)->create(['branch_id' => $branch->id, 'gender' => 'female']);
    Member::factory()->count(3)->create(['branch_id' => $otherBranch->id, 'gender' => 'male']);
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    $countResponse = $this->actingAs($admin)->getJson('/api/v1/members/count?gender=male');
    $expectedCount = $countResponse->json('data.count');
    expect($expectedCount)->toBe(12);

    $exportResponse = $this->actingAs($admin)->postJson('/api/v1/exports', ['gender' => 'male']);
    $exportJobId = $exportResponse->json('data.id');

    runExportJob($exportJobId);

    $job = ExportJob::find($exportJobId);
    expect($job->status)->toBe(ExportStatus::Completed);
    expect($job->processed_rows)->toBe($expectedCount);

    $csv = Storage::disk('local')->get($job->file_path);
    $lines = array_filter(explode("\n", trim($csv)));
    expect($lines)->toHaveCount($expectedCount + 1); // + header
});

test('a branch_admin export is scoped to their own branch even without an explicit filter', function () {
    $branch = Branch::factory()->create();
    $otherBranch = Branch::factory()->create();
    Member::factory()->count(4)->create(['branch_id' => $branch->id]);
    Member::factory()->count(6)->create(['branch_id' => $otherBranch->id]);
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    $exportResponse = $this->actingAs($admin)->postJson('/api/v1/exports', []);
    $exportJobId = $exportResponse->json('data.id');

    runExportJob($exportJobId);

    $job = ExportJob::find($exportJobId);
    expect($job->processed_rows)->toBe(4);
});

test('export resumes from a checkpoint without duplicating rows', function () {
    $branch = Branch::factory()->create();
    $members = Member::factory()->count(6)->create(['branch_id' => $branch->id]);
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    $exportResponse = $this->actingAs($admin)->postJson('/api/v1/exports', []);
    $exportJobId = $exportResponse->json('data.id');
    $job = ExportJob::find($exportJobId);

    // Simulate a first run that only got through the first 3 members before
    // being interrupted: write the header + first 3 rows by hand and set the
    // checkpoint fields the job itself would have set.
    $sorted = $members->sortBy('id')->values();
    $path = "exports/export-{$job->id}.csv";
    Storage::disk('local')->makeDirectory('exports');
    $writer = Writer::createFromPath(Storage::disk('local')->path($path), 'w');
    $writer->insertOne(MemberCsvColumns::exportColumns());
    foreach ($sorted->take(3) as $member) {
        $writer->insertOne([$member->member_number, $member->first_name, $member->last_name]);
    }
    $job->update([
        'file_path' => $path,
        'last_member_id' => $sorted[2]->id,
        'processed_rows' => 3,
        'status' => ExportStatus::Processing,
    ]);

    runExportJob($exportJobId);

    $job->refresh();
    expect($job->status)->toBe(ExportStatus::Completed);
    expect($job->processed_rows)->toBe(6);

    $csv = Storage::disk('local')->get($job->file_path);
    $lines = array_filter(explode("\n", trim($csv)));
    expect($lines)->toHaveCount(7); // header + 6 members, no duplicates

    $memberNumbers = array_map(fn ($line) => explode(',', $line)[0], array_slice($lines, 1));
    expect($memberNumbers)->toEqualCanonicalizing($sorted->pluck('member_number')->all());
});

test('download is blocked until the export is completed', function () {
    $branch = Branch::factory()->create();
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);
    $job = ExportJob::factory()->create(['user_id' => $admin->id, 'status' => ExportStatus::Processing]);

    $this->actingAs($admin)->get("/api/v1/exports/{$job->id}/download")->assertStatus(409);
});

test('only the owner or a super_admin can download an export', function () {
    $branch = Branch::factory()->create();
    $owner = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);
    $otherAdmin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);
    $superAdmin = User::factory()->superAdmin()->create();

    Storage::disk('local')->put('exports/export-owned.csv', "member_number\n");
    $job = ExportJob::factory()->create([
        'user_id' => $owner->id,
        'status' => ExportStatus::Completed,
        'file_path' => 'exports/export-owned.csv',
    ]);

    $this->actingAs($otherAdmin)->get("/api/v1/exports/{$job->id}/download")->assertStatus(404);
    $this->actingAs($owner)->get("/api/v1/exports/{$job->id}/download")->assertOk();
    $this->actingAs($superAdmin)->get("/api/v1/exports/{$job->id}/download")->assertOk();
});

test('unauthenticated request to list exports is rejected', function () {
    $this->getJson('/api/v1/exports')->assertStatus(401);
});
