<?php

declare(strict_types=1);

require_once __DIR__ . '/AnimalMedicalActions.php';
require_once __DIR__ . '/AnimalPhotoActions.php';
require_once __DIR__ . '/AnimalRelationshipActions.php';
require_once __DIR__ . '/AnimalLifecycleActions.php';

require_once __DIR__ . '/AnimalBrowseActions.php';
require_once __DIR__ . '/AnimalRecordActions.php';

final class AnimalsController
{
    use AnimalBrowseActions;

    use AnimalMedicalActions;

    use AnimalPhotoActions;

    use AnimalRecordActions;

    use AnimalLifecycleActions;
    private const INTAKE_TYPES = [
        'unknown' => 'Non renseigné',
        'found' => 'Trouvé',
        'owner_surrender' => 'Cession par le propriétaire',
        'transfer' => 'Transfert d’une autre structure',
        'born_in_care' => 'Né sous la responsabilité du refuge',
        'return' => 'Retour après placement',
        'seizure' => 'Saisie ou réquisition',
        'other' => 'Autre mode d’entrée',
    ];
    private const INTAKE_REASONS = [
        'unknown' => 'Non renseigné',
        'stray_found' => 'Animal errant ou trouvé',
        'unwanted_litter' => 'Portée non désirée',
        'owner_health' => 'Santé ou hospitalisation du propriétaire',
        'owner_death' => 'Décès du propriétaire',
        'owner_care_home' => 'Entrée en EHPAD ou établissement spécialisé du propriétaire',
        'allergy' => 'Allergie',
        'housing' => 'Logement ou déménagement',
        'financial' => 'Difficultés financières',
        'behavior' => 'Difficultés comportementales',
        'abandonment' => 'Abandon sur place',
        'danger' => 'Mise en danger ou maltraitance',
        'transfer' => 'Transfert entre structures',
        'birth' => 'Naissance',
        'other' => 'Autre motif',
    ];
    private static function intakeTypes(): array
    {
        $out = [];
        foreach (self::INTAKE_TYPES as $key => $fallback) {
            $out[$key] = t('statistics.intake.' . $key, [], $fallback);
        }
        return $out;
    }
    private static function intakeReasons(): array
    {
        $out = [];
        foreach (self::INTAKE_REASONS as $key => $fallback) {
            $out[$key] = t('statistics.reason.' . $key, [], $fallback);
        }
        return $out;
    }

