<?php
/**
 * Session manager — DB-backed, custom (no PHP native sessions).
 *
 * Stores session_token + csrf_token in `publisher_sessions`.
 * Cookie is HttpOnly + Secure + SameSite=Lax, signed token only.
 */

declare(strict_types=1);

class Session
{
    private static ?array $current = null;
    private static bool $loaded = false;

    /**
     * Resolve the current session from cookie. Returns null if guest.
     */
    public static function current(): ?array
    {
        if (self::$loaded) return self::$current;
        self::$loaded = true;

        $cookieName = (string)env('COOKIE_NAME', 'dt_pub_sess');
        $token = $_COOKIE[$cookieName] ?? null;
        if (!$token || !ctype_xdigit($token) || strlen($token) !== 64) {
            return null;
        }

        $stmt = db()->prepare("
            SELECT s.id, s.publisher_id, s.session_token, s.csrf_token, s.expires_at,
                   u.id AS user_id, u.uuid, u.name, u.email, u.phone, u.country, u.country_code,
                   u.profile_pic, u.company_name, u.credits, u.email_verified, u.status
            FROM publisher_sessions s
            JOIN publisher_user u ON u.id = s.publisher_id
            WHERE s.session_token = ?
              AND s.expires_at > NOW()
              AND u.status = 1
            LIMIT 1
        ");
        $stmt->execute([$token]);
        $row = $stmt->fetch();
        if (!$row) {
            self::clearCookie();
            return null;
        }

        // Sliding expiry — refresh expires_at on every load
        $newExpiry = date('Y-m-d H:i:s', time() + self::lifetimeSeconds());
        db()->prepare("UPDATE publisher_sessions SET expires_at = ? WHERE id = ?")
            ->execute([$newExpiry, $row['id']]);

        self::$current = $row;
        return $row;
    }

    public static function isLoggedIn(): bool
    {
        return self::current() !== null;
    }

    public static function userId(): ?int
    {
        return self::current()['publisher_id'] ?? null;
    }

    public static function user(): ?array
    {
        return self::current();
    }

    public static function csrfToken(): ?string
    {
        return self::current()['csrf_token'] ?? null;
    }

    /**
     * Create a fresh session for a publisher. Returns session_token.
     */
    public static function create(int $publisher_id, bool $remember = false): string
    {
        $sessionToken = bin2hex(random_bytes(32));
        $csrfToken    = bin2hex(random_bytes(32));
        $lifetime     = $remember
            ? (int)env('SESSION_REMEMBER_DAYS', 30) * 86400
            : self::lifetimeSeconds();
        $expiresAt    = date('Y-m-d H:i:s', time() + $lifetime);

        db()->prepare("
            INSERT INTO publisher_sessions
              (publisher_id, session_token, csrf_token, ip_address, user_agent, expires_at)
            VALUES (?, ?, ?, ?, ?, ?)
        ")->execute([
            $publisher_id,
            $sessionToken,
            $csrfToken,
            Logger::clientIp(),
            substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255),
            $expiresAt,
        ]);

        self::setCookie($sessionToken, time() + $lifetime);
        self::$loaded = false;  // re-read on next call
        return $sessionToken;
    }

    /**
     * Destroy current session.
     */
    public static function destroy(): void
    {
        $cookieName = (string)env('COOKIE_NAME', 'dt_pub_sess');
        $token = $_COOKIE[$cookieName] ?? null;
        if ($token) {
            db()->prepare("DELETE FROM publisher_sessions WHERE session_token = ?")
                ->execute([$token]);
        }
        self::clearCookie();
        self::$current = null;
        self::$loaded = true;
    }

    /**
     * Destroy ALL sessions of a publisher (e.g. after password reset).
     */
    public static function destroyAllFor(int $publisher_id): void
    {
        db()->prepare("DELETE FROM publisher_sessions WHERE publisher_id = ?")
            ->execute([$publisher_id]);
    }

    private static function lifetimeSeconds(): int
    {
        return (int)env('SESSION_LIFETIME_DAYS', 7) * 86400;
    }

    private static function setCookie(string $token, int $expiresAt): void
    {
        $cookieName = (string)env('COOKIE_NAME', 'dt_pub_sess');
        setcookie($cookieName, $token, [
            'expires'  => $expiresAt,
            'path'     => '/',
            'domain'   => (string)env('COOKIE_DOMAIN', ''),
            'secure'   => (int)env('COOKIE_SECURE', 1) === 1,
            'httponly' => true,
            'samesite' => (string)env('COOKIE_SAMESITE', 'Lax'),
        ]);
    }

    private static function clearCookie(): void
    {
        $cookieName = (string)env('COOKIE_NAME', 'dt_pub_sess');
        setcookie($cookieName, '', [
            'expires'  => time() - 3600,
            'path'     => '/',
            'domain'   => (string)env('COOKIE_DOMAIN', ''),
            'secure'   => (int)env('COOKIE_SECURE', 1) === 1,
            'httponly' => true,
            'samesite' => (string)env('COOKIE_SAMESITE', 'Lax'),
        ]);
        unset($_COOKIE[$cookieName]);
    }
}
