<?php

namespace Tests\Feature\PublicSite;

use App\Domain\Identity\Models\User;
use App\Domain\Organization\Models\Branch;
use App\Domain\Organization\Models\Company;
use App\Domain\Recruitment\Enums\JobDemandStatus;
use App\Domain\Recruitment\Models\Employer;
use App\Domain\Recruitment\Models\JobDemand;
use App\Domain\Website\Enums\RequisitionStatus;
use App\Domain\Website\Models\EmployerRequisition;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;

class EmployerRequisitionTest extends TestCase
{
    use RefreshDatabase;

    private Company $company;

    private Branch $branch;

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

        $this->company = Company::factory()->create();
        $this->branch = Branch::factory()->for($this->company)->create(['is_head_office' => true]);
    }

    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 payload(array $overrides = []): array
    {
        return array_merge([
            'company_name' => 'Al Rajhi Contracting',
            'country' => 'sa',
            'city' => 'Riyadh',
            'industry' => 'Construction',
            'company_address' => 'King Fahd Road, Riyadh',
            'contact_name' => 'Faisal Al Harbi',
            'contact_designation' => 'HR Manager',
            'contact_email' => 'faisal@alrajhi.test',
            'contact_phone' => '+966500000000',
            'urgency' => 'urgent',
            'consent' => true,
            'positions' => [
                [
                    'job_title' => 'Heavy Vehicle Driver',
                    'quantity' => 25,
                    'gender_preference' => 'male',
                    'minimum_age' => 24,
                    'maximum_age' => 45,
                    'minimum_experience_years' => 3,
                    'salary_amount' => 180000,
                    'salary_currency' => 'SAR',
                    'accommodation_provided' => true,
                    'food_provided' => true,
                    'required_documents' => ['Passport', 'Driving License'],
                ],
                [
                    'job_title' => 'Site Electrician',
                    'quantity' => 10,
                    'salary_amount' => 200000,
                    'salary_currency' => 'SAR',
                ],
            ],
        ], $overrides);
    }

    private function submit(array $overrides = [])
    {
        return $this->postJson('/api/v1/site/requisitions', $this->payload($overrides));
    }

    public function test_an_employer_can_submit_a_multi_position_requisition(): void
    {
        $response = $this->submit()->assertCreated();

        $this->assertSame(2, $response->json('data.positions'));
        $this->assertSame(35, $response->json('data.total_workers'));
        $this->assertNotEmpty($response->json('data.reference'));

        $requisition = EmployerRequisition::firstOrFail();
        $this->assertSame('REQ-SA-'.now()->year.'-00001', $requisition->requisition_no);
        $this->assertSame(RequisitionStatus::Submitted, $requisition->status);
        $this->assertSame('SA', $requisition->country);
        $this->assertSame(2, $requisition->positions()->count());

        // Submission itself is the first history entry.
        $this->assertDatabaseHas('employer_requisition_status_histories', [
            'employer_requisition_id' => $requisition->id,
            'to_status' => RequisitionStatus::Submitted->value,
        ]);
    }

    public function test_a_requisition_needs_at_least_one_position_and_consent(): void
    {
        $this->submit(['positions' => []])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['positions']]);

        $this->submit(['consent' => false])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['consent']]);

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

    public function test_each_position_must_carry_a_salary_and_currency(): void
    {
        $this->submit(['positions' => [['job_title' => 'Cleaner', 'quantity' => 5]]])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['positions.0.salary_amount', 'positions.0.salary_currency']]);
    }

    public function test_the_honeypot_rejects_bot_submissions(): void
    {
        $this->submit(['website_url' => 'http://spam.example'])->assertStatus(422);

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

    public function test_a_known_employer_is_linked_automatically(): void
    {
        $employer = Employer::factory()->for($this->company)->create([
            'company_name' => 'Al Rajhi Contracting',
            'country' => 'SA',
        ]);

        $this->submit()->assertCreated();

        $this->assertSame($employer->id, EmployerRequisition::firstOrFail()->employer_id);
    }

    public function test_the_confirmation_page_needs_the_token(): void
    {
        $reference = $this->submit()->assertCreated()->json('data.reference');

        $this->getJson("/api/v1/site/requisitions/{$reference}")
            ->assertOk()
            ->assertJsonPath('data.company_name', 'Al Rajhi Contracting')
            ->assertJsonPath('data.total_workers', 35);

        $this->getJson('/api/v1/site/requisitions/not-a-real-token')->assertStatus(404);
    }

    public function test_the_confirmation_page_leaks_no_internal_state(): void
    {
        $reference = $this->submit()->assertCreated()->json('data.reference');

        $requisition = EmployerRequisition::firstOrFail();
        $requisition->update([
            'status_reason' => 'INTERNAL: check their licence with the ministry.',
            'assigned_to' => $this->userWithRole('recruitment-manager')->id,
            'priority' => 'high',
        ]);

        $content = $this->getJson("/api/v1/site/requisitions/{$reference}")->assertOk()->getContent();

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

    public function test_the_admin_inbox_lists_requisitions(): void
    {
        $this->submit()->assertCreated();

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->getJson('/api/v1/employer-requisitions')
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.company_name', 'Al Rajhi Contracting')
            ->assertJsonPath('data.0.status', 'submitted');
    }

    public function test_the_inbox_is_limited_to_roles_that_may_see_it(): void
    {
        $this->submit()->assertCreated();

        $this->getJson('/api/v1/employer-requisitions')->assertStatus(401);

        // Cashiers have no requisition permission.
        $this->actingAs($this->userWithRole('cashier'))
            ->getJson('/api/v1/employer-requisitions')
            ->assertStatus(403);
    }

    public function test_a_requisition_walks_from_submitted_to_approved(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::UnderReview->value,
            ])->assertOk();

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::Approved->value,
            ])
            ->assertOk()
            ->assertJsonPath('data.status', 'approved')
            ->assertJsonPath('data.can_convert', true);

        $requisition->refresh();
        $this->assertNotNull($requisition->approved_at);
        $this->assertSame($manager->id, $requisition->approved_by);
    }

    public function test_a_submitted_requisition_cannot_jump_straight_to_approved(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::Approved->value,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['status']]);
    }

    public function test_requesting_information_needs_the_question_and_a_reason(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::InformationRequested->value,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['reason']]);

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::InformationRequested->value,
                'reason' => 'Licence details missing.',
                'information_requested' => 'Please send a copy of your labour recruitment licence.',
            ])
            ->assertOk()
            ->assertJsonPath('data.status', 'information_requested');

        $this->assertDatabaseHas('employer_requisition_status_histories', [
            'employer_requisition_id' => $requisition->id,
            'information_requested' => 'Please send a copy of your labour recruitment licence.',
        ]);
    }

    public function test_rejection_requires_a_reason_and_is_final(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $manager = $this->userWithRole('recruitment-manager');

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::Rejected->value,
                'reason' => 'Salary offered is below the minimum for this corridor.',
            ])->assertOk();

        $this->actingAs($manager)
            ->getJson("/api/v1/employer-requisitions/{$requisition->uuid}")
            ->assertOk()
            ->assertJsonPath('data.allowed_transitions', []);
    }

    public function test_converting_creates_one_draft_job_demand_per_position(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $manager = $this->userWithRole('recruitment-manager');
        $employer = Employer::factory()->for($this->company)->active()->create();

        foreach ([RequisitionStatus::UnderReview, RequisitionStatus::Approved] as $step) {
            $this->actingAs($manager)->postJson(
                "/api/v1/employer-requisitions/{$requisition->uuid}/status",
                ['status' => $step->value],
            )->assertOk();
        }

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/convert", [
                'employer_id' => $employer->uuid,
            ])
            ->assertOk()
            ->assertJsonPath('data.status', 'converted');

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

        $demand = JobDemand::where('job_title', 'Heavy Vehicle Driver')->firstOrFail();
        // Drafts: the agency's own fee and terms are not the employer's to set.
        $this->assertSame(JobDemandStatus::Draft, $demand->status);
        $this->assertSame(25, $demand->quantity_required);
        $this->assertSame(180000, (int) $demand->salary_amount);
        $this->assertSame($employer->id, $demand->employer_id);
        $this->assertNull($demand->recruitment_fee_amount);

        $this->assertNotNull($requisition->fresh()->converted_at);
    }

    public function test_an_unapproved_requisition_cannot_be_converted(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $employer = Employer::factory()->for($this->company)->active()->create();

        $this->actingAs($this->userWithRole('recruitment-manager'))
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/convert", [
                'employer_id' => $employer->uuid,
            ])
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['status']]);

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

    public function test_converting_twice_does_not_duplicate_job_demands(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $manager = $this->userWithRole('recruitment-manager');
        $employer = Employer::factory()->for($this->company)->active()->create();

        foreach ([RequisitionStatus::UnderReview, RequisitionStatus::Approved] as $step) {
            $this->actingAs($manager)->postJson(
                "/api/v1/employer-requisitions/{$requisition->uuid}/status",
                ['status' => $step->value],
            )->assertOk();
        }

        $this->actingAs($manager)->postJson(
            "/api/v1/employer-requisitions/{$requisition->uuid}/convert",
            ['employer_id' => $employer->uuid],
        )->assertOk();

        // Already converted, so the second attempt is refused outright.
        $this->actingAs($manager)->postJson(
            "/api/v1/employer-requisitions/{$requisition->uuid}/convert",
            ['employer_id' => $employer->uuid],
        )->assertStatus(422);

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

    public function test_a_role_without_approve_permission_cannot_approve(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();

        $this->actingAs($this->userWithRole('recruitment-manager'))->postJson(
            "/api/v1/employer-requisitions/{$requisition->uuid}/status",
            ['status' => RequisitionStatus::UnderReview->value],
        )->assertOk();

        // Support officers hold no requisition permissions at all.
        $this->actingAs($this->userWithRole('support-officer'))
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/status", [
                'status' => RequisitionStatus::Approved->value,
            ])
            ->assertStatus(403);
    }

    public function test_a_requisition_can_be_assigned_to_a_reviewer(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();
        $manager = $this->userWithRole('recruitment-manager');
        $officer = $this->userWithRole('processing-officer');

        $this->actingAs($manager)
            ->postJson("/api/v1/employer-requisitions/{$requisition->uuid}/assign", [
                'assigned_to' => $officer->uuid,
                'priority' => 'high',
            ])
            ->assertOk()
            ->assertJsonPath('data.priority', 'high');

        $this->assertSame($officer->id, $requisition->fresh()->assigned_to);
    }

    public function test_requisitions_from_another_company_are_not_reachable(): void
    {
        $this->submit()->assertCreated();
        $requisition = EmployerRequisition::firstOrFail();

        $outsider = tap(User::factory()->create([
            'company_id' => Company::factory()->create()->id,
        ]))->assignRole('recruitment-manager');

        $this->actingAs($outsider)
            ->getJson("/api/v1/employer-requisitions/{$requisition->uuid}")
            ->assertStatus(403);
    }
}
