<?php
declare(strict_types=1);

/**
 * /functional/friends/_lib.php
 * Бібліотека друзів/підписок:
 * - CSRF (сумісність з /includes/helpers.php csrf_token/csrf_check) + fallback
 * - кеш перевірки таблиць (information_schema) для швидкодії
 * - друзі: запит/прийняти/відхилити/скасувати/видалити
 * - підписки: підписатись/відписатись
 * Правила:
 * - якщо друзі => підписка недоступна
 * - якщо є pending-запит між користувачами => підписка недоступна
 * - якщо запит відхилено => можна підписатись
 */

if (!function_exists('h')) {
    function h(?string $s): string
    {
        return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}

/* =========================
   small utils
========================= */
if (!function_exists('vm_is_ajax')) {
    function vm_is_ajax(): bool
    {
        $xrw = strtolower((string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
        if ($xrw === 'xmlhttprequest') return true;

        $accept = strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? ''));
        return str_contains($accept, 'application/json');
    }
}

if (!function_exists('vm_json_exit')) {
    function vm_json_exit(array $data, int $code = 200): void
    {
        if (!headers_sent()) {
            http_response_code($code);
            header('Content-Type: application/json; charset=UTF-8', true);
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0', true);
            header('Pragma: no-cache', true);
        }
        echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }
}

if (!function_exists('vm_post_csrf_value')) {
    function vm_post_csrf_value(): string
    {
        $t = (string)($_POST['csrf'] ?? '');
        if ($t === '') $t = (string)($_POST['csrf_token'] ?? '');
        if ($t === '') $t = (string)($_POST['_csrf'] ?? '');
        return trim($t);
    }
}

/* =========================
   CSRF (compat + fallback)
========================= */
if (!function_exists('vm_csrf_token')) {
    function vm_csrf_token(): string
    {
        // якщо є централізований csrf_token() — використовуємо його
        if (function_exists('csrf_token')) {
            try {
                return (string)csrf_token();
            } catch (Throwable $e) {
                // fallback нижче
            }
        }

        if (session_status() !== PHP_SESSION_ACTIVE) @session_start();

        $a = (string)($_SESSION['_csrf'] ?? '');
        $b = (string)($_SESSION['csrf_token'] ?? '');

        if ($a === '' && $b === '') {
            $tok = bin2hex(random_bytes(32));
            $_SESSION['_csrf'] = $tok;
            $_SESSION['csrf_token'] = $tok;
            return $tok;
        }

        if ($a === '' && $b !== '') {
            $_SESSION['_csrf'] = $b;
            return $b;
        }

        if ($a !== '' && $b === '') {
            $_SESSION['csrf_token'] = $a;
            return $a;
        }

        return $a; // пріоритет _csrf
    }
}

if (!function_exists('vm_csrf_field')) {
    function vm_csrf_field(): string
    {
        return '<input type="hidden" name="csrf" value="' . h(vm_csrf_token()) . '">';
    }
}

if (!function_exists('vm_csrf_check')) {
    function vm_csrf_check(): void
    {
        $t = vm_post_csrf_value();

        // централізований csrf_check()
        if (function_exists('csrf_check')) {
            try {
                if (csrf_check($t)) return;
            } catch (Throwable $e) {
                // fallback нижче
            }
        }

        // fallback: руками звіряємо сесію
        if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
        $a = (string)($_SESSION['_csrf'] ?? '');
        $b = (string)($_SESSION['csrf_token'] ?? '');

        $ok = ($t !== '' && (
            ($a !== '' && hash_equals($a, $t)) ||
            ($b !== '' && hash_equals($b, $t))
        ));

        if ($ok) return;

        if (vm_is_ajax()) {
            vm_json_exit(['ok' => false, 'err' => 'csrf'], 400);
        }

        http_response_code(400);
        echo "Bad CSRF token";
        exit;
    }
}

/* =========================
   DB schema helpers (cached)
========================= */
if (!function_exists('vm_table_exists')) {
    function vm_table_exists(PDO $pdo, string $table): bool
    {
        static $cache = [];

        $key = strtolower(trim($table));
        if ($key === '') return false;

        if (array_key_exists($key, $cache)) {
            return (bool)$cache[$key];
        }

        // якщо є твій хелпер db_has_table() — використовуємо
        if (function_exists('db_has_table')) {
            try {
                $cache[$key] = (bool)db_has_table($pdo, $table);
                return (bool)$cache[$key];
            } catch (Throwable $e) {
                // fallback нижче
            }
        }

        try {
            $st = $pdo->prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1");
            $st->execute([$table]);
            $cache[$key] = (bool)$st->fetchColumn();
            return (bool)$cache[$key];
        } catch (Throwable $e) {
            $cache[$key] = false;
            return false;
        }
    }
}

if (!function_exists('vm_friends_ready')) {
    function vm_friends_ready(PDO $pdo): bool
    {
        return vm_table_exists($pdo, 'friend_requests') && vm_table_exists($pdo, 'user_follows');
    }
}

if (!function_exists('vm_same_user')) {
    function vm_same_user(int $a, int $b): bool
    {
        return $a > 0 && $a === $b;
    }
}

/* =========================
   Friends
========================= */
if (!function_exists('vm_are_friends')) {
    function vm_are_friends(PDO $pdo, int $a, int $b): bool
    {
        if ($a <= 0 || $b <= 0 || $a === $b) return false;
        if (!vm_table_exists($pdo, 'friend_requests')) return false;

        $st = $pdo->prepare("
            SELECT 1
            FROM friend_requests
            WHERE status='accepted'
              AND ((from_user_id=? AND to_user_id=?) OR (from_user_id=? AND to_user_id=?))
            LIMIT 1
        ");
        $st->execute([$a, $b, $b, $a]);
        return (bool)$st->fetchColumn();
    }
}

if (!function_exists('vm_pending_between')) {
    function vm_pending_between(PDO $pdo, int $a, int $b): bool
    {
        if ($a <= 0 || $b <= 0 || $a === $b) return false;
        if (!vm_table_exists($pdo, 'friend_requests')) return false;

        $st = $pdo->prepare("
            SELECT 1
            FROM friend_requests
            WHERE status='pending'
              AND ((from_user_id=? AND to_user_id=?) OR (from_user_id=? AND to_user_id=?))
            LIMIT 1
        ");
        $st->execute([$a, $b, $b, $a]);
        return (bool)$st->fetchColumn();
    }
}

if (!function_exists('vm_friend_request_send')) {
    function vm_friend_request_send(PDO $pdo, int $from, int $to): string
    {
        if ($from <= 0 || $to <= 0) return 'bad';
        if ($from === $to) return 'self';
        if (!vm_table_exists($pdo, 'friend_requests')) return 'no_table';

        if (vm_are_friends($pdo, $from, $to)) return 'already_friends';

        // Якщо навпаки вже є pending -> авто accept
        $st = $pdo->prepare("SELECT id FROM friend_requests WHERE from_user_id=? AND to_user_id=? AND status='pending' ORDER BY id DESC LIMIT 1");
        $st->execute([$to, $from]);
        $revId = (int)($st->fetchColumn() ?: 0);

        if ($revId > 0) {
            try {
                $pdo->beginTransaction();

                $st2 = $pdo->prepare("UPDATE friend_requests SET status='accepted', responded_at=NOW(), accepted_at=NOW() WHERE id=?");
                $st2->execute([$revId]);

                // прибираємо підписки в обидві сторони
                if (vm_table_exists($pdo, 'user_follows')) {
                    $d = $pdo->prepare("DELETE FROM user_follows
                                        WHERE (follower_user_id=? AND followee_user_id=?)
                                           OR (follower_user_id=? AND followee_user_id=?)");
                    $d->execute([$from, $to, $to, $from]);
                }

                $pdo->commit();
                return 'accepted';
            } catch (Throwable $e) {
                if ($pdo->inTransaction()) $pdo->rollBack();
                return 'error';
            }
        }

        // Якщо вже є такий запис (будь-який статус)
        $st = $pdo->prepare("SELECT id, status FROM friend_requests WHERE from_user_id=? AND to_user_id=? ORDER BY id DESC LIMIT 1");
        $st->execute([$from, $to]);
        $row = $st->fetch(PDO::FETCH_ASSOC);

        if ($row) {
            $status = (string)($row['status'] ?? '');
            if ($status === 'pending') return 'already_sent';

            // declined / canceled / accepted(на всяк) -> робимо pending знов
            $up = $pdo->prepare("UPDATE friend_requests
                                 SET status='pending', created_at=NOW(), responded_at=NULL, accepted_at=NULL
                                 WHERE id=?");
            $up->execute([(int)$row['id']]);
            return 'sent';
        }

        // Новий запит
        try {
            $ins = $pdo->prepare("INSERT INTO friend_requests (from_user_id,to_user_id,status,created_at) VALUES (?,?, 'pending', NOW())");
            $ins->execute([$from, $to]);
            return 'sent';
        } catch (PDOException $e) {
            // duplicate/unique — вважаємо що вже є
            if ((string)$e->getCode() === '23000') return 'already_sent';
            return 'error';
        } catch (Throwable $e) {
            return 'error';
        }
    }
}

if (!function_exists('vm_friend_request_accept')) {
    function vm_friend_request_accept(PDO $pdo, int $me, int $from): string
    {
        if ($me <= 0 || $from <= 0 || $me === $from) return 'bad';
        if (!vm_table_exists($pdo, 'friend_requests')) return 'no_table';

        try {
            $pdo->beginTransaction();

            $st = $pdo->prepare("UPDATE friend_requests
                                 SET status='accepted', responded_at=NOW(), accepted_at=NOW()
                                 WHERE from_user_id=? AND to_user_id=? AND status='pending'
                                 ORDER BY id DESC LIMIT 1");
            $st->execute([$from, $me]);

            if ($st->rowCount() <= 0) {
                $pdo->rollBack();
                return 'no_pending';
            }

            // прибираємо підписки в обидві сторони
            if (vm_table_exists($pdo, 'user_follows')) {
                $d = $pdo->prepare("DELETE FROM user_follows
                                    WHERE (follower_user_id=? AND followee_user_id=?)
                                       OR (follower_user_id=? AND followee_user_id=?)");
                $d->execute([$me, $from, $from, $me]);
            }

            $pdo->commit();
            return 'ok';
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            return 'error';
        }
    }
}

if (!function_exists('vm_friend_request_decline')) {
    function vm_friend_request_decline(PDO $pdo, int $me, int $from): string
    {
        if ($me <= 0 || $from <= 0 || $me === $from) return 'bad';
        if (!vm_table_exists($pdo, 'friend_requests')) return 'no_table';

        $st = $pdo->prepare("UPDATE friend_requests
                             SET status='declined', responded_at=NOW(), accepted_at=NULL
                             WHERE from_user_id=? AND to_user_id=? AND status='pending'
                             ORDER BY id DESC LIMIT 1");
        $st->execute([$from, $me]);
        return ($st->rowCount() > 0) ? 'ok' : 'no_pending';
    }
}

if (!function_exists('vm_friend_request_cancel')) {
    function vm_friend_request_cancel(PDO $pdo, int $me, int $to): string
    {
        if ($me <= 0 || $to <= 0 || $me === $to) return 'bad';
        if (!vm_table_exists($pdo, 'friend_requests')) return 'no_table';

        $st = $pdo->prepare("UPDATE friend_requests
                             SET status='canceled', responded_at=NOW(), accepted_at=NULL
                             WHERE from_user_id=? AND to_user_id=? AND status='pending'
                             ORDER BY id DESC LIMIT 1");
        $st->execute([$me, $to]);
        return ($st->rowCount() > 0) ? 'ok' : 'no_pending';
    }
}

if (!function_exists('vm_friend_remove')) {
    function vm_friend_remove(PDO $pdo, int $me, int $other): string
    {
        if ($me <= 0 || $other <= 0 || $me === $other) return 'bad';
        if (!vm_table_exists($pdo, 'friend_requests')) return 'no_table';

        $st = $pdo->prepare("DELETE FROM friend_requests
                             WHERE status='accepted'
                               AND ((from_user_id=? AND to_user_id=?) OR (from_user_id=? AND to_user_id=?))
                             ORDER BY id DESC LIMIT 1");
        $st->execute([$me, $other, $other, $me]);
        return ($st->rowCount() > 0) ? 'ok' : 'not_friends';
    }
}

/* =========================
   Follows
========================= */
if (!function_exists('vm_is_following')) {
    function vm_is_following(PDO $pdo, int $follower, int $followee): bool
    {
        if ($follower <= 0 || $followee <= 0 || $follower === $followee) return false;
        if (!vm_table_exists($pdo, 'user_follows')) return false;

        $st = $pdo->prepare("SELECT 1 FROM user_follows WHERE follower_user_id=? AND followee_user_id=? LIMIT 1");
        $st->execute([$follower, $followee]);
        return (bool)$st->fetchColumn();
    }
}

if (!function_exists('vm_follow_add')) {
    function vm_follow_add(PDO $pdo, int $follower, int $followee): string
    {
        if ($follower <= 0 || $followee <= 0) return 'bad';
        if ($follower === $followee) return 'self';

        if (vm_are_friends($pdo, $follower, $followee)) return 'friends_no_follow';
        if (vm_pending_between($pdo, $follower, $followee)) return 'pending_no_follow';

        if (!vm_table_exists($pdo, 'user_follows')) return 'no_table';
        if (vm_is_following($pdo, $follower, $followee)) return 'already';

        try {
            $ins = $pdo->prepare("INSERT INTO user_follows (follower_user_id, followee_user_id, created_at) VALUES (?,?, NOW())");
            $ins->execute([$follower, $followee]);
            return 'ok';
        } catch (PDOException $e) {
            if ((string)$e->getCode() === '23000') return 'already';
            return 'error';
        } catch (Throwable $e) {
            return 'error';
        }
    }
}

if (!function_exists('vm_follow_remove')) {
    function vm_follow_remove(PDO $pdo, int $follower, int $followee): string
    {
        if ($follower <= 0 || $followee <= 0 || $follower === $followee) return 'bad';
        if (!vm_table_exists($pdo, 'user_follows')) return 'no_table';

        $st = $pdo->prepare("DELETE FROM user_follows WHERE follower_user_id=? AND followee_user_id=? LIMIT 1");
        $st->execute([$follower, $followee]);
        return ($st->rowCount() > 0) ? 'ok' : 'no';
    }
}

/* =========================
   UI helpers
========================= */
if (!function_exists('vm_user_label')) {
    function vm_user_label(array $u): string
    {
        $fn = trim((string)($u['first_name'] ?? ''));
        $ln = trim((string)($u['last_name'] ?? ''));
        $nm = trim($fn . ' ' . $ln);
        if ($nm !== '') return $nm;

        $full = trim((string)($u['full_name'] ?? ''));
        if ($full !== '') return $full;

        return (string)($u['username'] ?? '');
    }
}