    public static function history(): void
    {
        $db = DB::pdo();
        $id = (int) ($_GET['id'] ?? 0);
        if ($id <= 0) {
            http_response_code(400);
            return;
        }

        $a = $db->prepare('SELECT * FROM animals WHERE id=:id');
        $a->execute([':id' => $id]);
        $animal = $a->fetch(PDO::FETCH_ASSOC);
        if (!$animal) {
            http_response_code(404);
            return;
        }

        $h = $db->prepare('
        SELECT ah.*,COALESCE(u.display_name,u.username) actor_name FROM animal_history ah
        LEFT JOIN users u ON u.id=ah.user_id
        WHERE ah.animal_id=:id
        ORDER BY ah.created_at DESC,ah.id DESC
        LIMIT 500
        ');
        $h->execute([':id' => $id]);
        $history = $h->fetchAll(PDO::FETCH_ASSOC);
        if (!PermissionService::can('medical')) {
            $history = array_values(
                array_filter(
                    $history,
                    static fn(array $row): bool => !in_array(
                        (string) ($row['type'] ?? ''),
                        ['medical', 'treatment', 'vaccine', 'deworming', 'death'],
                        true,
                    ),
                ),
            );
        }

        self::render('animal_history', [
            'title' => 'Historique – ' . ($animal['name'] ?? ''),
            'animal' => $animal,
            'history' => $history,
        ]);
    }

    private static function logHistory(int $animalId, string $type, string $label, ?string $details = null): void
    {
        $db = DB::pdo();
        $db->prepare(
            '
        INSERT INTO animal_history (animal_id, type, label, details, user_id)
        VALUES (:aid, :type, :label, :details, :user)
        ',
        )->execute([
            ':aid' => $animalId,
            ':type' => $type,
            ':label' => $label,
            ':details' => $details,
            ':user' => Auth::id(),
        ]);
    }

    private static function syncIntakeMovement(
        PDO $db,
        int $animalId,
        ?string $date,
        string $type,
        string $reason,
        ?int $depositorId,
        string $municipality,
        string $address,
        string $circumstances,
    ): void {
        $contact = null;
        if ($depositorId) {
            $s = $db->prepare('SELECT name,phone,email FROM directory_contacts WHERE id=:id AND deleted_at IS NULL');
            $s->execute([':id' => $depositorId]);
            $contact = $s->fetch(PDO::FETCH_ASSOC) ?: null;
        }
        $s = $db->prepare(
            "SELECT id FROM animal_movements WHERE animal_id=:animal AND kind='entry' ORDER BY id ASC LIMIT 1",
        );
        $s->execute([':animal' => $animalId]);
        $movementId = (int) $s->fetchColumn();
        $typeLabel = self::intakeTypes()[$type] ?? self::intakeTypes()['unknown'];
        $reasonLabel = self::intakeReasons()[$reason] ?? self::intakeReasons()['unknown'];
        $note = 'Motif : ' . $reasonLabel;
        if ($circumstances !== '') {
            $note .= "\nCirconstances : " . $circumstances;
        }
        $values = [
            ':animal' => $animalId,
            ':place' => $municipality !== '' ? $municipality : ($address !== '' ? $address : null),
            ':lieu' => $typeLabel,
            ':name' => $contact['name'] ?? null,
            ':phone' => $contact['phone'] ?? null,
            ':email' => $contact['email'] ?? null,
            ':note' => $note,
            ':at' => ($date ?: date('Y-m-d')) . ' 12:00:00',
        ];
        if ($movementId) {
            $values[':id'] = $movementId;
            $db->prepare(
                'UPDATE animal_movements SET place=:place,lieu=:lieu,contact_name=:name,contact_phone=:phone,contact_email=:email,note=:note,created_at=:at WHERE id=:id AND animal_id=:animal',
            )->execute($values);
        } else {
            $db->prepare(
                "INSERT INTO animal_movements(animal_id,kind,place,lieu,contact_name,contact_phone,contact_email,note,created_at) VALUES(:animal,'entry',:place,:lieu,:name,:phone,:email,:note,:at)",
            )->execute($values);
        }
    }

    private static function now(): string
    {
        return date('Y-m-d H:i:s');
    }

    private static function animalMediaDir(int $animalId): string
    {
        // public/media/animals/{id}
        return __DIR__ . '/../../public/media/animals/' . $animalId;
    }

    private static function ensureAnimalMediaDir(int $animalId): void
    {
        $dir = self::animalMediaDir($animalId);
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }
    }

    private static function safeFilename(string $name): string
    {
        $name = preg_replace('~[^a-zA-Z0-9._-]+~', '_', $name);
        $name = trim($name, '._-');
        return $name === '' ? 'photo' : $name;
    }

