<?php

namespace Tests\Feature\Applicants;

use App\Domain\Agents\Enums\AgentStatus;
use App\Domain\Agents\Models\Agent;
use App\Domain\Applicants\Models\Applicant;
use App\Domain\Communication\Models\Note;
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 Tests\TestCase;

class AssignmentAndNotesTest extends TestCase
{
    use RefreshDatabase;

    private Company $company;

    private Branch $branch;

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

        $this->company = Company::factory()->create();
        $this->branch = Branch::factory()->for($this->company)->create();
    }

    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 applicant(array $attributes = []): Applicant
    {
        return Applicant::factory()->for($this->company)->create(array_merge([
            'branch_id' => $this->branch->id,
        ], $attributes));
    }

    private function agent(array $attributes = []): Agent
    {
        return Agent::factory()->for($this->company)->create(array_merge([
            'branch_id' => $this->branch->id,
            'status' => AgentStatus::Active->value,
        ], $attributes));
    }

    public function test_an_applicant_can_be_assigned_to_an_agent(): void
    {
        $applicant = $this->applicant();
        $agent = $this->agent(['name' => 'Karim Uddin']);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-agent", [
                'agent_id' => $agent->uuid,
            ])
            ->assertOk();

        $this->assertSame($agent->id, $applicant->fresh()->agent_id);

        $this->assertDatabaseHas('activities', [
            'event_action' => 'applicant.agent_assigned',
            'event_title' => 'Agent assigned',
            'event_description' => 'Karim Uddin',
        ]);
    }

    public function test_reassignment_records_who_held_the_file_before(): void
    {
        $first = $this->agent(['name' => 'First Agent']);
        $second = $this->agent(['name' => 'Second Agent']);
        $applicant = $this->applicant(['agent_id' => $first->id]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-agent", [
                'agent_id' => $second->uuid,
                'reason' => 'First agent left the company.',
            ])
            ->assertOk();

        // Who owned a file when matters for commission and complaints.
        $this->assertDatabaseHas('activities', [
            'event_action' => 'applicant.agent_assigned',
            'event_description' => 'First Agent → Second Agent',
        ]);
    }

    public function test_an_agent_who_cannot_operate_cannot_be_given_work(): void
    {
        $applicant = $this->applicant();
        $suspended = $this->agent(['name' => 'Suspended One', 'status' => AgentStatus::Suspended->value]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-agent", [
                'agent_id' => $suspended->uuid,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['agent_id']]);

        $this->assertNull($applicant->fresh()->agent_id);
    }

    public function test_an_agent_from_another_company_is_refused(): void
    {
        $applicant = $this->applicant();
        $foreign = Agent::factory()->active()->create();

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-agent", [
                'agent_id' => $foreign->uuid,
            ])
            ->assertStatus(422);
    }

    public function test_an_assignment_can_be_cleared(): void
    {
        $agent = $this->agent(['name' => 'Karim Uddin']);
        $applicant = $this->applicant(['agent_id' => $agent->id]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-agent", ['agent_id' => null])
            ->assertOk();

        $this->assertNull($applicant->fresh()->agent_id);
        $this->assertDatabaseHas('activities', [
            'event_description' => 'Karim Uddin → unassigned',
        ]);
    }

    public function test_assigning_staff_requires_the_staff_permission(): void
    {
        $applicant = $this->applicant();
        $officer = $this->userWithRole('processing-officer');

        // Processing officers may update applicants but not reassign ownership.
        $this->actingAs($officer)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-staff", [
                'assigned_to' => $officer->uuid,
            ])
            ->assertStatus(403);
    }

    public function test_a_suspended_user_cannot_be_given_a_file(): void
    {
        $applicant = $this->applicant();
        $suspended = tap(User::factory()->create([
            'company_id' => $this->company->id,
            'status' => 'suspended',
        ]))->assignRole('processing-officer');

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/assign-staff", [
                'assigned_to' => $suspended->uuid,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['assigned_to']]);
    }

    public function test_a_note_can_be_added_and_read_back(): void
    {
        $applicant = $this->applicant();
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/notes", [
                'body' => 'Applicant asked to delay medical until after Eid.',
                'category' => 'processing',
            ])
            ->assertCreated()
            ->assertJsonPath('data.author', $manager->name);

        $this->actingAs($manager)
            ->getJson("/api/v1/applicants/{$applicant->uuid}/notes")
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.category', 'processing');
    }

    public function test_a_finance_note_is_invisible_to_staff_without_finance_access(): void
    {
        $applicant = $this->applicant();

        Note::create([
            'company_id' => $this->company->id,
            'notable_type' => $applicant->getMorphClass(),
            'notable_id' => $applicant->id,
            'category' => 'finance',
            'visibility' => 'finance',
            'body' => 'Outstanding balance disputed by the family.',
            'author_name_snapshot' => 'Accounts',
        ]);

        // Data entry officers hold no accounting permission.
        $hidden = $this->actingAs($this->userWithRole('data-entry-officer'))
            ->getJson("/api/v1/applicants/{$applicant->uuid}/notes")
            ->assertOk();

        $this->assertCount(0, $hidden->json('data'));
        $this->assertStringNotContainsString('disputed', $hidden->getContent());

        $visible = $this->actingAs($this->userWithRole('accounts-manager'))
            ->getJson("/api/v1/applicants/{$applicant->uuid}/notes")
            ->assertOk();

        $this->assertCount(1, $visible->json('data'));
    }

    public function test_nobody_can_write_a_note_they_could_not_read(): void
    {
        $applicant = $this->applicant();

        $this->actingAs($this->userWithRole('data-entry-officer'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/notes", [
                'body' => 'Trying to write into a band I cannot read.',
                'visibility' => 'finance',
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['visibility']]);

        $this->assertSame(0, Note::count());
    }

    public function test_the_visibility_options_offered_match_what_the_reader_may_write(): void
    {
        $applicant = $this->applicant();

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

        $this->assertCount(1, $plain->json('meta.visibilities'));

        $finance = $this->actingAs($this->userWithRole('accounts-manager'))
            ->getJson("/api/v1/applicants/{$applicant->uuid}/notes")
            ->assertOk();

        $this->assertGreaterThan(1, count($finance->json('meta.visibilities')));
    }

    public function test_only_the_author_or_a_manager_can_remove_a_note(): void
    {
        $applicant = $this->applicant();
        $author = $this->userWithRole('processing-officer');

        $noteId = $this->actingAs($author)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/notes", ['body' => 'Mine to withdraw.'])
            ->assertCreated()
            ->json('data.id');

        $someoneElse = $this->userWithRole('processing-officer');

        $this->actingAs($someoneElse)
            ->deleteJson("/api/v1/applicants/{$applicant->uuid}/notes/{$noteId}")
            ->assertStatus(403);

        $this->actingAs($author)
            ->deleteJson("/api/v1/applicants/{$applicant->uuid}/notes/{$noteId}")
            ->assertOk();

        $this->assertSoftDeleted('notes', ['uuid' => $noteId]);
    }

    public function test_an_applicant_never_sees_internal_notes_in_the_portal(): void
    {
        $portalUser = tap(User::factory()->create([
            'company_id' => $this->company->id,
            'user_type' => 'applicant',
        ]))->assignRole('applicant');

        $applicant = $this->applicant(['user_id' => $portalUser->id]);

        Note::create([
            'company_id' => $this->company->id,
            'notable_type' => $applicant->getMorphClass(),
            'notable_id' => $applicant->id,
            'category' => 'compliance',
            'visibility' => 'staff',
            'body' => 'INTERNAL: documents look altered, verify with the issuer.',
            'author_name_snapshot' => 'Verification',
        ]);

        foreach (['dashboard', 'application', 'status', 'documents'] as $endpoint) {
            $content = $this->actingAs($portalUser)
                ->getJson("/api/v1/portal/{$endpoint}")
                ->assertOk()
                ->getContent();

            $this->assertStringNotContainsString('INTERNAL', $content);
            $this->assertStringNotContainsString('altered', $content);
        }
    }

    public function test_pinned_notes_lead_the_list(): void
    {
        $applicant = $this->applicant();
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/notes", [
            'body' => 'An ordinary note.',
        ])->assertCreated();

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/notes", [
            'body' => 'Do not call before 6pm — night shift.',
            'is_pinned' => true,
        ])->assertCreated();

        $response = $this->actingAs($manager)
            ->getJson("/api/v1/applicants/{$applicant->uuid}/notes")
            ->assertOk();

        $this->assertTrue($response->json('data.0.is_pinned'));
        $this->assertStringContainsString('night shift', $response->json('data.0.body'));
    }
}
