<?php

declare(strict_types=1);

trait AnimalRelationshipActions
{
    public static function litterForm(): void
    {
        $db = DB::pdo();
        $sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
        $motherId = 0;
        $fatherId = 0;

        if ($sourceAnimalId > 0) {
            $stmt = $db->prepare(
                'SELECT id, sex FROM animals WHERE id = :id AND deleted_at IS NULL AND archived_at IS NULL',
            );
            $stmt->execute([':id' => $sourceAnimalId]);
            $source = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$source) {
                http_response_code(404);
                echo h(t('error.animal_not_found'));
                return;
            }
            if (($source['sex'] ?? '') === 'F') {
                $motherId = $sourceAnimalId;
            }
            if (($source['sex'] ?? '') === 'M') {
                $fatherId = $sourceAnimalId;
            }
        }

        $eligibleSql = "
        SELECT id, name FROM animals
        WHERE deleted_at IS NULL AND archived_at IS NULL AND lower(species) IN ('chat', 'cat') AND sex = :sex
          AND lower(status) NOT IN ('adopte', 'adopté', 'decede', 'décédé')
        ORDER BY name COLLATE NOCASE
        ";
        $stmt = $db->prepare($eligibleSql);
        $stmt->execute([':sex' => 'F']);
        $mothers = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $stmt->execute([':sex' => 'M']);
        $fathers = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $existingKittens = $db
            ->query(
                "SELECT a.id,a.name,a.internal_code,a.color,a.sex,a.status,a.archived_at FROM animals a WHERE a.deleted_at IS NULL AND lower(a.species) IN ('chat','cat') AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=a.id) ORDER BY CASE WHEN a.archived_at IS NULL THEN 0 ELSE 1 END,a.name COLLATE NOCASE,a.id",
            )
            ->fetchAll(PDO::FETCH_ASSOC);

