<?php
declare(strict_types=1);

/**
 * /includes/auth.php
 * Відповідає за:
 * - старт сесії (start_app_session)
 * - авторизацію: login_user / logout_user
 * - поточного юзера: current_user (кеш у межах одного HTTP-запиту)
 * - доступ: require_login / require_role
 *
 * ПОСИЛЕНО:
 * - secure/httponly/samesite cookies (де можливо)
 * - session_regenerate_id() на login/logout і періодично для живої сесії
 * - очищення сесії при login/logout
 * - прив'язка авторизованої сесії до User-Agent fingerprint
 * - current_user(): не прив'язано жорстко до колонок (підбирає існуючі)
 * - safe redirect параметр to= (тільки локальний шлях)
 */

require_once __DIR__ . '/db.php';
require_once __DIR__ . '/helpers.php';

/* =========================
   helpers
========================= */
if (!function_exists('auth_is_https_request')) {
    function auth_is_https_request(): bool
    {
        $cfv = (string)($_SERVER['HTTP_CF_VISITOR'] ?? '');
        if ($cfv !== '') {
            $j = json_decode($cfv, true);
            if (is_array($j) && isset($j['scheme']) && is_string($j['scheme'])) {
                return strtolower(trim($j['scheme'])) === 'https';
            }
        }

        $xfp = (string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '');
        if ($xfp !== '') {
            $first = strtolower(trim(explode(',', $xfp)[0]));
            if ($first === 'https') return true;
        }

        $xfs = strtolower(trim((string)($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '')));
        if ($xfs === 'on' || $xfs === '1' || $xfs === 'true') return true;

        $https = strtolower(trim((string)($_SERVER['HTTPS'] ?? '')));
        return ($https !== '' && $https !== 'off' && $https !== '0');
    }
}

