<?php

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

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Workflow\Enums\AppointmentStatus;
use App\Domain\Workflow\Models\Appointment;
use App\Domain\Workflow\Services\AppointmentService;
use App\Http\Controllers\Controller;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Validation\Rule;

/** Appointments (§20). */
class AppointmentController extends Controller
{
    /** The appointment types an agency actually books (§20). */
    private const TYPES = [
        'interview', 'medical', 'embassy', 'biometric', 'training',
        'orientation', 'flight', 'payment', 'internal_review',
    ];

    public function __construct(private readonly AppointmentService $appointments) {}

    public function index(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('task.view'), 403);

        $appointments = Appointment::query()
            ->with(['assignee:id,uuid,name', 'subject'])
            ->where('company_id', $request->user()->company_id)
            ->when($request->filled('type'), fn ($q) => $q->where('type', $request->string('type')))
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
            ->when($request->boolean('upcoming'), fn ($q) => $q->upcoming())
            ->when($request->boolean('awaiting_outcome'), fn ($q) => $q->awaitingOutcome())
            ->when($request->filled('from'), fn ($q) => $q->whereDate('scheduled_at', '>=', $request->date('from')))
            ->when($request->filled('to'), fn ($q) => $q->whereDate('scheduled_at', '<=', $request->date('to')))
            ->orderBy('scheduled_at')
            ->paginate($request->integer('per_page', 25));

        return ApiResponse::success(
            $appointments->through(fn (Appointment $appointment) => $this->present($appointment, $request)),
            meta: [
                'types' => self::TYPES,
                'statuses' => array_map(
                    fn (AppointmentStatus $status) => ['value' => $status->value, 'label' => $status->label()],
                    AppointmentStatus::cases(),
                ),
            ],
        );
    }

    public function store(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('task.create'), 403);

        $validated = $request->validate([
            'type' => ['required', Rule::in(self::TYPES)],
            'title' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string', 'max:2000'],
            'scheduled_at' => ['required', 'date'],
            'duration_minutes' => ['nullable', 'integer', 'between:5,1440'],
            'location' => ['nullable', 'string', 'max:255'],
            'instructions' => ['nullable', 'string', 'max:2000'],
            'is_visible_to_applicant' => ['sometimes', 'boolean'],
            'internal_notes' => ['nullable', 'string', 'max:2000'],
            'applicant_id' => ['nullable', 'uuid', Rule::exists('applicants', 'uuid')->whereNull('deleted_at')],
        ]);

        $subject = null;

        if (isset($validated['applicant_id'])) {
            $subject = Applicant::where('uuid', $validated['applicant_id'])->firstOrFail();
            abort_unless($request->user()->can('view', $subject), 403);
        }

        $appointment = $this->appointments->schedule(
            collect($validated)->except('applicant_id')->all(),
            $subject,
            $request->user(),
        );

        return ApiResponse::success(
            $this->present($appointment->load(['subject']), $request),
            'Appointment scheduled.',
            status: 201,
        );
    }

    public function reschedule(Request $request, Appointment $appointment): JsonResponse
    {
        abort_unless($request->user()->can('task.update'), 403);
        abort_unless($appointment->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'scheduled_at' => ['required', 'date'],
            'reason' => ['required', 'string', 'max:1000'],
        ]);

        $replacement = $this->appointments->reschedule(
            $appointment,
            Carbon::parse($validated['scheduled_at']),
            $validated['reason'],
            $request->user(),
        );

        return ApiResponse::success(
            $this->present($replacement->load(['subject']), $request),
            'Appointment moved. The original is kept in the history.',
        );
    }

    public function recordOutcome(Request $request, Appointment $appointment): JsonResponse
    {
        abort_unless($request->user()->can('task.update'), 403);
        abort_unless($appointment->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'status' => ['required', Rule::in(['attended', 'no_show', 'cancelled', 'confirmed'])],
            'outcome' => ['nullable', 'string', 'max:40'],
            'notes' => ['nullable', 'string', 'max:2000'],
        ]);

        $updated = $this->appointments->recordOutcome(
            $appointment,
            AppointmentStatus::from($validated['status']),
            $validated['outcome'] ?? null,
            $validated['notes'] ?? null,
            $request->user(),
        );

        return ApiResponse::success(
            $this->present($updated->fresh()->load(['subject']), $request),
            'Outcome recorded.',
        );
    }

    /** @return array<string, mixed> */
    private function present(Appointment $appointment, Request $request): array
    {
        return [
            'id' => $appointment->uuid,
            'appointment_no' => $appointment->appointment_no,
            'type' => $appointment->type,
            'title' => $appointment->title,
            'description' => $appointment->description,
            'status' => $appointment->status->value,
            'status_label' => $appointment->status->label(),
            'scheduled_at' => $appointment->scheduled_at->toIso8601String(),
            'duration_minutes' => $appointment->duration_minutes,
            'location' => $appointment->location,
            'instructions' => $appointment->instructions,
            'is_visible_to_applicant' => $appointment->is_visible_to_applicant,
            'is_missing_outcome' => $appointment->isMissingOutcome(),
            'outcome' => $appointment->outcome,
            'outcome_notes' => $appointment->outcome_notes,
            'reschedule_reason' => $appointment->reschedule_reason,
            'was_rescheduled' => $appointment->rescheduled_from_id !== null,
            // Staff commentary is not part of the ordinary payload.
            'internal_notes' => $request->user()?->can('task.update')
                ? $appointment->internal_notes
                : null,
            'subject' => $appointment->relationLoaded('subject') && $appointment->subject ? [
                'type' => class_basename($appointment->subject_type),
                'id' => $appointment->subject->uuid ?? null,
                'name' => $appointment->subject->full_name ?? $appointment->subject->name ?? null,
                'reference' => $appointment->subject->reference_no ?? null,
            ] : null,
        ];
    }
}
