<?php

namespace App\Http\Controllers\Api\V1\Processing;

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Processing\Enums\MedicalStatus;
use App\Domain\Processing\Enums\VisaStatus;
use App\Domain\Processing\Models\MedicalRecord;
use App\Domain\Processing\Models\PassportCustodyEvent;
use App\Domain\Processing\Models\TravelRecord;
use App\Domain\Processing\Models\VisaRecord;
use App\Domain\Processing\Services\MedicalService;
use App\Domain\Processing\Services\PassportCustodyService;
use App\Domain\Processing\Services\TravelService;
use App\Domain\Processing\Services\VisaService;
use App\Http\Controllers\Controller;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

/**
 * Medical, visa, passport custody and travel for one applicant (§21–§25).
 *
 * Grouped on the applicant because that is how the work is actually done: an
 * officer opens a file and moves it forward.
 */
class ApplicantProcessingController extends Controller
{
    public function __construct(
        private readonly MedicalService $medical,
        private readonly VisaService $visa,
        private readonly PassportCustodyService $custody,
        private readonly TravelService $travel,
    ) {}

    public function show(Request $request, Applicant $applicant): JsonResponse
    {
        $this->authorize('view', $applicant);
        abort_unless($request->user()->can('processing.view'), 403);

        $medical = MedicalRecord::where('applicant_id', $applicant->getKey())
            ->orderByDesc('attempt')
            ->get();

        $visa = VisaRecord::where('applicant_id', $applicant->getKey())->latest('id')->first();
        $travelRecord = TravelRecord::where('applicant_id', $applicant->getKey())->first();

        return ApiResponse::success([
            'medical' => [
                'current' => $medical->firstWhere('is_current', true)
                    ? $this->presentMedical($medical->firstWhere('is_current', true), $request)
                    : null,
                'history' => $medical->map(fn (MedicalRecord $record) => $this->presentMedical($record, $request)),
                'statuses' => $this->options(MedicalStatus::cases()),
            ],
            'visa' => [
                'current' => $visa ? $this->presentVisa($visa) : null,
                'submission_blockers' => $visa ? $this->visa->submissionBlockers($visa) : [],
                'statuses' => $this->options(VisaStatus::cases()),
            ],
            'passport' => [
                'is_held' => $this->custody->isHeld($applicant),
                'events' => PassportCustodyEvent::where('applicant_id', $applicant->getKey())
                    ->with('handledBy:id,name')
                    ->latest('occurred_at')
                    ->latest('id')
                    ->get()
                    ->map(fn (PassportCustodyEvent $event) => [
                        'id' => $event->uuid,
                        'event_type' => $event->event_type,
                        'passport_number' => $event->passport_number,
                        'counterparty' => $event->counterparty,
                        'stored_location' => $event->stored_location,
                        'reason' => $event->reason,
                        'acknowledgement' => $event->acknowledgement,
                        'handled_by' => $event->handledBy?->name,
                        'occurred_at' => $event->occurred_at->toIso8601String(),
                    ]),
            ],
            'travel' => $travelRecord ? $this->presentTravel($travelRecord) : null,
        ]);
    }

    public function scheduleMedical(Request $request, Applicant $applicant): JsonResponse
    {
        $this->authorize('view', $applicant);
        abort_unless($request->user()->can('processing.medical_manage'), 403);

        $validated = $request->validate([
            'center' => ['nullable', 'string', 'max:255'],
            'reference_no' => ['nullable', 'string', 'max:100'],
            'appointment_date' => ['required', 'date'],
            'fee_amount' => ['nullable', 'integer', 'min:0'],
            'fee_currency' => ['nullable', 'string', 'size:3', 'required_with:fee_amount'],
            'paid_by' => ['nullable', Rule::in(['applicant', 'agency', 'employer'])],
            'remarks' => ['nullable', 'string', 'max:1000'],
        ]);

        $record = $this->medical->schedule($applicant, $validated, $request->user());

        return ApiResponse::success(
            $this->presentMedical($record, $request),
            $record->attempt > 1 ? 'Retest booked.' : 'Medical booked.',
            status: 201,
        );
    }

