<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Models\AcademicInfo;
use App\Models\Admission;
use App\Models\Attachment;
use App\Models\Dependent;
use App\Support\AdmissionAttachments;
use App\Support\AdmissionForm;
use Core\Controller;
use Core\Csrf;
use Core\Database;
use Core\Session;
use Core\Upload;
use Core\Validator;
use Core\View;
use Dompdf\Dompdf;
use Dompdf\Options;
use PDOException;
use RuntimeException;
use Throwable;

final class AdminAdmissionController extends Controller
{
    public function index(): void
    {
        $this->requireAdmin();
        $config = require dirname(__DIR__, 2) . '/config/app.php';
        $filters = [
            'search' => trim((string) ($_GET['search'] ?? '')),
            'status' => trim((string) ($_GET['status'] ?? '')),
            'date_from' => trim((string) ($_GET['date_from'] ?? '')),
            'date_to' => trim((string) ($_GET['date_to'] ?? '')),
        ];

        $this->view('admin/admissions/index', [
            'config' => $config,
            'filters' => $filters,
            'admissions' => Admission::search($filters),
            'success' => Session::getFlash('success'),
            'error' => Session::getFlash('error'),
            'adminUser' => \App\Models\AdminUser::current(),
        ], 'admin');
    }

    public function show(string $id): void
    {
        $this->requireAdmin();
        $admission = $this->findAdmissionOrFail((int) $id, true);
        $config = require dirname(__DIR__, 2) . '/config/app.php';

        $this->view('admin/admissions/show', [
            'admission' => $admission,
            'config' => $config,
            'success' => Session::getFlash('success'),
            'error' => Session::getFlash('error'),
            'adminUser' => \App\Models\AdminUser::current(),
        ], 'admin');
    }

    public function edit(string $id): void
    {
        $this->requireAdmin();
        $admission = $this->findAdmissionOrFail((int) $id, true);
        $errors = Session::errors();

        if ($errors === []) {
            Session::clearOldInput();
        }

        $config = require dirname(__DIR__, 2) . '/config/app.php';

        $this->view('admin/admissions/edit', [
            'admission' => $admission,
            'config' => $config,
            'errors' => $errors,
            'error' => Session::getFlash('error'),
            'adminUser' => \App\Models\AdminUser::current(),
        ], 'admin');
    }

    public function updateStatus(string $id): void
    {
        $this->requireAdmin();
        $admissionId = (int) $id;
        $this->findAdmissionOrFail($admissionId);

        if (!Csrf::verify($_POST['_token'] ?? null)) {
            Session::flash('error', 'Token CSRF inválido. Atualize a página e tente novamente.');
            $this->redirectTo('admin/admissoes/' . $admissionId);
        }

        $config = require dirname(__DIR__, 2) . '/config/app.php';
        $status = trim((string) ($_POST['status'] ?? ''));

        if (!array_key_exists($status, $config['statuses'])) {
            Session::flash('error', 'Status inválido para esta ficha.');
            $this->redirectTo('admin/admissoes/' . $admissionId);
        }

        Admission::updateStatus($admissionId, $status);
        Session::flash('success', 'Status do colaborador atualizado com sucesso.');
        $this->redirectTo('admin/admissoes/' . $admissionId);
    }

    public function confirmDestroy(string $id): void
    {
        $this->requireAdmin();
        $admission = $this->findAdmissionOrFail((int) $id);

        $this->view('admin/admissions/delete', [
            'admission' => $admission,
            'success' => Session::getFlash('success'),
            'error' => Session::getFlash('error'),
            'adminUser' => \App\Models\AdminUser::current(),
        ], 'admin');
    }

