<?php

namespace App\Domain\Recruitment\Services;

use App\Domain\Audit\Enums\ActivityCategory;
use App\Domain\Audit\Services\ActivityLogger;
use App\Domain\Identity\Models\User;
use App\Domain\Recruitment\Enums\JobDemandStatus;
use App\Domain\Recruitment\Models\Employer;
use App\Domain\Recruitment\Models\JobDemand;
use App\Support\Sequences\SequenceGenerator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class JobDemandService
{
    public function __construct(
        private readonly SequenceGenerator $sequences,
        private readonly ActivityLogger $activity,
    ) {}

    /** @param array<string, mixed> $attributes */
    public function create(array $attributes, Employer $employer, ?User $actor = null): JobDemand
    {
        // A demand may only be raised against an employer the CRM says is ready:
        // active, compliance cleared and agreement live (§14).
        $this->assertEmployerReady($employer);

        return DB::transaction(function () use ($attributes, $employer, $actor) {
            $attributes['status'] = JobDemandStatus::Draft->value;
            $attributes['employer_id'] = $employer->getKey();
            $attributes['country'] = strtoupper($attributes['country'] ?? $employer->country);
            $attributes['demand_code'] ??= $this->generateCode($attributes['country']);
            $attributes['created_by'] = $actor?->getKey();
            $attributes['updated_by'] = $actor?->getKey();

            return JobDemand::create($attributes);
        });
    }

    /** @param array<string, mixed> $attributes */
    public function update(JobDemand $demand, array $attributes, ?User $actor = null): JobDemand
    {
        // Identity, employer, status and the pipeline counters are never editable here.
        unset(
            $attributes['demand_code'],
            $attributes['company_id'],
            $attributes['employer_id'],
            $attributes['status'],
            $attributes['approved_by'],
            $attributes['approved_at'],
            $attributes['shortlisted_count'],
            $attributes['submitted_count'],
            $attributes['interviewed_count'],
            $attributes['selected_count'],
            $attributes['visa_approved_count'],
            $attributes['deployed_count'],
        );

        // Once approved, the employment terms are what candidates were told;
        // only scheduling, notes and ownership stay editable.
        if (! $demand->status->allowsTermsEditing()) {
            $attributes = array_intersect_key($attributes, array_flip([
                'application_deadline',
                'interview_date',
                'deployment_target_date',
                'owner_id',
                'notes',
            ]));
        }

        if ($attributes === []) {
            throw ValidationException::withMessages([
                'status' => ["A {$demand->status->label()} demand only allows scheduling and notes to change."],
            ]);
        }

        return DB::transaction(function () use ($demand, $attributes, $actor) {
            if (isset($attributes['country'])) {
                $attributes['country'] = strtoupper($attributes['country']);
            }

            $attributes['updated_by'] = $actor?->getKey();
            $demand->fill($attributes)->save();

            return $demand;
        });
    }

    /** @throws ValidationException */
    public function changeStatus(
        JobDemand $demand,
        JobDemandStatus $target,
        ?string $reason,
        ?User $actor = null,
    ): JobDemand {
        $current = $demand->status;

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

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

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

        // Re-check the employer at the moment of approval: it may have been put
        // on hold or blacklisted since the demand was drafted.
        if ($target->requiresApproval()) {
            $this->assertEmployerReady($demand->employer);
        }

        return DB::transaction(function () use ($demand, $current, $target, $reason, $actor) {
            $demand->status = $target;
            $demand->status_reason = $reason;
            $demand->updated_by = $actor?->getKey();

            if ($target === JobDemandStatus::Approved && $demand->approved_at === null) {
                $demand->approved_by = $actor?->getKey();
                $demand->approved_at = now();
            }

            $demand->save();

            $this->activity->log(ActivityCategory::Recruitment, 'job_demand.status_changed', [
                'subject' => $demand,
                'parent' => $demand->employer,
                'actor' => $actor,
                'title' => 'Job demand status changed',
                'description' => "{$current->label()} → {$target->label()}",
                'old' => ['status' => $current->value],
                'new' => ['status' => $target->value],
                'metadata' => $reason ? ['reason' => $reason] : null,
            ]);

            return $demand;
        });
    }

    public function generateCode(string $country): string
    {
        return $this->sequences->next('job_demand', 'DEM', [strtoupper($country), now()->year], 4);
    }

    /** @throws ValidationException */
    private function assertEmployerReady(Employer $employer): void
    {
        if ($employer->canAcceptNewDemand()) {
            return;
        }

        throw ValidationException::withMessages([
            'employer_id' => $employer->demandBlockers(),
        ]);
    }
}