if (!function_exists('auth_db_has_column')) {
    function auth_db_has_column(PDO $pdo, string $table, string $col): bool
    {
        try {
            $st = $pdo->prepare('
                SELECT 1
                FROM information_schema.columns
                WHERE table_schema = DATABASE()
                  AND table_name = ?
                  AND column_name = ?
                LIMIT 1
            ');
            $st->execute([$table, $col]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('auth_pick_first_col')) {
    function auth_pick_first_col(PDO $pdo, string $table, array $candidates): ?string
    {
        foreach ($candidates as $c) {
            $c = (string)$c;
            if ($c !== '' && auth_db_has_column($pdo, $table, $c)) return $c;
        }
        return null;
    }
}

if (!function_exists('auth_safe_to')) {
    function auth_safe_to(string $raw, string $fallback = '/'): string
    {
        $raw = trim($raw);
        if ($raw === '') return $fallback;

        if (preg_match('~^https?://~i', $raw)) {
            $p = parse_url($raw);
            $path = (string)($p['path'] ?? '');
            $q = isset($p['query']) && (string)$p['query'] !== '' ? ('?' . (string)$p['query']) : '';
            $raw = $path . $q;
        }

        if (function_exists('safe_local_path')) {
            try {
                return (string)safe_local_path($raw, $fallback);
            } catch (Throwable $e) {
                // fallback нижче
            }
        }

        if ($raw === '' || $raw[0] !== '/' || str_starts_with($raw, '//')) return $fallback;
        return $raw;
    }
}

if (!function_exists('auth_cookie_params')) {
    function auth_cookie_params(): array
    {
        return [
            'lifetime' => 0,
            'path'     => '/',
            'secure'   => auth_is_https_request(),
            'httponly' => true,
            'samesite' => 'Lax',
        ];
    }
}

if (!function_exists('auth_session_cookie_name')) {
    function auth_session_cookie_name(): string
    {
        return 'vmeregi_sess';
    }
}

if (!function_exists('auth_delete_session_cookie')) {
    function auth_delete_session_cookie(): void
    {
        if (!ini_get('session.use_cookies')) return;

        $cookieName = session_name() ?: auth_session_cookie_name();
        $params = session_get_cookie_params();
        $path = (string)($params['path'] ?? '/');
        $domain = (string)($params['domain'] ?? '');
        $secure = !empty($params['secure']);
        $httponly = !empty($params['httponly']);
        $sameSite = (string)($params['samesite'] ?? 'Lax');

        try {
            if (PHP_VERSION_ID >= 70300) {
                setcookie($cookieName, '', [
                    'expires'  => time() - 42000,
                    'path'     => $path !== '' ? $path : '/',
                    'domain'   => $domain,
                    'secure'   => $secure,
                    'httponly' => $httponly,
                    'samesite' => $sameSite !== '' ? $sameSite : 'Lax',
                ]);
            } else {
                setcookie($cookieName, '', time() - 42000, $path !== '' ? $path : '/', $domain, $secure, $httponly);
            }
        } catch (Throwable $e) {
            // ігноруємо — logout не має падати через cookie cleanup
        }

        unset($_COOKIE[$cookieName]);
    }
}

if (!function_exists('auth_current_user_cache_reset')) {
    function auth_current_user_cache_reset(): void
    {
        unset($GLOBALS['__auth_current_user_cache'], $GLOBALS['__auth_current_user_cache_uid']);
        if (isset($GLOBALS['me'])) unset($GLOBALS['me']);
    }
}

if (!function_exists('auth_session_fingerprint')) {
    function auth_session_fingerprint(): string
    {
        $ua = trim((string)($_SERVER['HTTP_USER_AGENT'] ?? ''));
        if ($ua === '') $ua = 'no-user-agent';
        return hash('sha256', $ua);
    }
}

if (!function_exists('auth_mark_authenticated_session')) {
    function auth_mark_authenticated_session(): void
    {
        $_SESSION['_auth_ua'] = auth_session_fingerprint();
        $_SESSION['_auth_login_ts'] = time();
        $_SESSION['_auth_last_regen'] = time();
    }
}

if (!function_exists('auth_clear_auth_session_markers')) {
    function auth_clear_auth_session_markers(): void
    {
        unset(
            $_SESSION['uid'],
            $_SESSION['_auth_ua'],
            $_SESSION['_auth_login_ts'],
            $_SESSION['_auth_last_regen']
        );
    }
}

if (!function_exists('auth_preserve_session_subset')) {
    function auth_preserve_session_subset(array $keys): array
    {
        $keep = [];
        foreach ($keys as $k) {
            if (is_string($k) && array_key_exists($k, $_SESSION)) {
                $keep[$k] = $_SESSION[$k];
            }
        }
        return $keep;
    }
}

if (!function_exists('auth_restore_session_subset')) {
    function auth_restore_session_subset(array $data): void
    {
        foreach ($data as $k => $v) {
            if (!is_string($k) || $k === '') continue;
            $_SESSION[$k] = $v;
        }
    }
}

if (!function_exists('auth_harden_live_session')) {
    function auth_harden_live_session(): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) return;

        $uid = (int)($_SESSION['uid'] ?? 0);
        if ($uid <= 0) return;

        $fingerprint = auth_session_fingerprint();
        $storedFp = (string)($_SESSION['_auth_ua'] ?? '');

        if ($storedFp === '') {
            $_SESSION['_auth_ua'] = $fingerprint;
        } elseif (!hash_equals($storedFp, $fingerprint)) {
            $keep = auth_preserve_session_subset(['lang']);
            $_SESSION = [];
            auth_restore_session_subset($keep);
            auth_clear_auth_session_markers();
            if (function_exists('session_regenerate_id')) {
                @session_regenerate_id(true);
            }
            auth_current_user_cache_reset();
            return;
        }

        $now = time();
        $lastRegen = (int)($_SESSION['_auth_last_regen'] ?? 0);
        $regenEvery = 900; // 15 хв

        if ($lastRegen <= 0 || ($now - $lastRegen) >= $regenEvery) {
            if (function_exists('session_regenerate_id')) {
                @session_regenerate_id(true);
            }
            $_SESSION['_auth_last_regen'] = $now;
        }
    }
}

/* =========================
   session
========================= */
if (!function_exists('start_app_session')) {
    function start_app_session(): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) return;

        @ini_set('session.use_strict_mode', '1');
        @ini_set('session.use_only_cookies', '1');
        @ini_set('session.use_trans_sid', '0');
        @ini_set('session.cookie_httponly', '1');
        @ini_set('session.cookie_samesite', 'Lax');

        $secure = auth_is_https_request();
        @ini_set('session.cookie_secure', $secure ? '1' : '0');

        if (session_name() !== auth_session_cookie_name()) {
            session_name(auth_session_cookie_name());
        }

        $cookieParams = auth_cookie_params();
        if (PHP_VERSION_ID >= 70300) {
            try {
                session_set_cookie_params($cookieParams);
            } catch (Throwable $e) {
                // ігноруємо
            }
        }

        session_start();
        auth_harden_live_session();
    }
}