    public function destroy(string $id): void
    {
        $this->requireAdmin();
        $admissionId = (int) $id;
        $this->findAdmissionOrFail($admissionId);

        if (!Csrf::verify($_POST['_token'] ?? null)) {
            Session::flash('error', 'Token CSRF inválido. Atualizamos a confirmação; tente novamente.');
            $this->redirectTo('admin/admissoes/' . $admissionId . '/excluir');
        }

        $attachments = Attachment::forAdmission($admissionId);
        Admission::delete($admissionId);

        foreach ($attachments as $attachment) {
            Upload::deleteRelativePath((string) ($attachment['relative_path'] ?? ''));
        }

        Session::flash('success', 'Resposta excluída com sucesso.');
        $this->redirectTo('admin/admissoes');
    }

    public function update(string $id): void
    {
        $this->requireAdmin();
        $admissionId = (int) $id;
        $this->findAdmissionOrFail($admissionId);

        if (!Csrf::verify($_POST['_token'] ?? null)) {
            Session::flash('error', 'Token CSRF inválido. Atualize a página e tente novamente.');
            $this->redirectTo('admin/admissoes/' . $admissionId . '/editar');
        }

        $attachmentFields = AdmissionForm::attachmentFields();

        $errors = array_merge(
            Validator::admission($_POST, $_FILES, true),
            AdmissionAttachments::validate($_POST, $_FILES, $attachmentFields)
        );

        $cpf = AdmissionForm::normalizeDigits($_POST['cpf'] ?? '');
        if ($cpf !== '' && Admission::existsByCpfExcept($cpf, $admissionId)) {
            $errors['cpf'] = 'Já existe outra ficha cadastrada com este CPF.';
        }

        if ($errors !== []) {
            $this->failUpdateValidation($admissionId, $errors);
        }

        $storedPaths = [];
        $replacedPaths = [];
        $deletedPaths = [];

        try {
            Database::transaction(function () use (&$storedPaths, &$replacedPaths, &$deletedPaths, $admissionId, $cpf, $attachmentFields): void {
                Admission::update($admissionId, AdmissionForm::buildAdminPayload($_POST, $cpf));

                Dependent::replaceForAdmission($admissionId, AdmissionForm::dependentEntries($_POST));
                AcademicInfo::replaceForAdmission(
                    $admissionId,
                    (int) ($_POST['is_student'] ?? 0) === 1 ? ($_POST['academic'] ?? null) : null
                );

                $sync = AdmissionAttachments::sync($admissionId, $_POST, $_FILES, $attachmentFields);
                $storedPaths = $sync['stored'];
                $replacedPaths = $sync['replaced'];
                $deletedPaths = $sync['deleted'];
            });
        } catch (PDOException $exception) {
            $this->cleanupStoredPaths($storedPaths);

            if (($exception->errorInfo[1] ?? null) === 1062) {
                $this->failUpdateValidation($admissionId, ['cpf' => 'Já existe outra ficha cadastrada com este CPF.']);
            }

            throw $exception;
        } catch (RuntimeException $exception) {
            $this->cleanupStoredPaths($storedPaths);
            $this->failUpdateValidation($admissionId, ['uploads' => $exception->getMessage()], 'Revise o formulário. Houve problema no envio dos anexos.');
        } catch (Throwable $exception) {
            $this->cleanupStoredPaths($storedPaths);
            throw $exception;
        }

        $this->cleanupStoredPaths(array_merge($replacedPaths, $deletedPaths));
        Session::clearOldInput();
        Session::clearErrors();
        Session::flash('success', 'Ficha atualizada com sucesso.');
        $this->redirectTo('admin/admissoes/' . $admissionId);
    }

    public function print(string $id): void
    {
        $this->requireAdmin();
        $admission = $this->findAdmissionOrFail((int) $id, true);
        $config = require dirname(__DIR__, 2) . '/config/app.php';

        View::render('admin/admissions/print', ['admission' => $admission, 'config' => $config], 'print');
    }

