<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\OAuth;

use JTL\Backend\AdminAccount;
use JTL\Mapper\AdminLoginStatusMessageMapper;
use JTL\Mapper\AdminLoginStatusToLogLevel;
use JTL\Shop;

/**
 * Production {@see AdminSession}: decides whether the current visitor is a logged-in backend admin.
 *
 * The OAuth authorize route is dispatched by the FRONTEND router, which has already started the
 * frontend session (`JTLSHOP`) by the time this runs. PHP allows only one active session per
 * request, so the usual `Shop::Container()->getAdminAccount()->logged()` can never see the backend
 * (`eSIdAdm`) session here — it reads the frontend `$_SESSION`, which has no real AdminAccount.
 *
 * To read the real backend login state without disturbing the frontend session, we briefly close
 * the frontend session, open the backend session read-only by its cookie id (emitting no Set-Cookie
 * of its own), evaluate a fresh {@see AdminAccount} against it (which re-runs core's
 * password/2FA/URL validation via its constructor), then restore the frontend session exactly as it
 * was. `logged()` is true only after a real password login (plus 2FA when the account requires it) —
 * exactly the "a real admin approved this" signal the OAuth consent step requires.
 *
 * While that backend session is open — and **only** there — we also capture the approving admin's
 * `cLogin` ({@see self::approvingLogin()}). It cannot be read afterwards: the `finally` block puts
 * the *frontend* `$_SESSION` back, and the frontend session has no real `AdminAccount`.
 */
final class JtlAdminSession implements AdminSession
{
    private const string BACKEND_SESSION = 'eSIdAdm';

    private ?string $approvingLogin = null;

    public function approvingLogin(): ?string
    {
        return $this->approvingLogin;
    }

    public function isAuthenticated(): bool
    {
        $this->approvingLogin = null;
        $backendId = $_COOKIE[self::BACKEND_SESSION] ?? null;
        if (!\is_string($backendId) || $backendId === '') {
            return false;
        }

        $frontendName = \session_name();
        $frontendId   = \session_id();
        $wasActive    = \session_status() === \PHP_SESSION_ACTIVE;
        $wasFrontend  = Shop::isFrontend();
        // Snapshot the frontend session payload so a fresh session_start() can't leak backend state
        // into it, then hand PHP a clean slate for the backend read.
        $frontendData = $_SESSION ?? [];

        if ($wasActive) {
            \session_write_close();
        }

        try {
            $_SESSION = [];
            \session_name(self::BACKEND_SESSION);
            \session_id($backendId);
            // use_cookies=0 → do not emit a Set-Cookie for the backend session; cache_limiter=''
            // → no caching headers. We open (not read_and_close) so core's session-validity touch
            // in AdminAccount sees a real session id.
            \session_start(['use_cookies' => '0', 'use_only_cookies' => '0', 'cache_limiter' => '']);

            $container = Shop::Container();
            $account   = new AdminAccount(
                $container->getDB(),
                $container->getBackendLogService(),
                new AdminLoginStatusMessageMapper(),
                new AdminLoginStatusToLogLevel(),
                $container->getGetText(),
                $container->getAlertService(),
            );
            $logged = $account->logged();
            if ($logged) {
                // Read the login from the still-open BACKEND session. AdminAccount::account()
                // returns $_SESSION['AdminAccount'] only when authenticated (AdminAccount.php:360-363).
                $backendAccount = $account->account();
                if ($backendAccount !== false && \is_string($backendAccount->cLogin ?? null)) {
                    $this->approvingLogin = $backendAccount->cLogin;
                }
            }
        } finally {
            if (\session_status() === \PHP_SESSION_ACTIVE) {
                \session_write_close();
            }
            // Restore the frontend session exactly as it was. use_cookies=0 avoids re-emitting the
            // (already-present) frontend cookie as a duplicate Set-Cookie header.
            \session_name($frontendName === false ? 'JTLSHOP' : $frontendName);
            if ($frontendId !== false && $frontendId !== '') {
                \session_id($frontendId);
            }
            if ($wasActive) {
                \session_start(['use_cookies' => '0', 'use_only_cookies' => '0', 'cache_limiter' => '']);
            }
            $_SESSION = $frontendData;
            // AdminAccount's constructor calls Shop::setIsFrontend(false); put it back.
            Shop::setIsFrontend($wasFrontend);
        }

        return $logged;
    }

    public function loginUrl(): string
    {
        return Shop::getAdminURL();
    }
}
