<?php

declare(strict_types=1);

namespace Core;

use App\Support\AdmissionForm;
use DateTimeImmutable;

final class Validator
{
    public static function admission(array $input, array $files, bool $isUpdate = false, bool $requirePublicAttachments = true): array
    {
        $errors = [];
        $config = require dirname(__DIR__) . '/config/app.php';
        $jobTitle = trim((string) ($input['job_title'] ?? ''));
        $isInternEdu = AdmissionForm::isInternEducationJob($jobTitle);
        $isProfessorEdu = AdmissionForm::isProfessorJob($jobTitle, $config);

        $required = [
            'consent' => 'Você precisa aceitar o termo para prosseguir.',
            'race' => 'Informe a raça/cor.',
            'unit_name' => 'Informe a unidade.',
            'job_title' => 'Informe o cargo.',
            'full_name' => 'Informe o nome completo.',
            'mother_name' => 'Informe o nome da mãe.',
            'birth_date' => 'Informe a data de nascimento.',
            'marital_status' => 'Informe o estado civil.',
            'education_level' => 'Informe a escolaridade.',
            'nationality' => 'Informe a nacionalidade.',
            'birth_city' => 'Informe o município de nascimento.',
            'personal_email' => 'Informe o e-mail pessoal.',
            'phone' => 'Informe o celular.',
            'emergency_contact' => 'Informe o contato de emergência.',
            'address_line' => 'Informe o endereço.',
            'address_number' => 'Informe o número do endereço.',
            'neighborhood' => 'Informe o bairro.',
            'zip_code' => 'Informe o CEP.',
            'city' => 'Informe a cidade.',
            'state' => 'Informe a UF.',
            'rg_number' => 'Informe o RG.',
            'rg_issue_date' => 'Informe a data de expedição do RG.',
            'rg_issuer' => 'Informe o órgão expedidor.',
            'cpf' => 'Informe o CPF.',
            'pis_number' => 'Informe o PIS/PASEP.',
            'pis_issue_date' => 'Informe a data de emissão do PIS.',
        ];

        foreach ($required as $field => $message) {
            if (trim((string) ($input[$field] ?? '')) === '') {
                $errors[$field] = $message;
            }
        }

        if (!filter_var($input['personal_email'] ?? '', FILTER_VALIDATE_EMAIL)) {
            $errors['personal_email'] = 'Informe um e-mail válido.';
        }

        if (!self::isValidCpf($input['cpf'] ?? '')) {
            $errors['cpf'] = 'Informe um CPF válido.';
        }

        foreach (['birth_date', 'rg_issue_date', 'pis_issue_date'] as $dateField) {
            if (!self::isValidDate($input[$dateField] ?? '')) {
                $errors[$dateField] = 'Informe uma data válida.';
            }
        }

        if (AdmissionForm::hasDependents($input)) {
            $dependents = AdmissionForm::dependentEntries($input);

            if ($dependents === []) {
                $errors['dependents'] = 'Informe ao menos um dependente.';
            }

            foreach ($dependents as $index => $dependent) {
                $number = $index + 1;
                if (trim((string) ($dependent['full_name'] ?? '')) === '') {
                    $errors["dependents.$index.full_name"] = "Informe o nome do dependente $number.";
                }
                if (!self::isValidDate($dependent['birth_date'] ?? '')) {
                    $errors["dependents.$index.birth_date"] = "Informe a data de nascimento do dependente $number.";
                }
                if (!self::isValidCpf($dependent['cpf'] ?? '')) {
                    $errors["dependents.$index.cpf"] = "Informe o CPF válido do dependente $number.";
                }
                if (trim((string) ($dependent['birth_city'] ?? '')) === '') {
                    $errors["dependents.$index.birth_city"] = "Informe a cidade do dependente $number.";
                }
            }
        }

        if (AdmissionForm::usesTransportVoucher($input) && trim((string) ($input['transport_voucher_daily_value'] ?? '')) === '') {
            $errors['transport_voucher_daily_value'] = 'Informe o valor diário do vale-transporte.';
        }

        if ($isUpdate) {
            $bankPaymentMethod = AdmissionForm::resolveBankPaymentMethod($input);

            if ($bankPaymentMethod === 'pix' && trim((string) ($input['pix_key'] ?? '')) === '') {
                $errors['pix_key'] = 'Informe a chave PIX.';
            }

            if ($bankPaymentMethod === 'bank') {
                if (trim((string) ($input['bank_agency'] ?? '')) === '') {
                    $errors['bank_agency'] = 'Informe a agência.';
                }
                if (trim((string) ($input['bank_account'] ?? '')) === '') {
                    $errors['bank_account'] = 'Informe a conta.';
                }
            }
        }

        if ((int) ($input['is_student'] ?? 0) === 1 || $isInternEdu) {
            foreach ([
                'internship_nature',
                'academic_level',
                'institution_name',
                'institution_cnpj',
                'institution_address',
                'institution_neighborhood',
                'institution_number',
                'institution_zip',
                'institution_city',
                'institution_state',
                'advisor_name',
                'semester',
                'study_shift',
                'course_name',
                'registration_number',
            ] as $field) {
                if (trim((string) ($input['academic'][$field] ?? '')) === '') {
                    $errors["academic.$field"] = 'Campo acadêmico obrigatório.';
                }
            }
        }

        if ($isUpdate) {
            foreach (['admission_date', 'dismissal_date'] as $dateField) {
                $value = trim((string) ($input[$dateField] ?? ''));
                if ($value !== '' && !self::isValidDate($value)) {
                    $errors[$dateField] = 'Informe uma data válida.';
                }
            }

            $salaryAmount = trim((string) ($input['salary_amount'] ?? ''));
            $salaryType = trim((string) ($input['salary_type'] ?? ''));
            if ($salaryAmount !== '' && !preg_match('/^\d+(?:[\.,]\d{1,2})?$/', $salaryAmount)) {
                $errors['salary_amount'] = 'Informe um salário válido.';
            }
            if ($salaryAmount !== '' && $salaryType === '') {
                $errors['salary_type'] = 'Selecione o tipo de salário.';
            }
        }

        if ($requirePublicAttachments && !$isUpdate) {
            foreach (['rg_front', 'rg_back', 'residence_proof'] as $field) {
                $missing = !isset($files[$field]) || (int) ($files[$field]['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE;
                if ($missing) {
                    $errors[$field] = 'Este anexo é obrigatório.';
                }
            }
        }

        if ($isProfessorEdu) {
            if (trim((string) ($input['cref_number'] ?? '')) === '') {
                $errors['cref_number'] = 'Informe o número do CREF.';
            }
            if (!self::isValidCnpj($input['professor_mei_cnpj'] ?? '')) {
                $errors['professor_mei_cnpj'] = 'Informe um CNPJ válido para o professor.';
            }
            if ($requirePublicAttachments && !$isUpdate && self::isMissingFile($files['cref_document'] ?? null)) {
                $errors['cref_document'] = 'Envie o documento do CREF.';
            }
            if ($requirePublicAttachments && !$isUpdate && self::isMissingFile($files['mei_certificate'] ?? null)) {
                $errors['mei_certificate'] = 'Envie o Certificado Microempreendedor Individual (MEI).';
            }
        }

        $maritalStatus = trim((string) ($input['marital_status'] ?? ''));
        if ($requirePublicAttachments && $maritalStatus === 'casado' && !$isUpdate && self::isMissingFile($files['marriage_certificate'] ?? null)) {
            $errors['marriage_certificate'] = 'Envie a certidão de casamento.';
        }
        if ($requirePublicAttachments && $maritalStatus === 'divorciado' && !$isUpdate && self::isMissingFile($files['divorce_annotation'] ?? null)) {
            $errors['divorce_annotation'] = 'Envie a averbação do divórcio.';
        }

        if (AdmissionForm::hasDependents($input)) {
            $certificateFiles = $files['dependent_birth_certificates'] ?? null;
            foreach (array_keys(AdmissionForm::dependentEntries($input)) as $index) {
                $file = AdmissionForm::extractNestedUpload($certificateFiles, (int) $index);
                $missingCertificate = !$file || (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE;

                if ($requirePublicAttachments && !$isUpdate && $missingCertificate) {
                    $errors["dependent_birth_certificates.$index"] = 'Envie a certidão de nascimento deste dependente.';
                    continue;
                }

                if ($file && (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE) {
                    $fileError = self::file($file, AdmissionForm::documentMimeTypes(), AdmissionForm::maxUploadBytes());
                    if ($fileError !== null) {
                        $errors["dependent_birth_certificates.$index"] = $fileError;
                    }
                }
            }
        }

        return $errors;
    }

    public static function file(array $file, array $allowedMimeTypes, int $maxSizeBytes): ?string
    {
        $limitLabel = RequestLimits::formatBytes($maxSizeBytes);
        $errorCode = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($errorCode === UPLOAD_ERR_NO_FILE) {
            return null;
        }

        if ($errorCode !== UPLOAD_ERR_OK) {
            return match ($errorCode) {
                UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => "O arquivo excede {$limitLabel}.",
                UPLOAD_ERR_PARTIAL => 'O upload do arquivo foi interrompido. Tente novamente.',
                default => 'Falha ao enviar o arquivo.',
            };
        }

        if ((int) ($file['size'] ?? 0) > $maxSizeBytes) {
            return "O arquivo excede {$limitLabel}.";
        }

        $tmpName = (string) ($file['tmp_name'] ?? '');
        if ($tmpName === '' || !is_file($tmpName)) {
            return 'Arquivo temporário inválido.';
        }

        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mime = (string) $finfo->file($tmpName);
        if (!in_array($mime, $allowedMimeTypes, true)) {
            if ($allowedMimeTypes === ['application/pdf']) {
                return 'Tipo de arquivo inválido. Envie um arquivo PDF.';
            }

            if ($allowedMimeTypes === ['image/jpeg', 'image/png']) {
                return 'Tipo de arquivo inválido. Envie uma imagem JPG ou PNG.';
            }

            return 'Tipo de arquivo inválido.';
        }

        return null;
    }

    public static function isValidDate(?string $date): bool
    {
        if (!$date) {
            return false;
        }

        $parsed = DateTimeImmutable::createFromFormat('Y-m-d', $date);
        return $parsed && $parsed->format('Y-m-d') === $date;
    }

    public static function isValidCnpj(?string $cnpj): bool
    {
        $cnpj = preg_replace('/\D/', '', (string) $cnpj) ?? '';
        if (strlen($cnpj) !== 14 || preg_match('/(\d)\1{13}/', $cnpj)) {
            return false;
        }

        $lengths = [12, 13];
        $weights = [
            [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2],
            [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2],
        ];

        foreach ($lengths as $idx => $length) {
            $sum = 0;
            for ($i = 0; $i < $length; $i++) {
                $sum += (int) $cnpj[$i] * $weights[$idx][$i];
            }
            $remainder = $sum % 11;
            $digit = $remainder < 2 ? 0 : 11 - $remainder;
            if ((int) $cnpj[$length] !== $digit) {
                return false;
            }
        }

        return true;
    }

    public static function isValidCpf(?string $cpf): bool
    {
        $cpf = preg_replace('/\D/', '', (string) $cpf) ?? '';
        if (strlen($cpf) !== 11 || preg_match('/(\d)\1{10}/', $cpf)) {
            return false;
        }

        for ($t = 9; $t < 11; $t++) {
            $sum = 0;
            for ($i = 0; $i < $t; $i++) {
                $sum += (int) $cpf[$i] * (($t + 1) - $i);
            }
            $digit = ((10 * $sum) % 11) % 10;
            if ((int) $cpf[$t] !== $digit) {
                return false;
            }
        }

        return true;
    }

    private static function isMissingFile(mixed $file): bool
    {
        return !is_array($file) || (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE;
    }

}
