<?php

namespace Tests\Feature\Documents;

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Documents\Enums\DocumentStatus;
use App\Domain\Documents\Models\Document;
use App\Domain\Documents\Models\DocumentType;
use App\Domain\Identity\Models\User;
use App\Domain\Organization\Models\Branch;
use App\Domain\Organization\Models\Company;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class DocumentReviewTest extends TestCase
{
    use RefreshDatabase;

    private Company $company;

    private Branch $branch;

    private Applicant $applicant;

    private DocumentType $type;

    protected function setUp(): void
    {
        parent::setUp();
        Storage::fake('local');
        $this->seed([PermissionSeeder::class, RoleSeeder::class]);

        $this->company = Company::factory()->create();
        $this->branch = Branch::factory()->for($this->company)->create();
        $this->applicant = Applicant::factory()->for($this->company)->create(['branch_id' => $this->branch->id]);
        $this->type = DocumentType::factory()->for($this->company)->create(['name' => 'Police Clearance']);
    }

    private function userWithRole(string $role): User
    {
        return tap(User::factory()->create([
            'company_id' => $this->company->id,
            'branch_id' => $this->branch->id,
        ]))->assignRole($role);
    }

    private function document(array $attributes = []): Document
    {
        $uploader = $this->userWithRole('recruitment-manager');

        $this->actingAs($uploader)->post(
            "/api/v1/applicants/{$this->applicant->uuid}/documents",
            [
                'document_type_id' => $this->type->uuid,
                'file' => UploadedFile::fake()->create('scan.pdf', 100, 'application/pdf'),
            ],
            ['Accept' => 'application/json'],
        )->assertCreated();

        $document = Document::current()->firstOrFail();

        if ($attributes !== []) {
            $document->update($attributes);
        }

        return $document->fresh();
    }

    private function review(User $actor, Document $document, array $payload)
    {
        return $this->actingAs($actor)
            ->postJson("/api/v1/documents/{$document->uuid}/review", $payload);
    }

    public function test_an_officer_can_verify_a_document(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document();

        $this->review($officer, $document, ['decision' => 'verify'])
            ->assertOk()
            ->assertJsonPath('data.status', 'verified');

        $document->refresh();
        $this->assertSame($officer->id, $document->verified_by);
        $this->assertNotNull($document->verified_at);
    }

    public function test_a_rejection_must_carry_a_reason(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document();

        $this->review($officer, $document, ['decision' => 'reject'])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['reason']]);

        $this->review($officer, $document, [
            'decision' => 'reject',
            'reason' => 'The second page is missing.',
        ])
            ->assertOk()
            ->assertJsonPath('data.status', 'rejected')
            ->assertJsonPath('data.rejection_reason', 'The second page is missing.');
    }

    public function test_requesting_a_re_upload_leaves_the_document_actionable(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document();

        $this->review($officer, $document, [
            'decision' => 'request_reupload',
            'reason' => 'The scan is not readable.',
        ])
            ->assertOk()
            ->assertJsonPath('data.status', 'reupload_required');
    }

    public function test_a_verified_document_cannot_be_reviewed_again(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document();

        $this->review($officer, $document, ['decision' => 'verify'])->assertOk();

        $this->review($officer, $document->fresh(), [
            'decision' => 'reject',
            'reason' => 'Changed my mind.',
        ])->assertStatus(422);
    }

    public function test_an_expired_document_cannot_be_verified(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document(['expiry_date' => now()->subDay()->toDateString()]);

        $this->review($officer, $document, ['decision' => 'verify'])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['expiry_date']]);
    }

    public function test_a_verified_document_reads_as_expired_once_its_date_passes(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document(['expiry_date' => now()->addDay()->toDateString()]);

        $this->review($officer, $document, ['decision' => 'verify'])->assertOk();

        // Verification does not outlive the paper.
        $document->fresh()->update(['expiry_date' => now()->subDay()->toDateString()]);

        $this->assertSame(DocumentStatus::Expired, $document->fresh()->effectiveStatus());
        $this->assertFalse($document->fresh()->isSatisfied());
    }

    public function test_a_role_without_verify_permission_cannot_verify(): void
    {
        // Data entry officers may upload but not verify.
        $this->review($this->userWithRole('data-entry-officer'), $this->document(), ['decision' => 'verify'])
            ->assertStatus(403);
    }

    public function test_waiving_requires_the_waive_permission_and_a_reason(): void
    {
        $document = $this->document();

        $this->review($this->userWithRole('document-verification-officer'), $document, [
            'decision' => 'waive',
            'reason' => 'Not issued in this district.',
        ])->assertStatus(403);

        $this->review($this->userWithRole('super-admin'), $document, ['decision' => 'waive'])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['reason']]);

        $this->review($this->userWithRole('super-admin'), $document, [
            'decision' => 'waive',
            'reason' => 'Not issued in this district; employer accepted an affidavit.',
        ])
            ->assertOk()
            ->assertJsonPath('data.status', 'waived')
            ->assertJsonPath('data.is_satisfied', true);
    }

    public function test_the_verification_queue_lists_documents_awaiting_review(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document();

        $this->actingAs($officer)->getJson('/api/v1/documents/queue')
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.id', $document->uuid);

        $this->review($officer, $document, ['decision' => 'verify'])->assertOk();

        // Once decided it leaves the queue.
        $this->actingAs($officer)->getJson('/api/v1/documents/queue')
            ->assertOk()
            ->assertJsonCount(0, 'data');
    }

    public function test_the_queue_excludes_applicants_outside_the_users_scope(): void
    {
        $this->document();

        $foreignApplicant = Applicant::factory()->create();
        Document::factory()->create([
            'company_id' => $foreignApplicant->company_id,
            'document_type_id' => DocumentType::factory()->create([
                'company_id' => $foreignApplicant->company_id,
            ])->id,
            'owner_type' => $foreignApplicant->getMorphClass(),
            'owner_id' => $foreignApplicant->id,
        ]);

        $this->actingAs($this->userWithRole('document-verification-officer'))
            ->getJson('/api/v1/documents/queue')
            ->assertOk()
            ->assertJsonCount(1, 'data');
    }

    public function test_internal_notes_are_hidden_from_roles_that_cannot_verify(): void
    {
        $officer = $this->userWithRole('document-verification-officer');
        $document = $this->document();

        $this->review($officer, $document, [
            'decision' => 'verify',
            'notes' => 'Cross-checked against the embassy register.',
        ])->assertOk();

        $agentView = $this->actingAs($this->userWithRole('data-entry-officer'))
            ->getJson("/api/v1/applicants/{$this->applicant->uuid}/documents")
            ->assertOk();

        $this->assertStringNotContainsString('embassy register', $agentView->getContent());
    }
}
