<?php

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

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Training\Enums\ParticipationStatus;
use App\Domain\Training\Enums\ProgramStatus;
use App\Domain\Training\Models\TrainingParticipant;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingService;
use App\Http\Controllers\Controller;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

/** Training and orientation (§22). */
class TrainingController extends Controller
{
    public function __construct(private readonly TrainingService $training) {}

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

        $programs = TrainingProgram::query()
            ->where('company_id', $request->user()->company_id)
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
            ->when($request->boolean('upcoming'), fn ($q) => $q->upcoming())
            ->when($request->filled('search'), fn ($q) => $q->where(
                fn ($inner) => $inner
                    ->where('title', 'like', '%'.$request->string('search').'%')
                    ->orWhere('program_code', 'like', '%'.$request->string('search').'%'),
            ))
            ->latest('starts_on')
            ->paginate($request->integer('per_page', 25));

        return ApiResponse::success(
            $programs->through(fn (TrainingProgram $program) => $this->present($program)),
            meta: [
                'statuses' => array_map(
                    fn (ProgramStatus $status) => ['value' => $status->value, 'label' => $status->label()],
                    ProgramStatus::cases(),
                ),
                'outcomes' => array_map(
                    fn (ParticipationStatus $status) => ['value' => $status->value, 'label' => $status->label()],
                    ParticipationStatus::cases(),
                ),
            ],
        );
    }

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

        $program->load('participants.applicant:id,uuid,full_name,reference_no');

        return ApiResponse::success(array_merge($this->present($program), [
            'description' => $program->description,
            'participants' => $program->participants->map(
                fn (TrainingParticipant $participant) => $this->presentParticipant($participant),
            ),
        ]));
    }

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

        $validated = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'provider' => ['nullable', 'string', 'max:255'],
            'trainer' => ['nullable', 'string', 'max:255'],
            'location' => ['nullable', 'string', 'max:255'],
            'starts_on' => ['required', 'date'],
            'ends_on' => ['nullable', 'date', 'after_or_equal:starts_on'],
            'capacity' => ['nullable', 'integer', 'min:0', 'max:1000'],
            'fee_amount' => ['nullable', 'integer', 'min:0'],
            'currency' => ['required', 'string', 'size:3'],
            'fee_borne_by' => ['nullable', Rule::in(['applicant', 'agency', 'employer'])],
            'certificate_required' => ['nullable', 'boolean'],
            'vendor_id' => ['nullable', 'integer', 'exists:vendors,id'],
            'description' => ['nullable', 'string', 'max:2000'],
        ]);

        $program = $this->training->createProgram(
            $request->user()->company_id,
            $validated,
            $request->user(),
        );

        return ApiResponse::success($this->present($program), 'Programme scheduled.', status: 201);
    }

    public function enrol(Request $request, TrainingProgram $program): JsonResponse
    {
        abort_unless($request->user()->can('processing.training_manage'), 403);
        $this->assertSameCompany($request, $program);

        $validated = $request->validate(['applicant_id' => ['required', 'uuid']]);

        $applicant = Applicant::where('uuid', $validated['applicant_id'])->firstOrFail();

        $participant = $this->training->enrol($program, $applicant, $request->user());

        return ApiResponse::success(
            $this->presentParticipant($participant->fresh('applicant')),
            "{$applicant->full_name} is on the list.",
            status: 201,
        );
    }

    public function outcome(Request $request, TrainingParticipant $participant): JsonResponse
    {
        abort_unless($request->user()->can('processing.training_manage'), 403);
        abort_unless($participant->program->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'status' => ['required', Rule::in(ParticipationStatus::values())],
            'score' => ['nullable', 'integer', 'min:0', 'max:100'],
            'attended_on' => ['nullable', 'date'],
            'remarks' => ['nullable', 'string', 'max:1000'],
        ]);

        $updated = $this->training->recordOutcome(
            $participant,
            ParticipationStatus::from($validated['status']),
            $request->user(),
            $validated,
        );

        return ApiResponse::success($this->presentParticipant($updated->fresh('applicant')), 'Recorded.');
    }

    public function certificate(Request $request, TrainingParticipant $participant): JsonResponse
    {
        abort_unless($request->user()->can('processing.training_manage'), 403);
        abort_unless($participant->program->company_id === $request->user()->company_id, 403);

        $issued = $this->training->issueCertificate($participant, $request->user());

        return ApiResponse::success(
            $this->presentParticipant($issued->fresh('applicant')),
            "Certificate {$issued->certificate_no} issued.",
        );
    }

    public function changeStatus(Request $request, TrainingProgram $program): JsonResponse
    {
        abort_unless($request->user()->can('processing.training_manage'), 403);
        $this->assertSameCompany($request, $program);

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

        $updated = $this->training->changeProgramStatus(
            $program,
            ProgramStatus::from($validated['status']),
            $request->user(),
        );

        return ApiResponse::success($this->present($updated->fresh()), "Now {$updated->status->label()}.");
    }

    /** An applicant's own training history, for their file and the portal. */
    public function forApplicant(Request $request, Applicant $applicant): JsonResponse
    {
        $this->authorize('view', $applicant);
        abort_unless($request->user()->can('processing.view'), 403);

        $participations = TrainingParticipant::where('applicant_id', $applicant->getKey())
            ->with('program')
            ->latest('id')
            ->get();

        return ApiResponse::success(
            $participations->map(fn (TrainingParticipant $participant) => array_merge(
                $this->presentParticipant($participant),
                [
                    'program' => [
                        'title' => $participant->program->title,
                        'starts_on' => $participant->program->starts_on->toDateString(),
                        'location' => $participant->program->location,
                    ],
                ],
            )),
        );
    }

    private function assertSameCompany(Request $request, TrainingProgram $program): void
    {
        abort_unless($program->company_id === $request->user()->company_id, 403);
    }

    /** @return array<string, mixed> */
    private function present(TrainingProgram $program): array
    {
        return [
            'id' => $program->uuid,
            'program_code' => $program->program_code,
            'title' => $program->title,
            'provider' => $program->provider,
            'trainer' => $program->trainer,
            'location' => $program->location,
            'starts_on' => $program->starts_on?->toDateString(),
            'ends_on' => $program->ends_on?->toDateString(),
            'capacity' => (int) $program->capacity,
            'enrolled_count' => (int) $program->enrolled_count,
            'seats_left' => $program->seatsLeft(),
            'has_room' => $program->hasRoom(),
            'fee_amount' => (int) $program->fee_amount,
            'currency' => $program->currency,
            'fee_borne_by' => $program->fee_borne_by,
            'certificate_required' => $program->certificate_required,
            'status' => $program->status->value,
            'status_label' => $program->status->label(),
            'accepts_enrolment' => $program->status->acceptsEnrolment(),
        ];
    }

    /** @return array<string, mixed> */
    private function presentParticipant(TrainingParticipant $participant): array
    {
        return [
            'id' => $participant->uuid,
            'status' => $participant->status->value,
            'status_label' => $participant->status->label(),
            'public_label' => $participant->status->publicLabel(),
            'may_hold_certificate' => $participant->status->mayHoldCertificate(),
            'registered_at' => $participant->registered_at?->toIso8601String(),
            'attended_on' => $participant->attended_on?->toDateString(),
            'score' => $participant->score,
            'remarks' => $participant->remarks,
            'certificate_issued' => $participant->certificate_issued,
            'certificate_no' => $participant->certificate_no,
            'certificate_issued_on' => $participant->certificate_issued_on?->toDateString(),
            'applicant' => $participant->relationLoaded('applicant') && $participant->applicant ? [
                'id' => $participant->applicant->uuid,
                'name' => $participant->applicant->full_name,
                'reference' => $participant->applicant->reference_no,
            ] : null,
        ];
    }
}
