/*
 * Runtime Web – Unified Serverless Framework for Full-Stack TypeScript Applications
 * Documentation: https://runtimeweb.com/docs/getting-started
 *
 * @template: ai-chat
 */

/**
 * Property-based tests for SessionManager service.
 *
 * Feature: ai-chat-template, Property 7: SessionId Round-Trip Persistence
 * Validates: Requirements 5.1, 5.2
 *
 * @module services/__tests__/session-manager.property.test
 */

import * as fc from 'fast-check';
import { SessionManager } from '../session-manager.service';

/**
 * Mock localStorage implementation for testing.
 * Provides a clean, isolated storage for each test.
 */
class MockLocalStorage implements Storage {
    private store: Map<string, string> = new Map();

    get length(): number {
        return this.store.size;
    }

    clear(): void {
        this.store.clear();
    }

    getItem(key: string): string | null {
        return this.store.get(key) ?? null;
    }

    key(index: number): string | null {
        const keys = Array.from(this.store.keys());
        return keys[index] ?? null;
    }

    removeItem(key: string): void {
        this.store.delete(key);
    }

    setItem(key: string, value: string): void {
        this.store.set(key, value);
    }
}

describe('SessionManager Property Tests', () => {
    let mockStorage: MockLocalStorage;

    beforeEach(() => {
        mockStorage = new MockLocalStorage();

        // Mock both window.localStorage and global localStorage
        // The SessionManager uses `localStorage` directly after checking `typeof window !== 'undefined'`
        Object.defineProperty(globalThis, 'localStorage', {
            value: mockStorage,
            writable: true,
            configurable: true,
        });

        // Ensure window exists and has localStorage
        if (typeof globalThis.window === 'undefined') {
            Object.defineProperty(globalThis, 'window', {
                value: { localStorage: mockStorage },
                writable: true,
                configurable: true,
            });
        } else {
            Object.defineProperty(globalThis.window, 'localStorage', {
                value: mockStorage,
                writable: true,
                configurable: true,
            });
        }
    });

    afterEach(() => {
        // Clean up mocks
        mockStorage.clear();
    });

    /**
     * Feature: ai-chat-template, Property 7: SessionId Round-Trip Persistence
     * Validates: Requirements 5.1, 5.2
     *
     * Property: Generated sessionIds SHALL match the pattern `session-{timestamp}-{random}`.
     * The timestamp is base36 encoded, and random is a base36 string.
     */
    describe('Property 7: SessionId Round-Trip Persistence', () => {
        it('generated sessionId matches the pattern session-{timestamp}-{random}', () => {
            fc.assert(
                fc.property(fc.integer({ min: 1, max: 1000 }), (_iteration) => {
                    // Create fresh SessionManager for each iteration
                    const manager = new SessionManager();
                    mockStorage.clear();

                    const sessionId = manager.getSessionId();

                    // Pattern: session-{base36_timestamp}-{base36_random}
                    // Base36 characters: 0-9, a-z
                    const pattern = /^session-[0-9a-z]+-[0-9a-z]+$/;

                    expect(sessionId).toMatch(pattern);

                    // Verify structure: exactly 3 parts separated by '-'
                    const parts = sessionId.split('-');
                    expect(parts.length).toBe(3);
                    expect(parts[0]).toBe('session');
                    expect(parts[1].length).toBeGreaterThan(0); // timestamp
                    expect(parts[2].length).toBeGreaterThan(0); // random
                }),
                { numRuns: 100 },
            );
        });

        it('multiple calls to getSessionId() return the same value', () => {
            fc.assert(
                fc.property(fc.integer({ min: 2, max: 50 }), (callCount) => {
                    // Create fresh SessionManager for each iteration
                    const manager = new SessionManager();
                    mockStorage.clear();

                    // First call generates the sessionId
                    const firstSessionId = manager.getSessionId();

                    // Subsequent calls should return the same value
                    for (let i = 1; i < callCount; i++) {
                        const subsequentSessionId = manager.getSessionId();
                        expect(subsequentSessionId).toBe(firstSessionId);
                    }
                }),
                { numRuns: 100 },
            );
        });

        it('sessionId is persisted to localStorage', () => {
            fc.assert(
                fc.property(fc.constant(true), () => {
                    // Create fresh SessionManager for each iteration
                    const manager = new SessionManager();
                    mockStorage.clear();

                    const sessionId = manager.getSessionId();

                    // Verify localStorage contains the sessionId
                    const storedValue = mockStorage.getItem('ai-chat-session-id');
                    expect(storedValue).toBe(sessionId);
                }),
                { numRuns: 100 },
            );
        });

        it('new SessionManager instance retrieves sessionId from localStorage', () => {
            fc.assert(
                fc.property(fc.constant(true), () => {
                    mockStorage.clear();

                    // First manager generates and stores sessionId
                    const manager1 = new SessionManager();
                    const originalSessionId = manager1.getSessionId();

                    // Second manager should retrieve the same sessionId from localStorage
                    const manager2 = new SessionManager();
                    const retrievedSessionId = manager2.getSessionId();

                    expect(retrievedSessionId).toBe(originalSessionId);
                }),
                { numRuns: 100 },
            );
        });

        it('each new session (cleared localStorage) generates unique sessionIds', () => {
            fc.assert(
                fc.property(fc.integer({ min: 2, max: 20 }), (sessionCount) => {
                    const sessionIds = new Set<string>();

                    for (let i = 0; i < sessionCount; i++) {
                        // Simulate new browser session by clearing localStorage
                        mockStorage.clear();
                        const manager = new SessionManager();
                        const sessionId = manager.getSessionId();
                        sessionIds.add(sessionId);
                    }

                    // All generated sessionIds should be unique
                    expect(sessionIds.size).toBe(sessionCount);
                }),
                { numRuns: 50 },
            );
        });
    });

    /**
     * Additional property tests for behavior mode round-trip persistence.
     * While not explicitly part of Property 7, these validate the complete
     * SessionManager contract for localStorage persistence.
     */
    describe('Behavior Mode Round-Trip Persistence', () => {
        const validBehaviorModes = ['precise', 'balanced', 'creative'] as const;

        it('setBehaviorMode persists value to localStorage', () => {
            fc.assert(
                fc.property(
                    fc.constantFrom(...validBehaviorModes),
                    (mode) => {
                        const manager = new SessionManager();
                        mockStorage.clear();

                        manager.setBehaviorMode(mode);

                        const storedValue = mockStorage.getItem('ai-chat-behavior-mode');
                        expect(storedValue).toBe(mode);
                    },
                ),
                { numRuns: 100 },
            );
        });

        it('getBehaviorMode retrieves persisted value', () => {
            fc.assert(
                fc.property(
                    fc.constantFrom(...validBehaviorModes),
                    (mode) => {
                        mockStorage.clear();

                        // First manager sets the mode
                        const manager1 = new SessionManager();
                        manager1.setBehaviorMode(mode);

                        // Second manager retrieves the mode
                        const manager2 = new SessionManager();
                        const retrievedMode = manager2.getBehaviorMode();

                        expect(retrievedMode).toBe(mode);
                    },
                ),
                { numRuns: 100 },
            );
        });

        it('getBehaviorMode returns "balanced" as default when no value stored', () => {
            fc.assert(
                fc.property(fc.constant(true), () => {
                    mockStorage.clear();
                    const manager = new SessionManager();

                    const mode = manager.getBehaviorMode();

                    expect(mode).toBe('balanced');
                }),
                { numRuns: 100 },
            );
        });

        it('behavior mode persists across multiple set/get cycles', () => {
            fc.assert(
                fc.property(
                    fc.array(fc.constantFrom(...validBehaviorModes), { minLength: 1, maxLength: 10 }),
                    (modes) => {
                        mockStorage.clear();
                        const manager = new SessionManager();

                        for (const mode of modes) {
                            manager.setBehaviorMode(mode);
                            const retrieved = manager.getBehaviorMode();
                            expect(retrieved).toBe(mode);
                        }

                        // Final value should be the last mode set
                        const finalMode = manager.getBehaviorMode();
                        expect(finalMode).toBe(modes[modes.length - 1]);
                    },
                ),
                { numRuns: 100 },
            );
        });
    });
});