        render('litter_form.php', [
            'title' => t('litter.create'),
            'pageDescription' => 'Création groupée des fiches d’une portée de chatons.',
            'sourceAnimalId' => $sourceAnimalId,
            'motherId' => $motherId,
            'fatherId' => $fatherId,
            'mothers' => $mothers,
            'fathers' => $fathers,
            'existingKittens' => $existingKittens,
        ]);
    }

    public static function saveLitter(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            http_response_code(405);
            return;
        }
        $db = DB::pdo();
        $motherId = (int) ($_POST['mother_id'] ?? 0) ?: null;
        $fatherId = (int) ($_POST['father_id'] ?? 0) ?: null;
        $birthDate = trim((string) ($_POST['birth_date'] ?? '')) ?: null;
        $kittenMode = in_array((string) ($_POST['kitten_mode'] ?? 'new'), ['new', 'existing'], true)
            ? (string) $_POST['kitten_mode']
            : 'new';
        if ($birthDate !== null && preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $birthDate, $dateParts)) {
            $birthDate = $dateParts[3] . '-' . $dateParts[2] . '-' . $dateParts[1];
        }
        $kittens = is_array($_POST['kittens'] ?? null) ? array_values($_POST['kittens']) : [];
        $existingKittenIds = is_array($_POST['existing_kitten_ids'] ?? null)
            ? array_values(array_unique(array_filter(array_map('intval', $_POST['existing_kitten_ids']))))
            : [];
        if ($kittenMode === 'new') {
            $existingKittenIds = [];
        } else {
            $kittens = [];
        }

        if (!$kittens && !$existingKittenIds) {
            http_response_code(400);
            echo h(t('litter.need_kitten'));
            return;
        }
        if ($motherId !== null && $fatherId !== null && $motherId === $fatherId) {
            http_response_code(400);
            echo h(t('litter.different_parents'));
            return;
        }
        if ($birthDate !== null && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthDate)) {
            http_response_code(400);
            echo h(t('error.invalid_birth_date'));
            return;
        }

        $parentIds = array_values(array_filter([$motherId, $fatherId]));
        if ($parentIds) {
            $placeholders = implode(',', array_fill(0, count($parentIds), '?'));
            $stmt = $db->prepare(
                "SELECT id, name, sex FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND archived_at IS NULL AND lower(species) IN ('chat','cat') AND lower(status) NOT IN ('adopte','adopté','decede','décédé')",
            );
            $stmt->execute($parentIds);
            $parents = [];
            foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $parent) {
                $parents[(int) $parent['id']] = $parent;
            }
            if (
                ($motherId && ($parents[$motherId]['sex'] ?? '') !== 'F') ||
                ($fatherId && ($parents[$fatherId]['sex'] ?? '') !== 'M')
            ) {
                http_response_code(400);
                echo h(t('litter.invalid_parent'));
                return;
            }
        } else {
            $parents = [];
        }

        if ($existingKittenIds) {
            $placeholders = implode(',', array_fill(0, count($existingKittenIds), '?'));
            $stmt = $db->prepare(
                "SELECT id FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND lower(species) IN ('chat','cat') AND id NOT IN (?,?) AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=animals.id)",
            );
            $stmt->execute([...$existingKittenIds, (int) ($motherId ?? 0), (int) ($fatherId ?? 0)]);
            if (count($stmt->fetchAll(PDO::FETCH_ASSOC)) !== count($existingKittenIds)) {
                http_response_code(400);
                echo h(t('litter.invalid_selected'));
                return;
            }
        }

        $baseName = trim((string) ($parents[$motherId]['name'] ?? ($parents[$fatherId]['name'] ?? 'Portée')));
        $allowedSexes = ['F', 'M', 'U'];
        try {
            PrivateMediaService::moveVisibility($animalId, (string) $photo['filename'], $isPublic === 1);
        } catch (Throwable $e) {
            http_response_code(500);
            echo h(t('error.upload_failed'));
            return;
        }
        $db->beginTransaction();
        try {
            $stmt = $db->prepare(
                'INSERT INTO litters(mother_id, father_id, birth_date) VALUES(:mother, :father, :birth)',
            );
            $stmt->execute([':mother' => $motherId, ':father' => $fatherId, ':birth' => $birthDate]);
            $litterId = (int) $db->lastInsertId();
            $insertAnimal = $db->prepare("
            INSERT INTO animals(name, internal_code, status, species, sex, breed, color, birth_date, birth_is_estimated,intake_date,intake_type,intake_reason, created_at, updated_at)
            VALUES(:name, :code, 'refuge', 'chat', :sex, 'Chat Européen', :color, :birth, 0,:birth,'born_in_care','birth', datetime('now'), datetime('now'))
            ");
            $link = $db->prepare(
                'INSERT INTO litter_kittens(litter_id, animal_id, position) VALUES(:litter, :animal, :position)',
            );
            foreach ($kittens as $index => $kitten) {
                if (!is_array($kitten)) {
                    continue;
                }
                $position = $index + 1;
                $name = trim((string) ($kitten['name'] ?? ''));
                if ($name === '') {
                    $name = $baseName . '-Bébé-' . $position;
                }
                $sex = (string) ($kitten['sex'] ?? 'U');
                if (!in_array($sex, $allowedSexes, true)) {
                    $sex = 'U';
                }
                $color = trim((string) ($kitten['color'] ?? '')) ?: null;
                $insertAnimal->execute([
                    ':name' => $name,
                    ':code' => Ids::nextAnimalCode($name),
                    ':sex' => $sex,
                    ':color' => $color,
                    ':birth' => $birthDate,
                ]);
                $animalId = (int) $db->lastInsertId();
                $link->execute([':litter' => $litterId, ':animal' => $animalId, ':position' => $position]);
                self::syncIntakeMovement(
                    $db,
                    $animalId,
                    $birthDate,
                    'born_in_care',
                    'birth',
                    null,
                    '',
                    '',
                    'Naissance enregistrée avec la portée n°' . $litterId,
                );
                LocationHistoryService::record(
                    $db,
                    $animalId,
                    null,
                    ['status' => 'refuge', 'refuge_room' => null, 'care_box_key' => null, 'current_address' => null],
                    'litter_creation',
                    'Emplacement lors de la création de la portée',
                    $birthDate ? $birthDate . ' 12:00:00' : null,
                );
                self::logHistory($animalId, 'create', 'Chaton ajouté avec sa portée', 'Portée n°' . $litterId);
            }
            $position = count($kittens);
            foreach ($existingKittenIds as $animalId) {
                $link->execute([':litter' => $litterId, ':animal' => $animalId, ':position' => ++$position]);
                self::logHistory($animalId, 'update', 'Rattaché à une nouvelle portée', 'Portée n°' . $litterId);
            }
            $db->commit();
            $targetId = (int) ($_POST['source_animal_id'] ?? 0) ?: ($motherId ?: ($fatherId ?: 0));
            header('Location: ' . ($targetId ? '/animal?id=' . $targetId . '#tab-litters' : '/animals'));
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            throw $e;
        }
    }

    public static function litterAddExistingForm(): void
    {
        $db = DB::pdo();
        $litterId = (int) ($_GET['litter_id'] ?? 0);
        $sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
        $stmt = $db->prepare(
            'SELECT l.*,m.name mother_name,f.name father_name FROM litters l LEFT JOIN animals m ON m.id=l.mother_id LEFT JOIN animals f ON f.id=l.father_id WHERE l.id=:id',
        );
        $stmt->execute([':id' => $litterId]);
        $litter = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$litter) {
            http_response_code(404);
            echo h(t('litter.not_found'));
            return;
        }
        $stmt = $db->prepare("
        SELECT a.id,a.name,a.internal_code,a.color,a.sex,a.status,a.archived_at
        FROM animals a
        WHERE a.deleted_at IS NULL AND lower(a.species) IN ('chat','cat')
          AND a.id NOT IN (COALESCE(:mother,0),COALESCE(:father,0))
          AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=a.id)
        ORDER BY CASE WHEN a.archived_at IS NULL THEN 0 ELSE 1 END,a.name COLLATE NOCASE,a.id
        ");
        $stmt->execute([':mother' => $litter['mother_id'], ':father' => $litter['father_id']]);
        render('litter_add_existing.php', [
            'title' => t('litter.add_title'),
            'pageDescription' => 'Rattacher des fiches animales existantes à une portée.',
            'litter' => $litter,
            'animals' => $stmt->fetchAll(PDO::FETCH_ASSOC),
            'sourceAnimalId' => $sourceAnimalId,
        ]);
    }

    public static function saveExistingLitterKittens(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            http_response_code(405);
            return;
        }
        $db = DB::pdo();
        $litterId = (int) ($_POST['litter_id'] ?? 0);
        $sourceAnimalId = (int) ($_POST['source_animal_id'] ?? 0);
        $ids = is_array($_POST['animal_ids'] ?? null)
            ? array_values(array_unique(array_filter(array_map('intval', $_POST['animal_ids']))))
            : [];
        if ($litterId <= 0 || !$ids) {
            http_response_code(400);
            echo h(t('litter.select_one'));
            return;
        }
        $litterStmt = $db->prepare('SELECT id,mother_id,father_id FROM litters WHERE id=:id');
        $litterStmt->execute([':id' => $litterId]);
        $litter = $litterStmt->fetch(PDO::FETCH_ASSOC);
        if (!$litter) {
            http_response_code(404);
            echo h(t('litter.not_found'));
            return;
        }
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $stmt = $db->prepare(
            "SELECT id FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND lower(species) IN ('chat','cat') AND id NOT IN (?,?) AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=animals.id)",
        );
        $stmt->execute([...$ids, (int) ($litter['mother_id'] ?? 0), (int) ($litter['father_id'] ?? 0)]);
        if (count($stmt->fetchAll(PDO::FETCH_ASSOC)) !== count($ids)) {
            http_response_code(400);
            echo h(t('litter.invalid_existing'));
            return;
        }
        $db->beginTransaction();
        try {
            $position = (int) $db
                ->query('SELECT COALESCE(MAX(position),0) FROM litter_kittens WHERE litter_id=' . $litterId)
                ->fetchColumn();
            $link = $db->prepare(
                'INSERT INTO litter_kittens(litter_id,animal_id,position) VALUES(:litter,:animal,:position)',
            );
            foreach ($ids as $animalId) {
                $link->execute([':litter' => $litterId, ':animal' => $animalId, ':position' => ++$position]);
                self::logHistory($animalId, 'update', 'Rattaché à une portée existante', 'Portée n°' . $litterId);
            }
            $db->commit();
            header(
                'Location: /animal?id=' .
                    ($sourceAnimalId ?: ($litter['mother_id'] ?: ($litter['father_id'] ?: $ids[0]))) .
                    '#tab-litters',
            );
            exit();
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            throw $e;
        }
    }

    public static function bondedForm(): void
    {
        $db = DB::pdo();
        $sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
        if ($sourceAnimalId <= 0) {
            http_response_code(400);
            return;
        }
        $stmt = $db->prepare("
        SELECT a.id, a.name, a.color, a.species, a.status
        FROM animals a
        WHERE a.deleted_at IS NULL AND a.archived_at IS NULL
          AND lower(a.species) IN ('chat', 'cat')
          AND lower(a.status) NOT IN ('adopte', 'adopté', 'decede', 'décédé')
          AND (a.id = :source OR NOT EXISTS (
            SELECT 1 FROM bonded_group_members bgm
            JOIN bonded_groups bg ON bg.id = bgm.group_id AND bg.active = 1
            WHERE bgm.animal_id = a.id
          ))
        ORDER BY a.name COLLATE NOCASE
        ");
        $stmt->execute([':source' => $sourceAnimalId]);
        $animals = $stmt->fetchAll(PDO::FETCH_ASSOC);
        if (!array_filter($animals, fn(array $animal): bool => (int) $animal['id'] === $sourceAnimalId)) {
            http_response_code(404);
            echo h(t('bonded.unavailable'));
            return;
        }
        render('bonded_form.php', [
            'title' => t('bonded.create'),
            'pageDescription' => 'Associer plusieurs chats qui doivent rester ensemble.',
            'sourceAnimalId' => $sourceAnimalId,
            'animals' => $animals,
        ]);
    }

    public static function saveBondedGroup(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            http_response_code(405);
            return;
        }
        $ids = is_array($_POST['animal_ids'] ?? null)
            ? array_values(array_unique(array_filter(array_map('intval', $_POST['animal_ids']))))
            : [];
        if (count($ids) < 2) {
            http_response_code(400);
            echo h(t('bonded.need_two'));
            return;
        }
        $db = DB::pdo();
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $stmt = $db->prepare("
        SELECT a.id FROM animals a
        WHERE a.id IN ($placeholders) AND a.deleted_at IS NULL AND a.archived_at IS NULL
          AND lower(a.species) IN ('chat', 'cat')
          AND lower(a.status) NOT IN ('adopte', 'adopté', 'decede', 'décédé')
          AND NOT EXISTS (
            SELECT 1 FROM bonded_group_members bgm JOIN bonded_groups bg ON bg.id=bgm.group_id
            WHERE bgm.animal_id=a.id AND bg.active=1
          )
        ");
        $stmt->execute($ids);
        if (count($stmt->fetchAll()) !== count($ids)) {
            http_response_code(400);
            echo h(t('bonded.invalid_member'));
            return;
        }
        $name = trim((string) ($_POST['name'] ?? '')) ?: null;
        $notes = trim((string) ($_POST['notes'] ?? '')) ?: null;
        $db->beginTransaction();
        try {
            $stmt = $db->prepare('INSERT INTO bonded_groups(name, notes) VALUES(:name, :notes)');
            $stmt->execute([':name' => $name, ':notes' => $notes]);
            $groupId = (int) $db->lastInsertId();
            $link = $db->prepare(
                'INSERT INTO bonded_group_members(group_id, animal_id, position) VALUES(:group_id, :animal_id, :position)',
            );
            foreach ($ids as $index => $animalId) {
                $link->execute([':group_id' => $groupId, ':animal_id' => $animalId, ':position' => $index + 1]);
                self::logHistory(
                    $animalId,
                    'update',
                    'Ajouté à un groupe inséparable',
                    $name ?: 'Groupe n°' . $groupId,
                );
            }
            $db->commit();
            header('Location: /animal?id=' . $ids[0]);
            exit();
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            throw $e;
        }
    }

    public static function dissolveBondedGroup(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            http_response_code(405);
            return;
        }
        $groupId = (int) ($_POST['group_id'] ?? 0);
        $animalId = (int) ($_POST['animal_id'] ?? 0);
        if ($groupId <= 0) {
            http_response_code(400);
            return;
        }
        $db = DB::pdo();
        $members = $db->prepare('SELECT animal_id FROM bonded_group_members WHERE group_id=:id');
        $members->execute([':id' => $groupId]);
        $memberIds = array_map('intval', array_column($members->fetchAll(PDO::FETCH_ASSOC), 'animal_id'));
        $stmt = $db->prepare(
            "UPDATE bonded_groups SET active=0, dissolved_at=datetime('now') WHERE id=:id AND active=1",
        );
        $stmt->execute([':id' => $groupId]);
        foreach ($memberIds as $memberId) {
            self::logHistory($memberId, 'update', 'Groupe inséparable dissous');
        }
        header('Location: ' . ($animalId > 0 ? '/animal?id=' . $animalId : '/animals'));
        exit();
    }
}
