<?php

namespace App\Domain\Applicants\Models;

use App\Domain\Agents\Models\Agent;
use App\Domain\Applicants\Enums\ApplicantStatus;
use App\Domain\Billing\Models\Invoice;
use App\Domain\Identity\Models\User;
use App\Domain\Organization\Models\Branch;
use App\Domain\Organization\Models\Company;
use App\Domain\Payments\Models\Payment;
use App\Domain\Recruitment\Models\Employer;
use App\Domain\Recruitment\Models\JobDemand;
use App\Domain\Workflow\Models\ApplicantStageHistory;
use App\Domain\Workflow\Models\Pipeline;
use App\Domain\Workflow\Models\PipelineStage;
use App\Support\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class Applicant extends Model
{
    use HasFactory, HasUuid, SoftDeletes;

    protected $guarded = ['id', 'uuid'];

    protected function casts(): array
    {
        return [
            'status' => ApplicantStatus::class,
            'date_of_birth' => 'date',
            'passport_issue_date' => 'date',
            'passport_expiry_date' => 'date',
            'verified_at' => 'datetime',
            'stage_entered_at' => 'datetime',
            'has_overseas_experience' => 'boolean',
            'experience_years' => 'integer',
        ];
    }

    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }

    public function branch(): BelongsTo
    {
        return $this->belongsTo(Branch::class);
    }

    public function agent(): BelongsTo
    {
        return $this->belongsTo(Agent::class);
    }

    public function assignedStaff(): BelongsTo
    {
        return $this->belongsTo(User::class, 'assigned_to');
    }

    public function employer(): BelongsTo
    {
        return $this->belongsTo(Employer::class);
    }

    public function jobDemand(): BelongsTo
    {
        return $this->belongsTo(JobDemand::class);
    }

    public function invoices(): HasMany
    {
        return $this->hasMany(Invoice::class);
    }

    public function payments(): HasMany
    {
        return $this->hasMany(Payment::class);
    }

    public function mergedInto(): BelongsTo
    {
        return $this->belongsTo(self::class, 'merged_into_id');
    }

    public function pipeline(): BelongsTo
    {
        return $this->belongsTo(Pipeline::class);
    }

    public function currentStage(): BelongsTo
    {
        return $this->belongsTo(PipelineStage::class, 'current_stage_id');
    }

    public function stageHistories(): HasMany
    {
        return $this->hasMany(ApplicantStageHistory::class)->latest('changed_at');
    }

    /** Working days spent in the current stage, for SLA reporting (§87). */
    public function daysInCurrentStage(): ?int
    {
        return $this->stage_entered_at === null
            ? null
            : (int) $this->stage_entered_at->diffInDays(now());
    }

    public function isBreachingStageSla(): bool
    {
        $sla = $this->currentStage?->sla_days;

        return $sla !== null && ($this->daysInCurrentStage() ?? 0) > $sla;
    }

    public function scopeActive(Builder $query): Builder
    {
        return $query->whereNotIn('status', array_map(
            fn (ApplicantStatus $status) => $status->value,
            array_filter(ApplicantStatus::cases(), fn ($status) => $status->isClosed()),
        ));
    }

    public function age(): ?int
    {
        return $this->date_of_birth?->age;
    }

    public function passportExpiresWithin(int $days): bool
    {
        return $this->passport_expiry_date !== null
            && $this->passport_expiry_date->isBefore(now()->addDays($days));
    }

    public function hasValidPassport(): bool
    {
        return filled($this->passport_number)
            && $this->passport_expiry_date !== null
            && $this->passport_expiry_date->isFuture();
    }

    /**
     * Whether this applicant may be put forward for a job demand. The demand's
     * own intake rules are checked separately (§15, §16).
     *
     * @return array<int, string>
     */
    public function submissionBlockers(?JobDemand $demand = null): array
    {
        $blockers = [];

        if (! $this->status->isActive()) {
            $blockers[] = "The applicant file is {$this->status->label()}.";
        }

        if (! $this->hasValidPassport()) {
            $blockers[] = 'A valid, unexpired passport is required.';
        }

        if ($demand === null) {
            return $blockers;
        }

        $age = $this->age();

        if ($age !== null && $demand->minimum_age !== null && $age < $demand->minimum_age) {
            $blockers[] = "The applicant is below the minimum age of {$demand->minimum_age}.";
        }

        if ($age !== null && $demand->maximum_age !== null && $age > $demand->maximum_age) {
            $blockers[] = "The applicant is above the maximum age of {$demand->maximum_age}.";
        }

        if ($demand->gender_requirement !== 'any'
            && filled($this->gender)
            && $this->gender !== $demand->gender_requirement) {
            $blockers[] = "This demand requires {$demand->gender_requirement} candidates.";
        }

        return $blockers;
    }
}
