<?php

namespace App\Domain\Processing\Services;

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Audit\Enums\ActivityCategory;
use App\Domain\Audit\Services\ActivityLogger;
use App\Domain\Identity\Models\User;
use App\Domain\Processing\Models\PassportCustodyEvent;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/**
 * The passport custody register (§24).
 *
 * A worker's passport is their most valuable document and holding it is a
 * responsibility, so every movement is recorded and nothing is ever deleted.
 */
class PassportCustodyService
{
    public const RECEIVED = 'received';

    public const HANDED_OVER = 'handed_over';

    public const RETURNED = 'returned';

    public function __construct(private readonly ActivityLogger $activity) {}

    /** @throws ValidationException */
    public function record(
        Applicant $applicant,
        string $eventType,
        User $actor,
        array $attributes = [],
    ): PassportCustodyEvent {
        if (! in_array($eventType, [self::RECEIVED, self::HANDED_OVER, self::RETURNED], true)) {
            throw ValidationException::withMessages(['event_type' => ['Unknown custody movement.']]);
        }

        $passportNumber = $attributes['passport_number'] ?? $applicant->passport_number;

        if (blank($passportNumber)) {
            throw ValidationException::withMessages([
                'passport_number' => ['Record the passport number being taken in.'],
            ]);
        }

        $holding = $this->isHeld($applicant);

        // Taking in a passport already logged as held, or handing out one that
        // is not held, means the register has lost track of a real document.
        if ($eventType === self::RECEIVED && $holding) {
            throw ValidationException::withMessages([
                'event_type' => ['This passport is already recorded as held by the agency.'],
            ]);
        }

        if ($eventType !== self::RECEIVED && ! $holding) {
            throw ValidationException::withMessages([
                'event_type' => ['The agency is not currently recorded as holding this passport.'],
            ]);
        }

        if ($eventType !== self::RECEIVED && blank($attributes['acknowledgement'] ?? null)) {
            throw ValidationException::withMessages([
                'acknowledgement' => ['Record who acknowledged receiving the passport.'],
            ]);
        }

        return DB::transaction(function () use ($applicant, $eventType, $actor, $attributes, $passportNumber) {
            $event = PassportCustodyEvent::create([
                'company_id' => $applicant->company_id,
                'applicant_id' => $applicant->getKey(),
                'event_type' => $eventType,
                'passport_number' => $passportNumber,
                'handled_by' => $actor->getKey(),
                'counterparty' => $attributes['counterparty'] ?? null,
                'stored_location' => $attributes['stored_location'] ?? null,
                'reason' => $attributes['reason'] ?? null,
                'acknowledgement' => $attributes['acknowledgement'] ?? null,
                'notes' => $attributes['notes'] ?? null,
                'occurred_at' => $attributes['occurred_at'] ?? now(),
            ]);

            $applicant->update([
                'passport_status' => match ($eventType) {
                    self::RECEIVED => 'held_by_agency',
                    self::RETURNED => 'returned',
                    default => 'with_third_party',
                },
            ]);

            $this->activity->log(ActivityCategory::Processing, "passport.{$eventType}", [
                'subject' => $applicant,
                'actor' => $actor,
                'title' => match ($eventType) {
                    self::RECEIVED => 'Passport taken into custody',
                    self::HANDED_OVER => 'Passport handed over',
                    self::RETURNED => 'Passport returned to the applicant',
                },
                'description' => $attributes['counterparty'] ?? $attributes['stored_location'] ?? null,
                'visibility' => 'shared',
            ]);

            return $event;
        });
    }

    /** Whether the agency currently holds this applicant's passport. */
    public function isHeld(Applicant $applicant): bool
    {
        $latest = PassportCustodyEvent::where('applicant_id', $applicant->getKey())
            ->latest('occurred_at')
            ->latest('id')
            ->first();

        return $latest?->event_type === self::RECEIVED;
    }
}