    private static function resolveDirectoryContact(
        PDO $db,
        int $id,
        string $newName,
        string $kind,
        string $role,
    ): ?array {
        if ($id > 0) {
            $stmt = $db->prepare(
                'SELECT dc.id,dc.name,dc.organization_id FROM directory_contacts dc WHERE dc.id=:id AND dc.kind=:kind AND dc.deleted_at IS NULL AND EXISTS(SELECT 1 FROM directory_contact_roles r WHERE r.contact_id=dc.id AND r.role=:role)',
            );
            $stmt->execute([':id' => $id, ':kind' => $kind, ':role' => $role]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
        }
        $newName = trim($newName);
        if ($newName === '') {
            return null;
        }
        $stmt = $db->prepare(
            'SELECT id,name FROM directory_contacts WHERE deleted_at IS NULL AND kind=:kind AND lower(name)=lower(:name) LIMIT 1',
        );
        $stmt->execute([':kind' => $kind, ':name' => $newName]);
        $contact = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$contact) {
            $db->prepare('INSERT INTO directory_contacts(kind,name) VALUES(:kind,:name)')->execute([
                ':kind' => $kind,
                ':name' => $newName,
            ]);
            $contact = ['id' => (int) $db->lastInsertId(), 'name' => $newName, 'organization_id' => null];
        }
        $db->prepare('INSERT OR IGNORE INTO directory_contact_roles(contact_id,role) VALUES(:id,:role)')->execute([
            ':id' => $contact['id'],
            ':role' => $role,
        ]);
        return $contact;
    }

    private static function normalizeVeterinaryClinic(PDO $db, ?array &$vet, ?array &$clinic): void
    {
        if (!$vet) {
            return;
        }
        $organizationId = (int) ($vet['organization_id'] ?? 0);
        if ($organizationId > 0) {
            $s = $db->prepare(
                "SELECT id,name,organization_id FROM directory_contacts WHERE id=:id AND kind='organization' AND deleted_at IS NULL",
            );
            $s->execute([':id' => $organizationId]);
            $linked = $s->fetch(PDO::FETCH_ASSOC);
            if ($linked) {
                $clinic = $linked;
            }
            return;
        }
        if (!$clinic) {
            return;
        }
        $db->prepare(
            "UPDATE directory_contacts SET organization_id=:clinic,updated_at=datetime('now') WHERE id=:vet AND kind='person'",
        )->execute([':clinic' => $clinic['id'], ':vet' => $vet['id']]);
        $vet['organization_id'] = $clinic['id'];
    }

