<?php

declare(strict_types=1);

final class MedicalDocumentsController
{
    private const MAX_BYTES = 20 * 1024 * 1024;
    private const TYPES = [
        'application/pdf' => 'pdf',
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/webp' => 'webp',
    ];

    public static function savePrescription(): void
    {
        $animalId = self::animalId();
        $date = self::date((string) ($_POST['prescribed_on'] ?? ''));
        if (!$animalId || !$date) {
            self::fail(t('medical_document.invalid_data'));
            return;
        }
        try {
            $file = self::storeUpload('document', $animalId, 'prescription', true);
        } catch (Throwable $e) {
            self::fail($e->getMessage());
            return;
        }
        $db = DB::pdo();
        try {
            $db->beginTransaction();
            $stmt = $db->prepare(
                'INSERT INTO medical_prescriptions(animal_id,prescribed_on,veterinarian_contact_id,clinic_contact_id,status,notes,original_name,stored_name,mime_type,size_bytes,created_by) VALUES(?,?,?,?,?,?,?,?,?,?,?)',
            );
            $status = in_array($_POST['status'] ?? '', ['active', 'finished', 'replaced'], true)
                ? $_POST['status']
                : 'active';
            $stmt->execute([
                $animalId,
                $date,
                self::contact('veterinarian_contact_id'),
                self::contact('clinic_contact_id'),
                $status,
                self::text('notes'),
                $file['original'],
                $file['stored'],
                $file['mime'],
                $file['size'],
                Auth::id(),
            ]);
            $id = (int) $db->lastInsertId();
            $link = $db->prepare(
                'INSERT OR IGNORE INTO prescription_treatments(prescription_id,treatment_id) SELECT ?,id FROM treatments WHERE id=? AND animal_id=?',
            );
            foreach ((array) ($_POST['treatment_ids'] ?? []) as $treatmentId) {
                $link->execute([$id, (int) $treatmentId, $animalId]);
            }
            self::history($db, $animalId, 'Ordonnance ajoutée', self::fr($date));
            $db->commit();
            AuditService::log(
                'prescription_added',
                '/animal/prescription/save',
                t('medical_document.prescription_added'),
                'animal',
                $animalId,
                ['prescription_id' => $id],
            );
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            self::remove($animalId, $file['stored']);
            throw $e;
        }
        self::back($animalId);
    }

    public static function saveLabReport(): void
    {
        $animalId = self::animalId();
        $date = self::date((string) ($_POST['sampled_on'] ?? ''));
        $names = (array) ($_POST['parameter_name'] ?? []);
        $values = (array) ($_POST['value'] ?? []);
        if (!$animalId || !$date) {
            self::fail(t('medical_document.invalid_data'));
            return;
        }
        try {
            $file = self::storeUpload('document', $animalId, 'analysis', false);
        } catch (Throwable $e) {
            self::fail($e->getMessage());
            return;
        }
        $rows = [];
        foreach ($names as $i => $name) {
            $name = trim((string) $name);
            $raw = str_replace(',', '.', trim((string) ($values[$i] ?? '')));
            if ($name === '' && $raw === '') {
                continue;
            }
            if ($name === '' || !is_numeric($raw)) {
                if ($file) {
                    self::remove($animalId, $file['stored']);
                }
                self::fail(t('medical_document.invalid_result'));
                return;
            }
            $min = self::numberOrNull(($_POST['reference_min'] ?? [])[$i] ?? null);
            $max = self::numberOrNull(($_POST['reference_max'] ?? [])[$i] ?? null);
            if ($min !== null && $max !== null && $min > $max) {
                if ($file) {
                    self::remove($animalId, $file['stored']);
                }
                self::fail(t('medical_document.invalid_result'));
                return;
            }
            $rows[] = [
                'name' => mb_substr($name, 0, 120),
                'value' => (float) $raw,
                'unit' => self::arrayText('unit', $i, 30),
                'min' => $min,
                'max' => $max,
                'notes' => self::arrayText('result_notes', $i, 255),
            ];
        }
        if (!$rows && !$file) {
            self::fail(t('medical_document.result_or_document_required'));
            return;
        }
        $db = DB::pdo();
        try {
            $db->beginTransaction();
            $stmt = $db->prepare(
                'INSERT INTO lab_reports(animal_id,sampled_on,report_type,laboratory_name,veterinarian_contact_id,clinic_contact_id,notes,original_name,stored_name,mime_type,size_bytes,created_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)',
            );
            $stmt->execute([
                $animalId,
                $date,
                self::text('report_type', 120),
                self::text('laboratory_name', 150),
                self::contact('veterinarian_contact_id'),
                self::contact('clinic_contact_id'),
                self::text('notes'),
                $file['original'] ?? null,
                $file['stored'] ?? null,
                $file['mime'] ?? null,
                $file['size'] ?? 0,
                Auth::id(),
            ]);
            $reportId = (int) $db->lastInsertId();
            $insert = $db->prepare(
                'INSERT INTO lab_results(report_id,parameter_name,value,unit,reference_min,reference_max,notes,position) VALUES(?,?,?,?,?,?,?,?)',
            );
            foreach ($rows as $i => $row) {
                $insert->execute([
                    $reportId,
                    $row['name'],
                    $row['value'],
                    $row['unit'],
                    $row['min'],
                    $row['max'],
                    $row['notes'],
                    $i,
                ]);
            }
            self::history(
                $db,
                $animalId,
                'Résultats d’analyse ajoutés',
                self::fr($date) . ($rows ? ' · ' . count($rows) . ' paramètre(s)' : ''),
            );
            $db->commit();
            AuditService::log(
                'lab_report_added',
                '/animal/lab-report/save',
                t('medical_document.analysis_added'),
                'animal',
                $animalId,
                ['report_id' => $reportId, 'results' => count($rows)],
            );
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            if ($file) {
                self::remove($animalId, $file['stored']);
            }
            throw $e;
        }
        self::back($animalId);
    }

