<?php

namespace App\Http\Controllers\Applicant;

use App\Actions\Applications\ApplicationValidator;
use App\Actions\Applications\StartApplication;
use App\Enums\ApplicationStatus;
use App\Enums\ApplicationStep;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\GeneratedDocument;
use App\Models\Intake;
use App\Services\AuditLogger;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use Symfony\Component\HttpFoundation\StreamedResponse;

class ApplicationController extends Controller
{
    public function __construct(
        protected ApplicationValidator $validator,
        protected AuditLogger $audit,
    ) {}

    public function index(Request $request): View
    {
        return view('applicant.applications.index', [
            'applications' => $request->user()->applicant?->applications()
                ->with(['intake', 'choices.programme'])
                ->latest()
                ->get() ?? collect(),
        ]);
    }

    public function store(Request $request, StartApplication $action): RedirectResponse
    {
        $applicant = $request->user()->applicant;

        abort_unless($applicant, 403, 'Your applicant profile is missing.');

        $intake = $request->filled('intake')
            ? Intake::where('slug', $request->string('intake'))->firstOrFail()
            : Intake::current();

        try {
            $application = $action->execute($applicant, $intake);
        } catch (RuntimeException $e) {
            return back()->withErrors(['intake' => $e->getMessage()]);
        }

        return redirect()->route('applicant.applications.wizard', [$application, ApplicationStep::first()->value]);
    }

    public function show(Request $request, Application $application): View
    {
        $this->authorizeOwnership($request, $application);

        $application->load([
            'intake',
            'choices.programme.school',
            'documents',
            'payments',
            'admissionRecord.programme',
            'generatedDocuments',
            'statusHistory',
            'notes' => fn ($q) => $q->visibleToApplicant(),
        ]);

        return view('applicant.applications.show', [
            'application' => $application,
            'checklist' => $this->validator->checklist($application),
            // Internal review states are collapsed for the applicant (PRD 21).
            'timeline' => $this->timeline($application),
        ]);
    }

    public function wizard(Request $request, Application $application, ?string $step = null): View|RedirectResponse
    {
        $this->authorizeOwnership($request, $application);

        if (! $application->isEditable()) {
            return redirect()->route('applicant.applications.show', $application)
                ->with('status', 'This application has been submitted and can no longer be edited.');
        }

        $current = ApplicationStep::tryFrom((string) $step) ?? $application->current_step ?? ApplicationStep::first();

        return view('applicant.applications.wizard', [
            'application' => $application,
            'step' => $current,
        ]);
    }

    public function admissionLetter(Request $request, Application $application): StreamedResponse
    {
        return $this->downloadGenerated($request, $application, GeneratedDocument::TYPE_ADMISSION_LETTER);
    }

    public function joiningInstructions(Request $request, Application $application): StreamedResponse
    {
        return $this->downloadGenerated($request, $application, GeneratedDocument::TYPE_JOINING_INSTRUCTIONS);
    }

    /**
     * Serve a generated PDF through an authorised controller so the file
     * itself is never reachable by URL guessing (PRD 63).
     */
    protected function downloadGenerated(Request $request, Application $application, string $type): StreamedResponse
    {
        $this->authorizeOwnership($request, $application);

        abort_unless($application->status === ApplicationStatus::Admitted, 403, 'This document is not available.');

        $document = $application->generatedDocuments()
            ->where('document_type', $type)
            ->latest('version')
            ->first();

        abort_unless($document, 404, 'This document has not been generated yet.');

        $disk = Storage::disk($document->disk);

        abort_unless($disk->exists($document->storage_path), 404, 'The file could not be found.');

        $this->audit->log(AuditLogger::DOCUMENT_DOWNLOADED, $application, [], ['document_type' => $type]);

        return $disk->download($document->storage_path, $document->filename);
    }

    protected function authorizeOwnership(Request $request, Application $application): void
    {
        abort_unless(
            $application->applicant_id === $request->user()->applicant?->id,
            403,
            'You do not have access to this application.',
        );
    }

    /** Applicant-facing progress, hiding internal review shuffling. */
    protected function timeline(Application $application): array
    {
        $stages = [
            ['label' => 'Submitted', 'done' => $application->isSubmitted()],
            ['label' => 'Under Review', 'done' => in_array($application->status, [
                ApplicationStatus::UnderReview,
                ApplicationStatus::Shortlisted,
                ApplicationStatus::Recommended,
                ApplicationStatus::Admitted,
                ApplicationStatus::Rejected,
            ], true)],
            ['label' => 'Decision Pending', 'done' => in_array($application->status, [
                ApplicationStatus::Recommended,
                ApplicationStatus::Admitted,
                ApplicationStatus::Rejected,
            ], true)],
            ['label' => $application->status === ApplicationStatus::Rejected ? 'Decision' : 'Admitted',
             'done' => $application->status->isDecided()],
        ];

        return $stages;
    }
}
