<?php

namespace App\Domain\Website\Services;

use App\Domain\Identity\Models\User;
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 App\Domain\Website\Models\EmployerRequisitionPosition;
use App\Domain\Website\Models\EmployerRequisitionStatusHistory;
use App\Domain\Website\Models\JobCategory;
use App\Support\Sequences\SequenceGenerator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class RequisitionService
{
    public function __construct(private readonly SequenceGenerator $sequences) {}

    /**
     * Records a public submission (§144A).
     *
     * @param  array<string, mixed>  $attributes
     * @param  array<int, array<string, mixed>>  $positions
     */
    public function submit(Company $company, array $attributes, array $positions, ?string $ip = null): EmployerRequisition
    {
        return DB::transaction(function () use ($company, $attributes, $positions, $ip) {
            $country = strtoupper((string) $attributes['country']);

            $requisition = EmployerRequisition::create(array_merge($attributes, [
                'company_id' => $company->id,
                'country' => $country,
                'requisition_no' => $this->generateNumber($country),
                // Unguessable: it is what lets someone revisit their confirmation.
                'public_token' => Str::random(48),
                'status' => RequisitionStatus::Submitted->value,
                'submitted_at' => now(),
                'source_ip' => $ip,
                // An existing employer with the same name and country is linked
                // so staff see the history rather than a stranger's enquiry.
                'employer_id' => $this->matchEmployer($company->id, $attributes['company_name'], $country),
            ]));

            foreach (array_values($positions) as $index => $position) {
                $requisition->positions()->create(array_merge($position, [
                    'job_category_id' => $this->resolveCategory($position['job_category_id'] ?? null),
                    'sort_order' => $index,
                ]));
            }

            $this->recordHistory($requisition, null, RequisitionStatus::Submitted, 'Submitted from the website.', null, null);

            return $requisition->load('positions');
        });
    }

    /** @throws ValidationException */
    public function changeStatus(
        EmployerRequisition $requisition,
        RequisitionStatus $target,
        ?string $reason,
        ?User $actor = null,
        ?string $informationRequested = null,
    ): EmployerRequisition {
        $current = $requisition->status;

        if ($current === $target) {
            throw ValidationException::withMessages([
                'status' => ["The requisition is already {$target->label()}."],
            ]);
        }

        if (! $current->canTransitionTo($target)) {
            throw ValidationException::withMessages([
                'status' => ["A requisition cannot move from {$current->label()} to {$target->label()}."],
            ]);
        }

        if ($target->requiresReason() && blank($reason)) {
            throw ValidationException::withMessages([
                'reason' => ["A reason is required to mark a requisition {$target->label()}."],
            ]);
        }

        if ($target === RequisitionStatus::InformationRequested && blank($informationRequested)) {
            throw ValidationException::withMessages([
                'information_requested' => ['Say what information the employer needs to provide.'],
            ]);
        }

        return DB::transaction(function () use ($requisition, $current, $target, $reason, $actor, $informationRequested) {
            $requisition->status = $target;
            $requisition->status_reason = $reason;

            if ($target === RequisitionStatus::UnderReview && $requisition->reviewed_at === null) {
                $requisition->reviewed_at = now();
                $requisition->reviewed_by = $actor?->getKey();
            }

            if ($target === RequisitionStatus::Approved) {
                $requisition->approved_at = now();
                $requisition->approved_by = $actor?->getKey();
            }

            $requisition->save();

            $this->recordHistory($requisition, $current, $target, $reason, $informationRequested, $actor);

            return $requisition;
        });
    }

    /**
     * Turns an approved requisition into real job demands, one per position
     * (§144K). Each demand starts as a draft so the recruitment team sets the
     * agency's own commercials before it goes anywhere.
     *
     * @throws ValidationException
     */
    public function convert(EmployerRequisition $requisition, Employer $employer, ?User $actor = null): EmployerRequisition
    {
        if (! $requisition->status->canConvert()) {
            throw ValidationException::withMessages([
                'status' => ["Only an approved requisition can be converted. This one is {$requisition->status->label()}."],
            ]);
        }

        if ($employer->company_id !== $requisition->company_id) {
            throw ValidationException::withMessages([
                'employer_id' => ['That employer belongs to another company.'],
            ]);
        }

        $requisition->loadMissing('positions');

        return DB::transaction(function () use ($requisition, $employer, $actor) {
            foreach ($requisition->positions as $position) {
                if ($position->isConverted()) {
                    continue;
                }

                $demand = JobDemand::create([
                    'demand_code' => $this->nextDemandCode($requisition->company_id, $requisition->country),
                    'company_id' => $requisition->company_id,
                    'branch_id' => $requisition->branch_id,
                    'employer_id' => $employer->getKey(),
                    'owner_id' => $actor?->getKey(),
                    'job_title' => $position->job_title,
                    'country' => $requisition->country,
                    'city' => $requisition->city,
                    'industry' => $requisition->industry,
                    'job_category_id' => $position->job_category_id,
                    'quantity_required' => $position->quantity,
                    'gender_requirement' => $position->gender_preference,
                    'minimum_age' => $position->minimum_age,
                    'maximum_age' => $position->maximum_age,
                    'salary_amount' => $position->salary_amount,
                    'salary_currency' => $position->salary_currency,
                    'salary_period' => $position->salary_period,
                    'overtime_details' => $position->overtime_details,
                    'working_hours_per_day' => $position->working_hours_per_day,
                    'working_days_per_week' => $position->working_days_per_week,
                    'weekly_holiday' => $position->weekly_holiday,
                    'accommodation_provided' => $position->accommodation_provided,
                    'food_provided' => $position->food_provided,
                    'transport_provided' => $position->transport_provided,
                    'medical_provided' => $position->medical_provided,
                    'insurance_provided' => $position->insurance_provided,
                    'airfare_terms' => $position->airfare_provided ? 'Provided by employer' : null,
                    'contract_duration_months' => $position->contract_duration_months,
                    'probation_months' => $position->probation_months,
                    'qualification' => $position->qualification,
                    'required_experience_years' => $position->minimum_experience_years,
                    'skills' => $position->required_skills,
                    'application_deadline' => $requisition->application_deadline,
                    'interview_date' => $requisition->preferred_interview_date,
                    'deployment_target_date' => $requisition->expected_deployment_date,
                    // Draft: the agency's fee and terms are not the employer's to set.
                    'status' => JobDemandStatus::Draft->value,
                    'created_by' => $actor?->getKey(),
                    'updated_by' => $actor?->getKey(),
                ]);

                $position->update(['job_demand_id' => $demand->getKey()]);
            }

            $previous = $requisition->status;
            $requisition->status = RequisitionStatus::Converted;
            $requisition->employer_id = $employer->getKey();
            $requisition->converted_at = now();
            $requisition->save();

            $this->recordHistory(
                $requisition,
                $previous,
                RequisitionStatus::Converted,
                "Converted into {$requisition->positions->count()} job demand(s).",
                null,
                $actor,
            );

            return $requisition->load('positions.jobDemand');
        });
    }

    /**
     * Demand codes may already exist from imported or manually created records,
     * so the number is checked against the table rather than trusted blindly.
     */
    private function nextDemandCode(int $companyId, string $country): string
    {
        return $this->sequences->nextUnique(
            'job_demand',
            'DEM',
            [$country, now()->year],
            4,
            fn (string $code) => JobDemand::where('company_id', $companyId)
                ->where('demand_code', $code)
                ->exists(),
        );
    }

    public function generateNumber(string $country): string
    {
        return $this->sequences->next('employer_requisition', 'REQ', [strtoupper($country), now()->year], 5);
    }

    private function matchEmployer(int $companyId, string $name, string $country): ?int
    {
        return Employer::where('company_id', $companyId)
            ->where('company_name', $name)
            ->where('country', $country)
            ->value('id');
    }

    private function resolveCategory(?string $uuid): ?int
    {
        return $uuid === null ? null : JobCategory::where('uuid', $uuid)->value('id');
    }

    private function recordHistory(
        EmployerRequisition $requisition,
        ?RequisitionStatus $from,
        RequisitionStatus $to,
        ?string $reason,
        ?string $informationRequested,
        ?User $actor,
    ): void {
        EmployerRequisitionStatusHistory::create([
            'employer_requisition_id' => $requisition->getKey(),
            'from_status' => $from?->value,
            'to_status' => $to->value,
            'reason' => $reason,
            'information_requested' => $informationRequested,
            'changed_by' => $actor?->getKey(),
            'changed_at' => now(),
        ]);
    }
}
