<?php

namespace Tests\Feature\Recruitment;

use App\Domain\Identity\Models\User;
use App\Domain\Organization\Models\Branch;
use App\Domain\Organization\Models\Company;
use App\Domain\Recruitment\Enums\ComplianceStatus;
use App\Domain\Recruitment\Enums\EmployerStatus;
use App\Domain\Recruitment\Enums\JobDemandStatus;
use App\Domain\Recruitment\Models\Employer;
use App\Domain\Recruitment\Models\JobDemand;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class JobDemandApiTest extends TestCase
{
    use RefreshDatabase;

    private Company $company;

    private Branch $branch;

    private Employer $employer;

    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();
        $this->employer = Employer::factory()->for($this->company)->active()->create([
            'branch_id' => $this->branch->id,
            'country' => 'SA',
        ]);
    }

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

    private function demand(array $attributes = []): JobDemand
    {
        return JobDemand::factory()->for($this->company)->create(array_merge([
            'employer_id' => $this->employer->id,
            'branch_id' => $this->branch->id,
        ], $attributes));
    }

    private function payload(array $overrides = []): array
    {
        return array_merge([
            'employer_id' => $this->employer->uuid,
            'job_title' => 'Heavy Vehicle Driver',
            'quantity_required' => 25,
            'salary_amount' => 180000,
            'salary_currency' => 'SAR',
            'minimum_age' => 24,
            'maximum_age' => 45,
        ], $overrides);
    }

    public function test_a_demand_is_created_as_a_draft_with_a_country_and_year_scoped_code(): void
    {
        $response = $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson('/api/v1/job-demands', $this->payload());

        $response->assertCreated()
            ->assertJsonPath('data.status', 'draft')
            ->assertJsonPath('data.country', 'SA')
            ->assertJsonPath('data.accepts_candidates', false)
            ->assertJsonPath('data.pipeline.required', 25)
            ->assertJsonPath('data.pipeline.remaining', 25);

        $this->assertSame('DEM-SA-'.now()->year.'-0001', JobDemand::firstOrFail()->demand_code);
    }

    public function test_a_demand_cannot_be_raised_against_an_unready_employer(): void
    {
        $blocked = Employer::factory()->for($this->company)->create([
            'branch_id' => $this->branch->id,
            'status' => EmployerStatus::Prospect->value,
            'compliance_status' => ComplianceStatus::Pending->value,
        ]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson('/api/v1/job-demands', $this->payload(['employer_id' => $blocked->uuid]))
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['employer_id']]);

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

    public function test_the_country_defaults_to_the_employers_country(): void
    {
        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson('/api/v1/job-demands', $this->payload(['country' => null]))
            ->assertCreated()
            ->assertJsonPath('data.country', 'SA');
    }

    public function test_creation_validates_input(): void
    {
        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson('/api/v1/job-demands', [
                'employer_id' => $this->employer->uuid,
                'job_title' => '',
                'quantity_required' => 0,
                'minimum_age' => 40,
                'maximum_age' => 30,
                'salary_amount' => 5000,
                'working_days_per_week' => 9,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => [
                'job_title', 'quantity_required', 'maximum_age', 'salary_currency', 'working_days_per_week',
            ]]);
    }

    public function test_a_demand_walks_from_draft_to_open(): void
    {
        $manager = $this->userWithRole('recruitment-manager');
        $uuid = $this->actingAs($manager)
            ->postJson('/api/v1/job-demands', $this->payload())
            ->assertCreated()->json('data.id');

        foreach ([JobDemandStatus::PendingVerification, JobDemandStatus::Approved, JobDemandStatus::Open] as $step) {
            $this->actingAs($manager)
                ->postJson("/api/v1/job-demands/{$uuid}/status", ['status' => $step->value])
                ->assertOk()
                ->assertJsonPath('data.status', $step->value);
        }

        $demand = JobDemand::where('uuid', $uuid)->firstOrFail();
        $this->assertSame(JobDemandStatus::Open, $demand->status);
        $this->assertNotNull($demand->approved_at);
        $this->assertSame($manager->id, $demand->approved_by);
    }

    public function test_a_draft_cannot_jump_straight_to_open(): void
    {
        $demand = $this->demand();

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/job-demands/{$demand->uuid}/status", ['status' => JobDemandStatus::Open->value])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['status']]);

        $this->assertSame(JobDemandStatus::Draft, $demand->fresh()->status);
    }

    public function test_approval_is_refused_when_the_employer_has_since_been_blocked(): void
    {
        $demand = $this->demand(['status' => JobDemandStatus::PendingVerification->value]);

        // The employer is blacklisted after the demand was drafted.
        $this->employer->update([
            'status' => EmployerStatus::Blacklisted->value,
            'blacklisted_at' => now()->toDateString(),
        ]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/job-demands/{$demand->uuid}/status", ['status' => JobDemandStatus::Approved->value])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['employer_id']]);

        $this->assertSame(JobDemandStatus::PendingVerification, $demand->fresh()->status);
    }

    public function test_pausing_and_closing_require_a_reason(): void
    {
        $demand = $this->demand(['status' => JobDemandStatus::Open->value]);
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)
            ->postJson("/api/v1/job-demands/{$demand->uuid}/status", ['status' => JobDemandStatus::Paused->value])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['reason']]);

        $this->actingAs($manager)
            ->postJson("/api/v1/job-demands/{$demand->uuid}/status", [
                'status' => JobDemandStatus::Paused->value,
                'reason' => 'Employer asked to hold intake until visa quota is confirmed.',
            ])
            ->assertOk()
            ->assertJsonPath('data.status', 'paused')
            ->assertJsonPath('data.accepts_candidates', false);
    }

    public function test_a_closed_demand_is_final(): void
    {
        $demand = $this->demand(['status' => JobDemandStatus::Closed->value]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/job-demands/{$demand->uuid}/status", [
                'status' => JobDemandStatus::Open->value,
            ])
            ->assertStatus(422);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson("/api/v1/job-demands/{$demand->uuid}")
            ->assertOk()
            ->assertJsonPath('data.allowed_transitions', []);
    }

    public function test_terms_are_frozen_once_a_demand_is_approved(): void
    {
        $demand = $this->demand([
            'status' => JobDemandStatus::Open->value,
            'salary_amount' => 180000,
            'quantity_required' => 25,
        ]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->patchJson("/api/v1/job-demands/{$demand->uuid}", [
                'salary_amount' => 90000,
                'quantity_required' => 5,
                'interview_date' => '2026-10-01',
            ])
            ->assertOk()
            ->assertJsonPath('data.interview_date', '2026-10-01');

        $demand->refresh();
        $this->assertSame(180000, (int) $demand->salary_amount);
        $this->assertSame(25, $demand->quantity_required);
    }

    public function test_terms_remain_editable_while_a_demand_is_a_draft(): void
    {
        $demand = $this->demand(['salary_amount' => 180000]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->patchJson("/api/v1/job-demands/{$demand->uuid}", ['salary_amount' => 200000])
            ->assertOk()
            ->assertJsonPath('data.salary_amount', 200000);
    }

    public function test_an_update_with_only_frozen_fields_is_refused(): void
    {
        $demand = $this->demand(['status' => JobDemandStatus::Open->value]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->patchJson("/api/v1/job-demands/{$demand->uuid}", ['salary_amount' => 1])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['status']]);
    }

    public function test_the_code_employer_and_pipeline_counters_cannot_be_rewritten(): void
    {
        $other = Employer::factory()->for($this->company)->active()->create();
        $demand = $this->demand(['demand_code' => 'DEM-SA-2026-0099', 'deployed_count' => 3]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->patchJson("/api/v1/job-demands/{$demand->uuid}", [
                'job_title' => 'Renamed Role',
                'demand_code' => 'DEM-HACK-2026-0001',
                'employer_id' => $other->uuid,
                'deployed_count' => 99,
                'status' => JobDemandStatus::Open->value,
            ])
            ->assertOk()
            ->assertJsonPath('data.job_title', 'Renamed Role');

        $demand->refresh();
        $this->assertSame('DEM-SA-2026-0099', $demand->demand_code);
        $this->assertSame($this->employer->id, $demand->employer_id);
        $this->assertSame(3, $demand->deployed_count);
        $this->assertSame(JobDemandStatus::Draft, $demand->status);
    }

    public function test_intake_is_blocked_once_every_position_is_deployed(): void
    {
        $demand = $this->demand([
            'status' => JobDemandStatus::Open->value,
            'quantity_required' => 10,
            'deployed_count' => 10,
        ]);

        $response = $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson("/api/v1/job-demands/{$demand->uuid}")
            ->assertOk();

        $this->assertFalse($response->json('data.accepts_candidates'));
        $this->assertSame(0, $response->json('data.pipeline.remaining'));
        $this->assertTrue($response->json('data.pipeline.fully_deployed'));
        $this->assertContains('All positions have been deployed.', $response->json('data.intake_blockers'));
    }

    public function test_a_passed_deadline_blocks_intake_and_over_selection_is_flagged(): void
    {
        $demand = $this->demand([
            'status' => JobDemandStatus::Open->value,
            'quantity_required' => 5,
            'selected_count' => 8,
            'application_deadline' => now()->subDay()->toDateString(),
        ]);

        $response = $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson("/api/v1/job-demands/{$demand->uuid}")
            ->assertOk();

        $this->assertTrue($response->json('data.deadline_passed'));
        $this->assertTrue($response->json('data.pipeline.over_selected'));
        $this->assertContains('The application deadline has passed.', $response->json('data.intake_blockers'));
    }

    public function test_an_open_demand_within_deadline_accepts_candidates(): void
    {
        $demand = $this->demand([
            'status' => JobDemandStatus::Open->value,
            'quantity_required' => 10,
            'deployed_count' => 2,
            'application_deadline' => now()->addMonth()->toDateString(),
        ]);

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson("/api/v1/job-demands/{$demand->uuid}")
            ->assertOk()
            ->assertJsonPath('data.accepts_candidates', true)
            ->assertJsonPath('data.intake_blockers', [])
            ->assertJsonPath('data.pipeline.remaining', 8);
    }

    public function test_a_role_without_approve_permission_cannot_approve_a_demand(): void
    {
        $demand = $this->demand(['status' => JobDemandStatus::PendingVerification->value]);

        // Processing officers may view demands but not approve them.
        $this->actingAs($this->userWithRole('processing-officer'))
            ->postJson("/api/v1/job-demands/{$demand->uuid}/status", [
                'status' => JobDemandStatus::Approved->value,
            ])
            ->assertStatus(403);
    }

    public function test_listing_is_scoped_and_filterable(): void
    {
        $this->demand(['job_title' => 'Welder', 'status' => JobDemandStatus::Open->value]);
        $this->demand(['job_title' => 'Cleaner', 'country' => 'AE']);
        JobDemand::factory()->create(); // another company

        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)->getJson('/api/v1/job-demands')
            ->assertOk()->assertJsonCount(2, 'data');

        $this->actingAs($manager)->getJson('/api/v1/job-demands?open_only=1')
            ->assertOk()->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.job_title', 'Welder');

        $this->actingAs($manager)->getJson('/api/v1/job-demands?country=ae')
            ->assertOk()->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.job_title', 'Cleaner');

        $this->actingAs($manager)->getJson("/api/v1/job-demands?employer_id={$this->employer->uuid}")
            ->assertOk()->assertJsonCount(2, 'data');

        $this->actingAs($manager)->getJson('/api/v1/job-demands?search=Weld')
            ->assertOk()->assertJsonCount(1, 'data');
    }

    public function test_agency_commercials_are_hidden_from_read_only_roles(): void
    {
        $demand = $this->demand(['recruitment_fee_amount' => 15000000, 'fee_currency' => 'BDT']);

        $readOnly = $this->actingAs($this->userWithRole('auditor'))
            ->getJson("/api/v1/job-demands/{$demand->uuid}")
            ->assertOk();

        $this->assertArrayNotHasKey('recruitment_fee_amount', $readOnly->json('data'));

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson("/api/v1/job-demands/{$demand->uuid}")
            ->assertOk()
            ->assertJsonPath('data.recruitment_fee_amount', 15000000);
    }

    public function test_demand_endpoints_require_authentication_and_permission(): void
    {
        $this->getJson('/api/v1/job-demands')->assertStatus(401);

        $this->actingAs($this->userWithRole('cashier'))
            ->getJson('/api/v1/job-demands')
            ->assertStatus(403);
    }
}
