<?php

namespace MintyPHP\Service\Auth;

use MintyPHP\Http\SessionStoreInterface;

class MicrosoftOidcStateStoreService
{
    private const SESSION_KEY = 'microsoft_oidc_states';

    public function __construct(
        private readonly SessionStoreInterface $sessionStore,
        private readonly int $ttlSeconds = 600,
        private readonly int $maxEntries = 50
    ) {
    }

    public function store(string $state, array $payload): void
    {
        $state = trim($state);
        if ($state === '') {
            return;
        }

        $states = $this->states();
        $states = $this->pruneExpiredStates($states);

        $payload['created_at'] = (int) ($payload['created_at'] ?? time());
        $states[$state] = $payload;

        // Cap size to prevent session bloat from many unfinished authorization flows.
        if (count($states) > $this->maxEntries) {
            uasort($states, static function (array $a, array $b): int {
                return ((int) ($a['created_at'] ?? 0)) <=> ((int) ($b['created_at'] ?? 0));
            });
            while (count($states) > $this->maxEntries) {
                array_shift($states);
            }
        }

        $this->sessionStore->set(self::SESSION_KEY, $states);
    }

    /**
     * @return array{ok:bool,error?:string,entry?:array}
     */
    public function consume(string $state): array
    {
        $state = trim($state);
        if ($state === '') {
            return ['ok' => false, 'error' => 'state_invalid'];
        }

        $states = $this->states();
        $entry = is_array($states[$state] ?? null) ? $states[$state] : null;
        if ($entry === null) {
            return ['ok' => false, 'error' => 'state_invalid'];
        }

        // Remove the state before checking TTL — prevents replay even if the check below rejects it.
        unset($states[$state]);
        $this->sessionStore->set(self::SESSION_KEY, $states);

        $createdAt = (int) ($entry['created_at'] ?? 0);
        if ($createdAt <= 0 || (time() - $createdAt) > $this->ttlSeconds) {
            return ['ok' => false, 'error' => 'state_expired'];
        }

        return ['ok' => true, 'entry' => $entry];
    }

    public function prune(): void
    {
        $this->sessionStore->set(self::SESSION_KEY, $this->pruneExpiredStates($this->states()));
    }

    private function states(): array
    {
        $states = $this->sessionStore->get(self::SESSION_KEY, []);
        return is_array($states) ? $states : [];
    }

    private function pruneExpiredStates(array $states): array
    {
        $now = time();
        foreach ($states as $key => $payload) {
            if (!is_array($payload)) {
                unset($states[$key]);
                continue;
            }
            $createdAt = (int) ($payload['created_at'] ?? 0);
            if ($createdAt <= 0 || ($now - $createdAt) > $this->ttlSeconds) {
                unset($states[$key]);
            }
        }

        return $states;
    }
}