/* =========================
   user
========================= */
if (!function_exists('current_user')) {
    function current_user(): ?array
    {
        start_app_session();

        $id = (int)($_SESSION['uid'] ?? 0);
        if ($id <= 0) {
            auth_current_user_cache_reset();
            return null;
        }

        $cached = $GLOBALS['__auth_current_user_cache'] ?? null;
        $cachedUid = (int)($GLOBALS['__auth_current_user_cache_uid'] ?? 0);
        if (is_array($cached) && $cachedUid === $id) {
            return $cached;
        }

        $pdo = db();

        $cols = ['id'];
        $map = [
            'email'       => ['email'],
            'username'    => ['username'],
            'role'        => ['role'],
            'lang'        => ['lang'],
            'active'      => ['is_active','active','verified','email_verified','is_verified'],
            'created_at'  => ['created_at'],
            'first_name'  => ['first_name'],
            'last_name'   => ['last_name'],
            'full_name'   => ['full_name','name'],
            'avatar_path' => ['avatar_path'],
            'cover_path'  => ['cover_path'],
        ];

        foreach ($map as $alias => $cands) {
            $col = auth_pick_first_col($pdo, 'users', $cands);
            if ($col) {
                $cols[] = "`$col` AS `$alias`";
            }
        }

        $sql = "SELECT " . implode(', ', $cols) . " FROM users WHERE id=? LIMIT 1";
        $st = $pdo->prepare($sql);
        $st->execute([$id]);
        $u = $st->fetch(PDO::FETCH_ASSOC) ?: null;

        if (!$u) {
            $keep = auth_preserve_session_subset(['lang']);
            $_SESSION = [];
            auth_restore_session_subset($keep);
            if (function_exists('session_regenerate_id')) {
                @session_regenerate_id(true);
            }
            auth_current_user_cache_reset();
            return null;
        }

        $GLOBALS['__auth_current_user_cache_uid'] = $id;
        $GLOBALS['__auth_current_user_cache'] = $u;
        return $u;
    }
}

if (!function_exists('login_user')) {
    function login_user(int $uid): void
    {
        start_app_session();

        $uid = (int)$uid;
        if ($uid <= 0) {
            throw new InvalidArgumentException('Invalid user id for login_user()');
        }

        $keep = auth_preserve_session_subset(['lang']);
        $_SESSION = [];
        auth_restore_session_subset($keep);

        if (function_exists('session_regenerate_id')) {
            @session_regenerate_id(true);
        }

        $_SESSION['uid'] = $uid;
        auth_mark_authenticated_session();
        auth_current_user_cache_reset();
    }
}

if (!function_exists('logout_user')) {
    function logout_user(): void
    {
        start_app_session();

        $keep = auth_preserve_session_subset(['lang']);

        $_SESSION = [];
        auth_delete_session_cookie();

        try {
            @session_destroy();
        } catch (Throwable $e) {
            // ігноруємо
        }

        try {
            @session_write_close();
        } catch (Throwable $e) {
            // ігноруємо
        }

        if (session_status() !== PHP_SESSION_ACTIVE) {
            try {
                @session_id('');
            } catch (Throwable $e) {
                // ігноруємо
            }
            start_app_session();
        }

        $_SESSION = [];
        auth_restore_session_subset($keep);

        if (function_exists('session_regenerate_id')) {
            @session_regenerate_id(true);
        }

        auth_clear_auth_session_markers();
        auth_current_user_cache_reset();
    }
}

/* =========================
   access control
========================= */
if (!function_exists('require_login')) {
    function require_login(): void
    {
        if (current_user()) return;

        $msg = function_exists('t') ? (string)t('only_for_logged') : 'Login required.';
        if (function_exists('flash_set')) {
            flash_set('err', $msg);
        }

        $to = '/';
        if (function_exists('current_uri')) {
            try {
                $to = (string)current_uri();
            } catch (Throwable $e) {
                $to = '/';
            }
        } else {
            $to = (string)($_SERVER['REQUEST_URI'] ?? '/');
            if ($to === '') $to = '/';
        }

        $to = auth_safe_to($to, '/');
        redirect('/login.php?to=' . urlencode($to));
    }
}

if (!function_exists('require_role')) {
    function require_role(array $roles): void
    {
        $u = current_user();
        if (!$u) {
            $msg = function_exists('t') ? (string)t('only_for_logged') : 'Login required.';
            if (function_exists('flash_set')) {
                flash_set('err', $msg);
            }

            $to = '/';
            if (function_exists('current_uri')) {
                try {
                    $to = (string)current_uri();
                } catch (Throwable $e) {
                    $to = '/';
                }
            } else {
                $to = (string)($_SERVER['REQUEST_URI'] ?? '/');
                if ($to === '') $to = '/';
            }

            $to = auth_safe_to($to, '/');
            redirect('/login.php?to=' . urlencode($to));
        }

        $r = (string)($u['role'] ?? '');
        if (!in_array($r, $roles, true)) {
            http_response_code(403);
            echo 'Forbidden';
            exit;
        }
    }
}
