<?php
declare(strict_types=1);

namespace App\Framework\Http\Session;

use App\Framework\DateTime\Clock;
use App\Framework\Http\Cookies\Cookie;
use App\Framework\Http\Cookies\Cookies;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\Http\ResponseManipulator;

final class SessionManager
{
    private const string SESSION_COOKIE_NAME = 'ms_context';

    public function __construct(
        private readonly SessionIdGenerator $generator,
        private readonly ResponseManipulator $responseManipulator,
        private readonly Clock $clock,
        private readonly array $cookieConfig = [
            'path' => '/',
            'domain' => null,
            'secure' => true,
            'httpOnly' => true,
            'sameSite' => 'lax',
        ],
        private readonly SessionStorage $storage = new RedisSessionStorage(),
    ) {
    }

    public function getOrCreateSession(Request $request): Session
    {
        // Session-ID aus Cookie lesen
        $sessionId = $this->getSessionIdFromCookies($request->cookies);

        if ($sessionId !== null) {
            // Versuchen, existierende Session zu laden
            $data = $this->storage->read($sessionId);

            $session = new Session($sessionId, $this->clock);;
            $session->fromArray($data);

            if ($session !== null) {

                return $session;
            }
        }

        // Keine gültige Session gefunden - neue erstellen
        return $this->createNewSession();
    }



    public function createNewSession(): Session
    {
        $sessionId = $this->generator->generate();
        return new Session($sessionId, $this->clock);
    }

    public function regenerateSession(Session $session): Session
    {
        $oldData = $session->all();

        // Neue Session erstellen
        $newSession = $this->createNewSession();
        $newSession->fromArray($oldData);

        // Session-Sicherheitsdaten aktualisieren
        $newSession->security->markRegenerated();

        // Alte Session löschen
        $this->storage->remove($session->id);

        // Neue Session speichern
        $this->storage->write($newSession->id, $newSession->all());

        return $newSession;
    }


    public function saveSession(Session $session, Response $response): Response
    {
        $this->saveSessionData($session);

        // Setze das Session-Cookie
        $cookie = new Cookie(
            self::SESSION_COOKIE_NAME,
            $session->id->toString(),
            0, // 0 = Browser-Session
            $this->cookieConfig['path'],
            $this->cookieConfig['domain'],
            $this->cookieConfig['secure'],
            $this->cookieConfig['httpOnly'],
            $this->cookieConfig['sameSite']
        );

        return $this->responseManipulator->withHeader(
            $response,
            'Set-Cookie',
            $cookie->toHeaderString()
        );
    }

    /**
     * Speichert nur die Session-Daten ohne Cookie zu setzen
     */
    private function saveSessionData(Session $session): void
    {
        $this->storage->write($session->id, $session->all());
    }

    private function getSessionIdFromCookies(Cookies $cookies): ?SessionId
    {
        $sessionCookie = $cookies->get(self::SESSION_COOKIE_NAME);

        if ($sessionCookie === null) {
            return null;
        }

        return SessionId::fromString($sessionCookie->value);
    }

    public function destroySession(Session $session, Response $response): Response
    {
        // Session löschen
        $this->storage->remove($session->id);

        // Cookie zum Löschen setzen (abgelaufenes Datum)
        $cookie = new Cookie(
            self::SESSION_COOKIE_NAME,
            '',
            1, // In der Vergangenheit
            $this->cookieConfig['path'],
            $this->cookieConfig['domain'],
            $this->cookieConfig['secure'],
            $this->cookieConfig['httpOnly'],
            $this->cookieConfig['sameSite']
        );

        // Cookie dem Response hinzufügen
        return $this->responseManipulator->withHeader($response, 'Set-Cookie', $cookie->toHeaderString());
    }

    public function getCookieName(): string
    {
        return self::SESSION_COOKIE_NAME;
    }

    public function getCookieConfig(): array
    {
        return $this->cookieConfig;
    }

}