    public function pdf(string $id): void
    {
        $this->requireAdmin();

        if (!class_exists(Dompdf::class)) {
            Session::flash('error', 'Dompdf não instalado. Rode composer install para habilitar PDF.');
            $this->redirectTo('admin/admissoes/' . (int) $id);
        }

        $admission = $this->findAdmissionOrFail((int) $id, true);
        $config = require dirname(__DIR__, 2) . '/config/app.php';
        $html = View::renderToString('admin/admissions/pdf', ['admission' => $admission, 'config' => $config]);

        $options = new Options();
        $options->set('isRemoteEnabled', true);

        $dompdf = new Dompdf($options);
        $dompdf->loadHtml($html, 'UTF-8');
        $dompdf->setPaper('A4');
        $dompdf->render();
        $dompdf->stream('ficha-admissao-' . $admission['id'] . '.pdf', ['Attachment' => false]);
        exit;
    }

    public function previewAttachment(string $id, string $attachmentId): void
    {
        $this->requireAdmin();
        $attachment = $this->findOwnedAttachmentOrFail((int) $id, (int) $attachmentId);
        $absolutePath = Upload::absolutePath((string) $attachment['relative_path']);

        if ($absolutePath === null || !is_file($absolutePath)) {
            http_response_code(404);
            exit('Arquivo físico não encontrado.');
        }

        header('Content-Type: ' . $attachment['mime_type']);
        header('Content-Length: ' . (string) filesize($absolutePath));
        header('Content-Disposition: inline; filename="' . $this->safeAttachmentFilename((string) $attachment['original_name']) . '"');
        header('X-Content-Type-Options: nosniff');

        readfile($absolutePath);
        exit;
    }

    public function downloadAttachment(string $id, string $attachmentId): void
    {
        $this->requireAdmin();
        $attachment = $this->findOwnedAttachmentOrFail((int) $id, (int) $attachmentId);
        $absolutePath = Upload::absolutePath((string) $attachment['relative_path']);

        if ($absolutePath === null || !is_file($absolutePath)) {
            http_response_code(404);
            exit('Arquivo físico não encontrado.');
        }

        header('Content-Description: File Transfer');
        header('Content-Type: ' . $attachment['mime_type']);
        header('Content-Disposition: attachment; filename="' . $this->safeAttachmentFilename((string) $attachment['original_name']) . '"');
        header('Content-Length: ' . (string) filesize($absolutePath));
        header('X-Content-Type-Options: nosniff');

        readfile($absolutePath);
        exit;
    }

    private function failUpdateValidation(int $admissionId, array $errors, string $message = 'Revise o formulário. Existem campos obrigatórios ou inválidos.'): never
    {
        Session::flash('error', $message);
        Session::putErrors($errors);
        Session::putOldInput($_POST);
        $this->redirectTo('admin/admissoes/' . $admissionId . '/editar');
    }

    private function cleanupStoredPaths(array $relativePaths): void
    {
        foreach (array_unique(array_filter($relativePaths)) as $relativePath) {
            Upload::deleteRelativePath((string) $relativePath);
        }
    }

    private function findAdmissionOrFail(int $id, bool $withRelations = false): array
    {
        $admission = $withRelations ? Admission::findWithRelations($id) : Admission::find($id);
        if ($admission === null) {
            http_response_code(404);
            exit('Ficha não encontrada.');
        }

        return $admission;
    }

    private function safeAttachmentFilename(string $name): string
    {
        $name = basename(str_replace(["\r", "\n"], '', $name));
        $name = preg_replace('/["\x00-\x1F\x7F]+/u', '-', $name) ?? 'arquivo';
        $name = trim($name, " .-\t");

        return $name !== '' ? $name : 'arquivo';
    }

    private function findOwnedAttachmentOrFail(int $admissionId, int $attachmentId): array
    {
        $admission = Admission::find($admissionId);
        $attachment = Attachment::find($attachmentId);

        if ($admission === null || $attachment === null || (int) $attachment['admission_id'] !== (int) $admission['id']) {
            http_response_code(404);
            exit('Anexo não encontrado.');
        }

        return $attachment;
    }
}
