<?php

declare(strict_types=1);

use App\Framework\DateTime\FrozenClock;
use App\Framework\Http\Session\InMemorySessionStorage;
use App\Framework\Http\Session\Session;
use App\Framework\Http\Session\SessionId;
use App\Framework\Http\Session\SessionKey;
use App\Framework\Random\TestableRandomGenerator;
use App\Framework\Security\CsrfTokenGenerator;

beforeEach(function () {
    $this->clock = new FrozenClock();
    $this->randomGenerator = new TestableRandomGenerator();
    $this->csrfTokenGenerator = new CsrfTokenGenerator($this->randomGenerator);
    $this->storage = new InMemorySessionStorage();
    $this->sessionId = SessionId::fromString('testcomponentspersistencesessionid');
});

describe('Session Component Persistence', function () {
    test('flash messages persist across session save/load cycles', function () {
        // 1. Neue Session erstellen und Flash-Nachricht hinzufügen
        $session1 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session1->fromArray([]); // Initialisiert die Komponenten

        $session1->flash->add('success', 'Operation successful!');
        $session1->flash->add('error', 'Something went wrong');

        // 2. Session-Daten speichern
        $sessionData = $session1->all();
        $this->storage->write($this->sessionId, $sessionData);

        // 3. Neue Session-Instanz laden
        $loadedData = $this->storage->read($this->sessionId);
        $session2 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session2->fromArray($loadedData);

        // 4. Flash-Nachrichten sollten verfügbar sein
        $successMessages = $session2->flash->get('success');
        $errorMessages = $session2->flash->get('error');

        expect($successMessages)->toBe(['Operation successful!']);
        expect($errorMessages)->toBe(['Something went wrong']);

        // 5. Nach dem Abrufen sollten die Nachrichten gelöscht sein (Flash-Verhalten)
        expect($session2->flash->get('success'))->toBe([]);
        expect($session2->flash->get('error'))->toBe([]);
    });

    test('validation errors persist across session save/load cycles', function () {
        // 1. Session mit Validation Errors erstellen
        $session1 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session1->fromArray([]);

        $session1->validation->add('user_form', [
            'email' => ['Invalid email format'],
            'password' => ['Password too short'],
        ]);
        $session1->validation->add('profile_form', [
            'email' => ['Email already exists'],
        ]);

        // 2. Session speichern
        $sessionData = $session1->all();
        $this->storage->write($this->sessionId, $sessionData);

        // 3. Session laden
        $loadedData = $this->storage->read($this->sessionId);
        $session2 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session2->fromArray($loadedData);

        // 4. Validation Errors sollten verfügbar sein
        expect($session2->validation->has('user_form'))->toBeTrue();
        expect($session2->validation->has('profile_form'))->toBeTrue();

        $userFormErrors = $session2->validation->get('user_form');
        $profileFormErrors = $session2->validation->get('profile_form');

        expect($userFormErrors['email'])->toBe(['Invalid email format']);
        expect($userFormErrors['password'])->toBe(['Password too short']);
        expect($profileFormErrors['email'])->toBe(['Email already exists']);
    });

    test('form data persists across session save/load cycles', function () {
        // 1. Session mit Form Data erstellen
        $session1 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session1->fromArray([]);

        $formData = [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'preferences' => [
                'theme' => 'dark',
                'notifications' => true,
            ],
        ];

        $session1->form->store('user_profile', $formData);

        // 2. Session speichern
        $sessionData = $session1->all();
        $this->storage->write($this->sessionId, $sessionData);

        // 3. Session laden
        $loadedData = $this->storage->read($this->sessionId);
        $session2 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session2->fromArray($loadedData);

        // 4. Form Data sollte verfügbar sein
        expect($session2->form->get('user_profile'))->toBe($formData);
        expect($session2->form->has('user_profile'))->toBeTrue();
    });

    test('CSRF tokens persist across session save/load cycles', function () {
        // 1. Session mit CSRF Token erstellen
        $session1 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session1->fromArray([]);

        $token1 = $session1->csrf->generateToken('login_form');
        $token2 = $session1->csrf->generateToken('profile_form');

        // 2. Session speichern
        $sessionData = $session1->all();
        $this->storage->write($this->sessionId, $sessionData);

        // 3. Session laden
        $loadedData = $this->storage->read($this->sessionId);
        $session2 = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session2->fromArray($loadedData);

        // 4. Token sollten gültig sein
        expect($session2->csrf->validateToken('login_form', $token1))->toBeTrue();
        expect($session2->csrf->validateToken('profile_form', $token2))->toBeTrue();

        // Für ungültigen Token erstellen wir ein CsrfToken Objekt
        $invalidToken = \App\Framework\Security\CsrfToken::fromString('0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef');
        expect($session2->csrf->validateToken('login_form', $invalidToken))->toBeFalse();
    });

    test('all component data is stored in session under specific keys', function () {
        // 1. Session mit allen Komponenten-Daten erstellen
        $session = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session->fromArray([]);

        // Komponenten verwenden
        $session->flash->add('info', 'Test message');
        $session->validation->add('test_form', ['field' => ['Test error']]);
        $session->form->store('test_form', ['data' => 'test']);
        $session->csrf->generateToken('test_form');

        // Auch normale Session-Daten hinzufügen
        $session->set('user_id', 123);
        $session->set('custom_data', 'custom_value');

        // 2. Session-Daten analysieren
        $allData = $session->all();

        // 3. Komponenten-Keys sollten existieren
        expect($allData)->toHaveKey(SessionKey::FLASH->value);
        expect($allData)->toHaveKey(SessionKey::VALIDATION_ERRORS->value);
        expect($allData)->toHaveKey(SessionKey::FORM_DATA->value);
        expect($allData)->toHaveKey(SessionKey::CSRF->value);

        // 4. Normale Session-Daten sollten auch existieren
        expect($allData)->toHaveKey('user_id');
        expect($allData)->toHaveKey('custom_data');
        expect($allData['user_id'])->toBe(123);
        expect($allData['custom_data'])->toBe('custom_value');

        // 5. Komponenten-Daten sollten korrekt strukturiert sein
        expect($allData[SessionKey::FLASH->value])->toBeArray();
        expect($allData[SessionKey::VALIDATION_ERRORS->value])->toBeArray();
        expect($allData[SessionKey::FORM_DATA->value])->toBeArray();
        expect($allData[SessionKey::CSRF->value])->toBeArray();
    });

    test('components work immediately after fromArray initialization', function () {
        // 1. Session-Daten mit bestehenden Komponenten-Daten laden
        $existingData = [
            SessionKey::FLASH->value => [
                'success' => ['Pre-existing message'],
            ],
            SessionKey::VALIDATION_ERRORS->value => [
                'contact_form' => [
                    'email' => ['Pre-existing error'],
                ],
            ],
            SessionKey::FORM_DATA->value => [
                'contact_form' => ['name' => 'Pre-existing Name'],
            ],
            'user_id' => 456,
        ];

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

        // 2. Komponenten sollten sofort die existierenden Daten haben
        expect($session->flash->get('success'))->toBe(['Pre-existing message']);
        $contactFormErrors = $session->validation->get('contact_form');
        expect($contactFormErrors['email'])->toBe(['Pre-existing error']);
        expect($session->form->get('contact_form'))->toBe(['name' => 'Pre-existing Name']);
        expect($session->get('user_id'))->toBe(456);

        // 3. Neue Daten sollten zu den existierenden hinzugefügt werden
        $session->flash->add('info', 'New message');
        $session->validation->add('login_form', ['password' => ['New error']]);

        expect($session->flash->get('info'))->toBe(['New message']);
        $loginFormErrors = $session->validation->get('login_form');
        expect($loginFormErrors['password'])->toBe(['New error']);

        // Die ursprünglichen Flash-Messages sollten nach dem Abrufen gelöscht sein
        expect($session->flash->get('success'))->toBe([]); // Flash wurde bereits abgerufen
    });

    test('component keys are created when components are first used', function () {
        // 1. Leere Session erstellen
        $session = new Session($this->sessionId, $this->clock, $this->csrfTokenGenerator);
        $session->fromArray([]);

        // 2. Komponenten verwenden um ihre Keys zu initialisieren
        $session->flash->add('test', 'message');
        $session->validation->add('form', ['field' => ['error']]);
        $session->form->store('form', ['data' => 'value']);
        $session->csrf->generateToken('form');

        // 3. Alle Komponenten-Keys sollten jetzt existieren
        $allData = $session->all();

        expect($allData)->toHaveKey(SessionKey::FLASH->value);
        expect($allData)->toHaveKey(SessionKey::VALIDATION_ERRORS->value);
        expect($allData)->toHaveKey(SessionKey::FORM_DATA->value);
        expect($allData)->toHaveKey(SessionKey::CSRF->value);

        // 4. Sie sollten die erwarteten Daten enthalten
        expect($allData[SessionKey::FLASH->value])->toHaveKey('test');
        expect($allData[SessionKey::VALIDATION_ERRORS->value])->toHaveKey('form');
        expect($allData[SessionKey::FORM_DATA->value])->toHaveKey('form');
        expect($allData[SessionKey::CSRF->value])->toHaveKey('form');
    });
});
