<?php

declare(strict_types=1);

namespace Acms\Services\Setup;

/**
 * セットアップ（install / メンテナンス）専用のセッションラッパー。
 *
 * 本体 {@see \Acms\Services\Session\Engine} はストレージが acmsSerialize（DB 由来 salt 依存）で、
 * fresh install の DB 未接続段階では salt がランダム化して保持できないため流用できない。そこで setup は
 * 素の $_SESSION を使うが、これまで setup.php / install.php / import.php に散らばっていた session 名・
 * Cookie 属性・毎リクエスト再生成・auth / CSRF トークンの読み書きを本クラスへ集約する。
 *
 * - 値の格納は acmsSerialize を通さない素の $_SESSION なので pre-DB でも安全。
 * - Cookie 属性を安全側（httponly / samesite=Lax / SSL 時 secure）に倒す（旧 initSession は未設定だった）。
 * - session_start 等の I/O は start()/destroy() に閉じ、get/set 等の値操作は $_SESSION への薄い委譲とする。
 */
final class SetupSession
{
    public const SESSION_NAME = 'acmsSetup';
    private const AUTH_KEY = 'auth';
    private const TOKEN_KEY = 'setupFormToken';

    /**
     * セッションを開始する。session 名と安全な Cookie 属性を設定し、毎リクエスト ID を再生成する。
     * 既に開始済みなら何もしない（多重 start 防止）。
     */
    public function start(): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            return;
        }
        session_name(self::SESSION_NAME);
        session_set_cookie_params([
            'lifetime' => 0,
            'path' => '/',
            'httponly' => true,
            'samesite' => 'Lax',
            'secure' => defined('HTTPS') && HTTPS,
        ]);
        session_start();
        session_regenerate_id(true);
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $_SESSION[$key] ?? $default;
    }

    public function set(string $key, mixed $value): void
    {
        $_SESSION[$key] = $value;
    }

    public function has(string $key): bool
    {
        return isset($_SESSION[$key]);
    }

    public function remove(string $key): void
    {
        unset($_SESSION[$key]);
    }

    /**
     * 管理者としてログイン済みか。
     */
    public function isAuthenticated(): bool
    {
        return ($_SESSION[self::AUTH_KEY] ?? null) ? true : false;
    }

    /**
     * ログイン中のユーザー ID（未ログインなら null）。
     */
    public function userId(): ?int
    {
        $auth = $_SESSION[self::AUTH_KEY] ?? null;

        return is_numeric($auth) ? (int)$auth : null;
    }

    public function authenticate(int $userId): void
    {
        $_SESSION[self::AUTH_KEY] = $userId;
    }

    /**
     * 保持中の CSRF トークン（未発行なら null）。
     */
    public function formToken(): ?string
    {
        $token = $_SESSION[self::TOKEN_KEY] ?? null;

        return is_string($token) && $token !== '' ? $token : null;
    }

    /**
     * CSRF トークンを確保して返す（未発行なら発行）。
     */
    public function ensureFormToken(): string
    {
        $token = $this->formToken();
        if ($token === null) {
            $token = SetupCsrfToken::generate();
            $_SESSION[self::TOKEN_KEY] = $token;
        }

        return $token;
    }

    /**
     * install はステップ状態を持たない設計のためセッションを全消去するが、CSRF トークンだけは
     * フロー継続のため保持する（退避 → 全消去 → 書き戻し）。
     */
    public function resetKeepingToken(): void
    {
        $token = $this->formToken();
        $_SESSION = [];
        if ($token !== null) {
            $_SESSION[self::TOKEN_KEY] = $token;
        }
    }

    /**
     * ログアウト。セッションを破棄し Cookie を失効させる。
     */
    public function destroy(): void
    {
        $_SESSION = [];
        if (session_status() !== PHP_SESSION_ACTIVE) {
            return;
        }
        $params = session_get_cookie_params();
        // session_status() が ACTIVE の場合のみここに来るため session_name() は文字列だが、
        // PHPStan 上は string|false のためキャストして setcookie の string 引数に合わせる。
        setcookie((string)session_name(), '', [
            'expires' => time() - 42000,
            'path' => $params['path'],
            'domain' => $params['domain'],
            'secure' => $params['secure'],
            'httponly' => $params['httponly'],
            'samesite' => $params['samesite'],
        ]);
        session_destroy();
    }
}