    public static function download(): void
    {
        $id = (int) ($_GET['id'] ?? 0);
        $kind = (string) ($_GET['kind'] ?? '');
        $table = $kind === 'prescription' ? 'medical_prescriptions' : ($kind === 'analysis' ? 'lab_reports' : '');
        if (!$id || !$table) {
            http_response_code(404);
            return;
        }
        $stmt = DB::pdo()->prepare(
            "SELECT d.animal_id,d.original_name,d.stored_name,d.mime_type FROM $table d JOIN animals a ON a.id=d.animal_id WHERE d.id=? AND a.deleted_at IS NULL",
        );
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row || !$row['stored_name']) {
            http_response_code(404);
            return;
        }
        $path = self::directory((int) $row['animal_id']) . '/' . basename((string) $row['stored_name']);
        if (!is_file($path)) {
            http_response_code(404);
            return;
        }
        PrivacyService::logAccess('medical_' . $kind, $id, (int) $row['animal_id'], (string) $row['original_name']);
        AuditService::log(
            'private_file_viewed',
            '/animal/medical-document',
            'Document médical privé consulté',
            'animal',
            (int) $row['animal_id'],
            ['kind' => $kind, 'document_id' => $id],
        );
        header('Content-Type: ' . ($row['mime_type'] ?: 'application/octet-stream'));
        header('Content-Length: ' . filesize($path));
        header("Content-Disposition: inline; filename*=UTF-8''" . rawurlencode((string) $row['original_name']));
        header('Cache-Control: private, no-store');
        readfile($path);
    }

    private static function animalId(): int
    {
        $id = (int) ($_POST['animal_id'] ?? 0);
        if (!$id) {
            return 0;
        }
        $s = DB::pdo()->prepare('SELECT 1 FROM animals WHERE id=? AND deleted_at IS NULL');
        $s->execute([$id]);
        return $s->fetchColumn() ? $id : 0;
    }
    private static function date(string $v): ?string
    {
        $d = DateTimeImmutable::createFromFormat('!Y-m-d', $v);
        return $d && $d->format('Y-m-d') === $v ? $v : null;
    }
    private static function contact(string $key): ?int
    {
        $id = (int) ($_POST[$key] ?? 0);
        if ($id <= 0) {
            return null;
        }
        $role = $key === 'veterinarian_contact_id' ? 'veterinaire' : 'cabinet';
        $s = DB::pdo()->prepare(
            'SELECT 1 FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id WHERE dc.id=? AND dc.deleted_at IS NULL AND r.role=?',
        );
        $s->execute([$id, $role]);
        return $s->fetchColumn() ? $id : null;
    }
    private static function text(string $key, int $max = 2000): ?string
    {
        $v = trim((string) ($_POST[$key] ?? ''));
        return $v === '' ? null : mb_substr($v, 0, $max);
    }
    private static function arrayText(string $key, int $i, int $max): ?string
    {
        $v = trim((string) (($_POST[$key] ?? [])[$i] ?? ''));
        return $v === '' ? null : mb_substr($v, 0, $max);
    }
    private static function numberOrNull(mixed $v): ?float
    {
        $v = str_replace(',', '.', trim((string) $v));
        return $v !== '' && is_numeric($v) ? (float) $v : null;
    }
    private static function directory(int $animalId): string
    {
        return dirname(__DIR__, 2) . '/data/medical-documents/' . $animalId;
    }
    private static function storeUpload(string $key, int $animalId, string $prefix, bool $required): ?array
    {
        $f = $_FILES[$key] ?? null;
        if (!$f || ($f['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            if ($required) {
                throw new RuntimeException(t('medical_document.file_required'));
            }
            return null;
        }
        if (($f['error'] ?? 1) !== UPLOAD_ERR_OK || ($f['size'] ?? 0) <= 0 || $f['size'] > self::MAX_BYTES) {
            throw new RuntimeException(t('medical_document.file_invalid'));
        }
        $mime = (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']);
        if (!isset(self::TYPES[$mime])) {
            throw new RuntimeException(t('medical_document.file_type'));
        }
        $dir = self::directory($animalId);
        if (!is_dir($dir) && !mkdir($dir, 0770, true) && !is_dir($dir)) {
            throw new RuntimeException(t('medical_document.storage_failed'));
        }
        $stored = $prefix . '-' . bin2hex(random_bytes(16)) . '.' . self::TYPES[$mime];
        if (!move_uploaded_file($f['tmp_name'], $dir . '/' . $stored)) {
            throw new RuntimeException(t('medical_document.storage_failed'));
        }
        return [
            'original' => mb_substr(basename((string) $f['name']), 0, 240),
            'stored' => $stored,
            'mime' => $mime,
            'size' => (int) $f['size'],
        ];
    }
    private static function remove(int $animalId, string $name): void
    {
        @unlink(self::directory($animalId) . '/' . basename($name));
    }
    private static function history(PDO $db, int $animalId, string $label, string $details): void
    {
        $db->prepare(
            "INSERT INTO animal_history(animal_id,type,label,details,user_id) VALUES(?,'medical',?,?,?)",
        )->execute([$animalId, $label, $details, Auth::id()]);
    }
    private static function fr(string $date): string
    {
        return (new DateTimeImmutable($date))->format('d/m/Y');
    }
    private static function fail(string $message): void
    {
        http_response_code(400);
        echo h($message);
    }
    private static function back(int $id): never
    {
        header('Location: /animal?id=' . $id . '#tab-med');
        exit();
    }
}
