<?php

namespace Tests\Feature\Workflow;

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Identity\Models\User;
use App\Domain\Organization\Models\Branch;
use App\Domain\Organization\Models\Company;
use App\Domain\Workflow\Models\Pipeline;
use App\Domain\Workflow\Models\PipelineStage;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\PipelineSeeder;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ApplicantStageApiTest extends TestCase
{
    use RefreshDatabase;

    private Company $company;

    private Branch $branch;

    private Pipeline $pipeline;

    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();

        // Seed the shipped default pipeline against this company.
        $this->seed(PipelineSeeder::class);
        $this->pipeline = Pipeline::where('company_id', $this->company->id)->firstOrFail();
    }

    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 stage(string $code): PipelineStage
    {
        return PipelineStage::where('pipeline_id', $this->pipeline->id)->where('code', $code)->firstOrFail();
    }

    public function test_the_default_pipeline_seeds_the_full_flow_as_configuration(): void
    {
        $this->assertTrue($this->pipeline->is_default);
        $this->assertGreaterThan(20, $this->pipeline->stages()->count());
        $this->assertSame('lead', $this->pipeline->initialStage()->code);

        // Exception stages exist and sit outside the happy path.
        $this->assertTrue($this->stage('on_hold')->is_exception);
        $this->assertTrue($this->stage('completed')->is_terminal);
    }

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

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/applicants/{$applicant->uuid}/pipeline", [
                'pipeline_id' => $this->pipeline->uuid,
            ])
            ->assertOk()
            ->assertJsonPath('data.current_stage.name', 'Lead');
    }

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

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/pipeline", [
            'pipeline_id' => $this->pipeline->uuid,
        ])->assertOk();

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

        $this->assertSame('Lead', $response->json('data.current_stage.name'));
        $this->assertNotEmpty($response->json('data.available_transitions'));
        $this->assertCount(1, $response->json('data.history'));
        // Overriding requirements is a director-level right, not a manager one.
        $this->assertFalse($response->json('data.can_override'));

        $this->actingAs($this->userWithRole('director'))
            ->getJson("/api/v1/applicants/{$applicant->uuid}/stage")
            ->assertOk()
            ->assertJsonPath('data.can_override', true);
    }

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

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/pipeline", [
            'pipeline_id' => $this->pipeline->uuid,
        ])->assertOk();

        $this->actingAs($manager)
            ->getJson("/api/v1/applicants/{$applicant->uuid}/stage")
            ->assertOk()
            ->assertJsonPath('data.current_stage.name', 'Lead')
            ->assertJsonPath('data.current_stage.applicant_label', 'Enquiry received');
    }

    public function test_a_move_blocked_by_requirements_returns_the_reasons(): void
    {
        // Job selection needs a verified profile and an open demand.
        $applicant = $this->applicant(['verified_at' => null]);
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/pipeline", [
            'pipeline_id' => $this->pipeline->uuid,
        ])->assertOk();

        // Walk to the stage before job selection.
        foreach (['pre_registration', 'profile_created', 'initial_verification'] as $code) {
            $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage($code)->uuid,
            ])->assertOk();
        }

        $response = $this->actingAs($manager)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage('job_selected')->uuid,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['requirements']]);

        $this->assertContains(
            'The applicant profile has been verified by staff.',
            $response->json('errors.requirements'),
        );
    }

    public function test_a_user_without_override_permission_cannot_bypass_requirements(): void
    {
        $applicant = $this->applicant(['verified_at' => null]);
        // Data entry officers hold neither change_stage nor override.
        $officer = $this->userWithRole('data-entry-officer');

        $this->actingAs($officer)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage('pre_registration')->uuid,
                'override' => true,
            ])
            ->assertStatus(403);
    }

    public function test_an_override_flag_from_an_unprivileged_role_is_ignored(): void
    {
        $applicant = $this->applicant(['verified_at' => null]);
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/pipeline", [
            'pipeline_id' => $this->pipeline->uuid,
        ])->assertOk();

        foreach (['pre_registration', 'profile_created', 'initial_verification'] as $code) {
            $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage($code)->uuid,
            ])->assertOk();
        }

        // Processing officers may change stages but hold no override right, so
        // the flag must not let them through.
        $officer = $this->userWithRole('processing-officer');

        $this->actingAs($officer)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage('job_selected')->uuid,
                'override' => true,
                'reason' => 'Trying anyway',
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['requirements']]);
    }

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

        $this->actingAs($manager)->postJson("/api/v1/applicants/{$applicant->uuid}/pipeline", [
            'pipeline_id' => $this->pipeline->uuid,
        ])->assertOk();

        $this->actingAs($manager)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage('on_hold')->uuid,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['reason']]);

        $this->actingAs($manager)
            ->postJson("/api/v1/applicants/{$applicant->uuid}/stage", [
                'stage_id' => $this->stage('on_hold')->uuid,
                'reason' => 'Applicant unreachable for three weeks.',
            ])
            ->assertOk()
            ->assertJsonPath('data.current_stage.name', 'On Hold');
    }

    public function test_the_pipeline_list_exposes_stages_and_known_requirement_types(): void
    {
        $response = $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson('/api/v1/pipelines')
            ->assertOk();

        $this->assertSame('Standard Overseas Recruitment', $response->json('data.0.name'));
        $this->assertNotEmpty($response->json('data.0.stages'));
        $this->assertNotEmpty($response->json('meta.requirement_types'));
    }

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

        $this->getJson("/api/v1/applicants/{$applicant->uuid}/stage")->assertStatus(401);

        // Payroll officers have no applicant.view permission at all.
        $this->actingAs($this->userWithRole('payroll-officer'))
            ->getJson("/api/v1/applicants/{$applicant->uuid}/stage")
            ->assertStatus(403);
    }
}