    public function recordMedicalResult(Request $request, MedicalRecord $medicalRecord): JsonResponse
    {
        abort_unless($request->user()->can('processing.medical_manage'), 403);
        abort_unless($medicalRecord->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'status' => ['required', Rule::in(['attended', 'fit', 'unfit', 'retest_required'])],
            'findings' => ['nullable', 'string', 'max:2000'],
            'expiry_date' => ['nullable', 'date', 'after:today'],
        ]);

        $record = $this->medical->recordResult(
            $medicalRecord,
            MedicalStatus::from($validated['status']),
            $request->user(),
            $validated['findings'] ?? null,
            $validated['expiry_date'] ?? null,
        );

        return ApiResponse::success($this->presentMedical($record->fresh(), $request), 'Result recorded.');
    }

    public function openVisa(Request $request, Applicant $applicant): JsonResponse
    {
        $this->authorize('view', $applicant);
        abort_unless($request->user()->can('processing.visa_manage'), 403);

        $validated = $request->validate([
            'country' => ['nullable', 'string', 'size:2'],
            'visa_type' => ['nullable', 'string', 'max:60'],
            'application_number' => ['nullable', 'string', 'max:100'],
            'embassy' => ['nullable', 'string', 'max:255'],
        ]);

        $record = $this->visa->open($applicant, $validated, $request->user());

        return ApiResponse::success($this->presentVisa($record), 'Visa application opened.', status: 201);
    }

    public function changeVisaStatus(Request $request, VisaRecord $visaRecord): JsonResponse
    {
        abort_unless($request->user()->can('processing.visa_manage'), 403);
        abort_unless($visaRecord->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'status' => ['required', Rule::in(VisaStatus::values())],
            'reason' => ['nullable', 'string', 'max:1000'],
            'visa_number' => ['nullable', 'string', 'max:100'],
            'valid_from' => ['nullable', 'date'],
            'valid_until' => ['nullable', 'date', 'after:valid_from'],
            'biometric_date' => ['nullable', 'date'],
            'application_number' => ['nullable', 'string', 'max:100'],
        ]);

        $record = $this->visa->changeStatus(
            $visaRecord,
            VisaStatus::from($validated['status']),
            $request->user(),
            $validated['reason'] ?? null,
            collect($validated)->except(['status', 'reason'])->filter()->all(),
        );

        return ApiResponse::success($this->presentVisa($record->fresh()), 'Visa updated.');
    }

    public function recordCustody(Request $request, Applicant $applicant): JsonResponse
    {
        $this->authorize('view', $applicant);
        abort_unless($request->user()->can('processing.passport_custody'), 403);

        $validated = $request->validate([
            'event_type' => ['required', Rule::in(['received', 'handed_over', 'returned'])],
            'passport_number' => ['nullable', 'string', 'max:50'],
            'counterparty' => ['nullable', 'string', 'max:255'],
            'stored_location' => ['nullable', 'string', 'max:255'],
            'reason' => ['nullable', 'string', 'max:1000'],
            'acknowledgement' => ['nullable', 'string', 'max:1000'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);

        $this->custody->record($applicant, $validated['event_type'], $request->user(), $validated);

        return ApiResponse::success(
            ['is_held' => $this->custody->isHeld($applicant->fresh())],
            'Custody movement recorded.',
            status: 201,
        );
    }

    public function bookTravel(Request $request, Applicant $applicant): JsonResponse
    {
        $this->authorize('view', $applicant);
        abort_unless($request->user()->can('processing.travel_manage'), 403);

        $validated = $request->validate([
            'pnr' => ['nullable', 'string', 'max:40'],
            'airline' => ['nullable', 'string', 'max:120'],
            'flight_number' => ['nullable', 'string', 'max:20'],
            'departure_airport' => ['nullable', 'string', 'max:10'],
            'arrival_airport' => ['nullable', 'string', 'max:10'],
            'departure_at' => ['required', 'date'],
            'arrival_at' => ['nullable', 'date', 'after:departure_at'],
            'ticket_cost_amount' => ['nullable', 'integer', 'min:0'],
            'ticket_cost_currency' => ['nullable', 'string', 'size:3', 'required_with:ticket_cost_amount'],
            'paid_by' => ['nullable', Rule::in(['applicant', 'agency', 'employer'])],
        ]);

        $record = $this->travel->book($applicant, $validated, $request->user());

        return ApiResponse::success($this->presentTravel($record), 'Ticket recorded.', status: 201);
    }

    public function updateTravelStatus(Request $request, TravelRecord $travelRecord): JsonResponse
    {
        abort_unless($request->user()->can('processing.travel_manage'), 403);
        abort_unless($travelRecord->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'status' => ['required', Rule::in(['departed', 'joined'])],
        ]);

        $record = $validated['status'] === 'departed'
            ? $this->travel->markDeparted($travelRecord, $request->user())
            : $this->travel->markJoined($travelRecord, $request->user());

        return ApiResponse::success($this->presentTravel($record->fresh()), 'Travel updated.');
    }

    /** @return array<string, mixed> */
    private function presentMedical(MedicalRecord $record, Request $request): array
    {
        $status = $record->effectiveStatus();

        return [
            'id' => $record->uuid,
            'attempt' => $record->attempt,
            'is_current' => $record->is_current,
            'center' => $record->center,
            'reference_no' => $record->reference_no,
            'appointment_date' => $record->appointment_date?->toDateString(),
            'status' => $status->value,
            'status_label' => $status->label(),
            'public_label' => $status->publicLabel(),
            'clears_for_deployment' => $record->clearsForDeployment(),
            'result_date' => $record->result_date?->toDateString(),
            'expiry_date' => $record->expiry_date?->toDateString(),
            'has_expired' => $record->hasExpired(),
            'fee_amount' => $record->fee_amount,
            'fee_currency' => $record->fee_currency,
            'paid_by' => $record->paid_by,
            // Clinical findings are not part of the ordinary payload (§21).
            'findings' => $request->user()?->can('processing.medical_manage')
                ? $record->findings
                : null,
            'remarks' => $record->remarks,
        ];
    }

    /** @return array<string, mixed> */
    private function presentVisa(VisaRecord $record): array
    {
        return [
            'id' => $record->uuid,
            'country' => $record->country,
            'visa_type' => $record->visa_type,
            'application_number' => $record->application_number,
            'embassy' => $record->embassy,
            'status' => $record->status->value,
            'status_label' => $record->status->label(),
            'public_label' => $record->status->publicLabel(),
            'is_closed' => $record->status->isClosed(),
            'submitted_on' => $record->submitted_on?->toDateString(),
            'biometric_date' => $record->biometric_date?->toDateString(),
            'approved_on' => $record->approved_on?->toDateString(),
            'visa_number' => $record->visa_number,
            'valid_from' => $record->valid_from?->toDateString(),
            'valid_until' => $record->valid_until?->toDateString(),
            'has_expired' => $record->hasExpired(),
            'is_usable' => $record->isUsable(),
            'rejection_reason' => $record->rejection_reason,
        ];
    }

    /** @return array<string, mixed> */
    private function presentTravel(TravelRecord $record): array
    {
        return [
            'id' => $record->uuid,
            'status' => $record->status->value,
            'status_label' => $record->status->label(),
            'is_deployed' => $record->status->isDeployed(),
            'pnr' => $record->pnr,
            'airline' => $record->airline,
            'flight_number' => $record->flight_number,
            'departure_airport' => $record->departure_airport,
            'arrival_airport' => $record->arrival_airport,
            'departure_at' => $record->departure_at?->toIso8601String(),
            'arrival_at' => $record->arrival_at?->toIso8601String(),
            'departed_at' => $record->departed_at?->toIso8601String(),
            'joined_employer_at' => $record->joined_employer_at?->toIso8601String(),
            'ticket_cost_amount' => $record->ticket_cost_amount,
            'ticket_cost_currency' => $record->ticket_cost_currency,
        ];
    }

    /** @param array<int, MedicalStatus|VisaStatus> $cases */
    private function options(array $cases): array
    {
        return array_map(fn ($case) => ['value' => $case->value, 'label' => $case->label()], $cases);
    }
}