    private static function isAllowedImage(string $tmpPath): bool
    {
        $info = @getimagesize($tmpPath);
        if (!$info) {
            return false;
        }
        $mime = $info['mime'] ?? '';
        return in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true);
    }

    public static function togglePhotoVisibility(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            http_response_code(405);
            return;
        }

        $animalId = (int) ($_POST['animal_id'] ?? 0);
        $photoId = (int) ($_POST['photo_id'] ?? 0);
        $isPublic = (int) ($_POST['is_public'] ?? 1 ? 1 : 0);

        if ($animalId <= 0 || $photoId <= 0) {
            http_response_code(400);
            echo h(t('error.bad_request'));
            return;
        }

        // onglet retour
        $redirectHash = trim((string) ($_POST['redirect_hash'] ?? '#tab-photos'));
        $hash = $redirectHash !== '' && $redirectHash[0] === '#' ? $redirectHash : '#tab-photos';

        $db = DB::pdo();

        // Récupère la photo ciblée + état actuel
        $st = $db->prepare('
        SELECT id, is_primary, is_public, filename
        FROM animal_photos
        WHERE id = :pid AND animal_id = :aid
        LIMIT 1
        ');
        $st->execute([':pid' => $photoId, ':aid' => $animalId]);
        $photo = $st->fetch(PDO::FETCH_ASSOC);

        if (!$photo) {
            http_response_code(404);
            echo h(t('error.photo_not_found'));
            return;
        }

        $wasPrimary = (int) ($photo['is_primary'] ?? 0) === 1;
        $curPublic = (int) ($photo['is_public'] ?? 1);

        // Rien à faire si déjà le bon état
        if ($curPublic === $isPublic) {
            header('Location: /animal?id=' . $animalId . $hash);
            exit();
        }

        try {
            PrivateMediaService::moveVisibility($animalId, (string) $photo['filename'], $isPublic === 1);
            $db->beginTransaction();

            // 1) Toggle visibilité
            $db->prepare(
                '
        UPDATE animal_photos
        SET is_public = :pub
        WHERE id = :pid AND animal_id = :aid
        ',
            )->execute([
                ':pub' => $isPublic,
                ':pid' => $photoId,
                ':aid' => $animalId,
            ]);

            // 2) Si on rend PRIVÉ une photo qui était PRINCIPALE → on lui retire le flag principale
            //    et on promeut une autre photo publique (la plus récente) si possible.
            if ($isPublic === 0 && $wasPrimary) {
                // Retire principale à celle-ci
                $db->prepare(
                    '
            UPDATE animal_photos
            SET is_primary = 0
            WHERE id = :pid AND animal_id = :aid
            ',
                )->execute([':pid' => $photoId, ':aid' => $animalId]);

                // Cherche une photo publique à promouvoir
                $newPrimaryId = $db
                    ->query(
                        '
            SELECT id
            FROM animal_photos
            WHERE animal_id = ' .
                            (int) $animalId .
                            '
            AND is_public = 1
            ORDER BY is_primary DESC, id DESC
            LIMIT 1
            ',
                    )
                    ->fetchColumn();

                if ($newPrimaryId) {
                    // Met toutes les autres à 0 puis celle-là à 1 (propre)
                    $db->prepare('UPDATE animal_photos SET is_primary=0 WHERE animal_id=:aid')->execute([
                        ':aid' => $animalId,
                    ]);
                    $db->prepare('UPDATE animal_photos SET is_primary=1 WHERE id=:pid AND animal_id=:aid')->execute([
                        ':pid' => (int) $newPrimaryId,
                        ':aid' => $animalId,
                    ]);
                } else {
                    // aucune publique => aucune principale (OK)
                    $db->prepare('UPDATE animal_photos SET is_primary=0 WHERE animal_id=:aid')->execute([
                        ':aid' => $animalId,
                    ]);
                }
            }

            // 3) Si on rend PUBLIC et qu'il n'y a aucune principale → cette photo devient principale
            if ($isPublic === 1) {
                $hasPrimary = (bool) $db
                    ->query(
                        '
            SELECT 1
            FROM animal_photos
            WHERE animal_id=' .
                            (int) $animalId .
                            ' AND is_primary=1
            LIMIT 1
            ',
                    )
                    ->fetchColumn();

                if (!$hasPrimary) {
                    // On peut la promouvoir
                    $db->prepare('UPDATE animal_photos SET is_primary=0 WHERE animal_id=:aid')->execute([
                        ':aid' => $animalId,
                    ]);
                    $db->prepare('UPDATE animal_photos SET is_primary=1 WHERE id=:pid AND animal_id=:aid')->execute([
                        ':pid' => $photoId,
                        ':aid' => $animalId,
                    ]);
                }
            }

            $db->commit();
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            try {
                PrivateMediaService::moveVisibility($animalId, (string) $photo['filename'], $isPublic !== 1);
            } catch (Throwable) {
            }
            throw $e;
        }

        // Historique (optionnel)
        if (method_exists(__CLASS__, 'logHistory')) {
            self::logHistory(
                $animalId,
                'photo',
                $isPublic ? 'Photo rendue publique' : 'Photo rendue privée',
                (string) ($photo['filename'] ?? ''),
            );
        }

        $db->prepare("UPDATE animals SET updated_at=datetime('now') WHERE id=:id")->execute([':id' => $animalId]);

        header('Location: /animal?id=' . $animalId . $hash);
        exit();
    }

    public static function icad(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            http_response_code(405);
            header('Allow: POST');
            return;
        }
        $db = DB::pdo();
        $id = (int) ($_POST['id'] ?? 0);
        if ($id <= 0) {
            http_response_code(400);
            echo h(t('error.bad_request'));
            return;
        }

        $force = (int) ($_POST['force'] ?? 0) === 1;

        // animal + puce
        $st = $db->prepare('SELECT id, chip_id FROM animals WHERE id=:id LIMIT 1');
        $st->execute([':id' => $id]);
        $animal = $st->fetch(PDO::FETCH_ASSOC);
        if (!$animal) {
            http_response_code(404);
            echo h(t('error.animal_not_found'));
            return;
        }

        $chipId = preg_replace('/\D+/', '', (string) ($animal['chip_id'] ?? ''));
        if ($chipId === '') {
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(['cached' => null, 'stale' => true, 'error' => 'chip_id vide'], JSON_UNESCAPED_UNICODE);
            return;
        }

        require_once __DIR__ . '/../Services/IcadService.php';
        $svc = new IcadService($db);

        $cached = $svc->getCachedByAnimal($id);
        $stale = $svc->isStale($cached, 86400);

        if ($force || $stale) {
            try {
                $svc->refreshByChip($id, $chipId);
            } catch (Throwable $e) {
                // on laisse le cache en "error"
            }
            $cached = $svc->getCachedByAnimal($id);
            $stale = $svc->isStale($cached, 86400);
        }

        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['cached' => $cached, 'stale' => $stale], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }

    public static function geocode(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            http_response_code(405);
            header('Allow: POST');
            return;
        }
        $db = DB::pdo();

        $id = (int) ($_POST['id'] ?? 0);
        if ($id <= 0) {
            http_response_code(400);
            echo h(t('error.bad_request'));
            return;
        }

        $force = (int) ($_POST['force'] ?? 0) === 1;

        // Récupérer les adresses + coords existantes
        $st = $db->prepare('
        SELECT id,
        rescue_address, rescue_lat, rescue_lng,
        current_address, current_lat, current_lng
        FROM animals
        WHERE id=:id
        LIMIT 1
        ');
        $st->execute([':id' => $id]);
        $a = $st->fetch(PDO::FETCH_ASSOC);
        if (!$a) {
            http_response_code(404);
            echo h(t('error.animal_not_found'));
            return;
        }

        require_once __DIR__ . '/../Services/GeocodeService.php';

        $updates = [];
        $debug = [
            'animal_id' => $id,
            'force' => $force,
            'rescue' => null,
            'current' => null,
        ];

        // Rescue
        $rescueAddr = trim((string) ($a['rescue_address'] ?? ''));
        $hasRescueCoords = $a['rescue_lat'] !== null && $a['rescue_lng'] !== null;

        if ($rescueAddr !== '' && ($force || !$hasRescueCoords)) {
            $g = GeocodeService::geocode($rescueAddr);
            $debug['rescue'] = ['query' => $rescueAddr, 'result' => $g];
            if ($g) {
                $updates['rescue_lat'] = $g['lat'];
                $updates['rescue_lng'] = $g['lng'];
            }
        }

        // Current
        $currentAddr = trim((string) ($a['current_address'] ?? ''));
        $hasCurrentCoords = $a['current_lat'] !== null && $a['current_lng'] !== null;

        if ($currentAddr !== '' && ($force || !$hasCurrentCoords)) {
            $g = GeocodeService::geocode($currentAddr);
            $debug['current'] = ['query' => $currentAddr, 'result' => $g];
            if ($g) {
                $updates['current_lat'] = $g['lat'];
                $updates['current_lng'] = $g['lng'];
            }
        }

        if (!empty($updates)) {
            $set = [];
            $params = [':id' => $id];
            foreach ($updates as $k => $v) {
                $set[] = "$k = :$k";
                $params[":$k"] = $v;
            }
            $sql = 'UPDATE animals SET ' . implode(', ', $set) . ", updated_at=datetime('now') WHERE id=:id";
            $u = $db->prepare($sql);
            $u->execute($params);
        }

        // Si appel AJAX/JSON
        $wantsJson = isset($_POST['json']) && (int) $_POST['json'] === 1;

        if ($wantsJson) {
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(
                [
                    'ok' => true,
                    'updated' => $updates,
                    'debug' => $debug,
                ],
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
            );
            return;
        }

        // Sinon redirection propre vers l'onglet identité
        header('Location: /animal?id=' . $id . '#tab-ident');
        exit();
    }
}
