/**
 * EditorStore Unit Tests
 * 
 * Testing approach: Since EditorStore uses Svelte 5 runes ($state) which require
 * Svelte compilation, we test the logic by:
 * 1. Extracting and testing pure validation/helper functions
 * 2. Mocking localStorage for persistence tests
 * 3. Testing the store's public API through a mock implementation
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { GameSettings, Team, Round, FinalRound, Question, Category } from '$lib/types/kuldvillak';
import { DEFAULT_SETTINGS } from '$lib/types/kuldvillak';

// ============================================
// Test Helpers - Replicate pure functions from store
// ============================================

function generateId(): string {
    return crypto.randomUUID();
}

function createQuestion(points: number): Question {
    return {
        id: generateId(),
        question: "",
        answer: "",
        points,
        isDailyDouble: false,
        isRevealed: false,
    };
}

function createCategory(settings: GameSettings, multiplier: number = 1): Category {
    return {
        id: generateId(),
        name: "",
        questions: settings.pointValues.map((p) => createQuestion(p * multiplier)),
    };
}

function createRound(name: string, multiplier: number, settings: GameSettings): Round {
    return {
        id: generateId(),
        name,
        categories: Array.from({ length: settings.categoriesPerRound }, () =>
            createCategory(settings, multiplier)
        ),
        pointMultiplier: multiplier,
    };
}

// Validation function extracted for testing
function validateGame(
    teams: Team[],
    rounds: Round[],
    settings: GameSettings,
    finalRound: FinalRound
): string | null {
    if (teams.length < 2) {
        return "At least 2 players required";
    }

    for (let i = 0; i < rounds.length; i++) {
        const hasQuestions = rounds[i].categories.some((cat) =>
            cat.questions.some((q) => q.question.trim())
        );
        if (!hasQuestions) {
            return `Round ${i + 1} needs at least one question`;
        }
    }

    if (settings.enableFinalRound && !finalRound.question.trim()) {
        return "Final round question is required";
    }

    return null;
}

// Count daily doubles helper
function countDailyDoubles(rounds: Round[], roundIndex: number): number {
    return rounds[roundIndex]?.categories.reduce(
        (sum, cat) => sum + cat.questions.filter((q) => q.isDailyDouble).length,
        0
    ) ?? 0;
}

// ============================================
// Mock localStorage
// ============================================

const AUTOSAVE_KEY = "kuldvillak-editor-autosave";

function createMockLocalStorage() {
    let store: Record<string, string> = {};
    return {
        getItem: vi.fn((key: string) => store[key] ?? null),
        setItem: vi.fn((key: string, value: string) => { store[key] = value; }),
        removeItem: vi.fn((key: string) => { delete store[key]; }),
        clear: vi.fn(() => { store = {}; }),
        get _store() { return store; },
    };
}

// ============================================
// Tests
// ============================================

describe('EditorStore Logic', () => {
    let mockLocalStorage: ReturnType<typeof createMockLocalStorage>;

    beforeEach(() => {
        mockLocalStorage = createMockLocalStorage();
        vi.stubGlobal('localStorage', mockLocalStorage);
    });

    afterEach(() => {
        vi.unstubAllGlobals();
    });

    // ============================================
    // Helper Function Tests
    // ============================================

    describe('createQuestion', () => {
        it('should create a question with correct structure', () => {
            const q = createQuestion(100);
            expect(q.points).toBe(100);
            expect(q.question).toBe("");
            expect(q.answer).toBe("");
            expect(q.isDailyDouble).toBe(false);
            expect(q.isRevealed).toBe(false);
            expect(q.id).toBeDefined();
        });

        it('should generate unique IDs', () => {
            const q1 = createQuestion(100);
            const q2 = createQuestion(100);
            expect(q1.id).not.toBe(q2.id);
        });
    });

    describe('createCategory', () => {
        it('should create category with correct number of questions', () => {
            const cat = createCategory(DEFAULT_SETTINGS, 1);
            expect(cat.questions.length).toBe(DEFAULT_SETTINGS.pointValues.length);
        });

        it('should apply multiplier to point values', () => {
            const cat = createCategory(DEFAULT_SETTINGS, 2);
            const expectedPoints = DEFAULT_SETTINGS.pointValues.map(p => p * 2);
            cat.questions.forEach((q, i) => {
                expect(q.points).toBe(expectedPoints[i]);
            });
        });
    });

    describe('createRound', () => {
        it('should create round with correct structure', () => {
            const round = createRound("Test Round", 1, DEFAULT_SETTINGS);
            expect(round.name).toBe("Test Round");
            expect(round.pointMultiplier).toBe(1);
            expect(round.categories.length).toBe(DEFAULT_SETTINGS.categoriesPerRound);
        });

        it('should apply multiplier to all questions', () => {
            const round = createRound("Double", 2, DEFAULT_SETTINGS);
            round.categories.forEach(cat => {
                cat.questions.forEach((q, i) => {
                    expect(q.points).toBe(DEFAULT_SETTINGS.pointValues[i] * 2);
                });
            });
        });
    });

    // ============================================
    // Validation Tests
    // ============================================

    describe('validateGame', () => {
        let validTeams: Team[];
        let validRounds: Round[];
        let validSettings: GameSettings;
        let validFinalRound: FinalRound;

        beforeEach(() => {
            validTeams = [
                { id: '1', name: 'Team 1', score: 0 },
                { id: '2', name: 'Team 2', score: 0 },
            ];
            validRounds = [createRound("Round 1", 1, DEFAULT_SETTINGS)];
            // Fill at least one question
            validRounds[0].categories[0].questions[0].question = "Test question?";
            validSettings = { ...DEFAULT_SETTINGS, enableFinalRound: false };
            validFinalRound = { category: "", question: "", answer: "" };
        });

        it('should pass with valid minimal configuration', () => {
            const result = validateGame(validTeams, validRounds, validSettings, validFinalRound);
            expect(result).toBeNull();
        });

        it('should fail with fewer than 2 teams', () => {
            const result = validateGame([validTeams[0]], validRounds, validSettings, validFinalRound);
            expect(result).toBe("At least 2 players required");
        });

        it('should fail with 0 teams', () => {
            const result = validateGame([], validRounds, validSettings, validFinalRound);
            expect(result).toBe("At least 2 players required");
        });

        it('should fail when round has no questions', () => {
            const emptyRounds = [createRound("Empty", 1, DEFAULT_SETTINGS)];
            const result = validateGame(validTeams, emptyRounds, validSettings, validFinalRound);
            expect(result).toBe("Round 1 needs at least one question");
        });

        it('should fail when second round has no questions', () => {
            const twoRounds = [
                validRounds[0],
                createRound("Round 2", 2, DEFAULT_SETTINGS), // No questions filled
            ];
            const result = validateGame(validTeams, twoRounds, validSettings, validFinalRound);
            expect(result).toBe("Round 2 needs at least one question");
        });

        it('should fail when final round enabled but no question', () => {
            const settingsWithFinal = { ...validSettings, enableFinalRound: true };
            const result = validateGame(validTeams, validRounds, settingsWithFinal, validFinalRound);
            expect(result).toBe("Final round question is required");
        });

        it('should pass when final round enabled and has question', () => {
            const settingsWithFinal = { ...validSettings, enableFinalRound: true };
            const finalWithQuestion = { category: "History", question: "What year?", answer: "1990" };
            const result = validateGame(validTeams, validRounds, settingsWithFinal, finalWithQuestion);
            expect(result).toBeNull();
        });

        it('should pass with whitespace-only question as empty', () => {
            validRounds[0].categories[0].questions[0].question = "   ";
            const result = validateGame(validTeams, validRounds, validSettings, validFinalRound);
            expect(result).toBe("Round 1 needs at least one question");
        });
    });

    // ============================================
    // Team Management Tests
    // ============================================

    describe('Team Management Logic', () => {
        it('canAddTeam should be true when under 6 teams', () => {
            const teams: Team[] = [
                { id: '1', name: 'Team 1', score: 0 },
                { id: '2', name: 'Team 2', score: 0 },
            ];
            expect(teams.length < 6).toBe(true);
        });

        it('canAddTeam should be false when at 6 teams', () => {
            const teams: Team[] = Array.from({ length: 6 }, (_, i) => ({
                id: String(i),
                name: `Team ${i + 1}`,
                score: 0,
            }));
            expect(teams.length < 6).toBe(false);
        });

        it('canRemoveTeam should be true when over 2 teams', () => {
            const teams: Team[] = [
                { id: '1', name: 'Team 1', score: 0 },
                { id: '2', name: 'Team 2', score: 0 },
                { id: '3', name: 'Team 3', score: 0 },
            ];
            expect(teams.length > 2).toBe(true);
        });

        it('canRemoveTeam should be false when at 2 teams', () => {
            const teams: Team[] = [
                { id: '1', name: 'Team 1', score: 0 },
                { id: '2', name: 'Team 2', score: 0 },
            ];
            expect(teams.length > 2).toBe(false);
        });

        it('addTeam should generate unique ID', () => {
            const teams: Team[] = [];
            const newTeam = { id: generateId(), name: 'New Team', score: 0 };
            teams.push(newTeam);
            expect(newTeam.id).toBeDefined();
            expect(typeof newTeam.id).toBe('string');
            expect(newTeam.id.length).toBeGreaterThan(0);
        });

        it('removeTeam should filter by ID', () => {
            const teams: Team[] = [
                { id: '1', name: 'Team 1', score: 0 },
                { id: '2', name: 'Team 2', score: 0 },
                { id: '3', name: 'Team 3', score: 0 },
            ];
            const filtered = teams.filter(t => t.id !== '2');
            expect(filtered.length).toBe(2);
            expect(filtered.find(t => t.id === '2')).toBeUndefined();
        });
    });

    // ============================================
    // Daily Double Tests
    // ============================================

    describe('Daily Double Logic', () => {
        it('countDailyDoubles should return 0 for round with no DDs', () => {
            const rounds = [createRound("Test", 1, DEFAULT_SETTINGS)];
            expect(countDailyDoubles(rounds, 0)).toBe(0);
        });

        it('countDailyDoubles should count correctly', () => {
            const rounds = [createRound("Test", 1, DEFAULT_SETTINGS)];
            rounds[0].categories[0].questions[0].isDailyDouble = true;
            rounds[0].categories[1].questions[2].isDailyDouble = true;
            expect(countDailyDoubles(rounds, 0)).toBe(2);
        });

        it('should respect maxDD limit logic', () => {
            const maxDD = 2;
            const currentDD = 2;
            const canAddMore = currentDD < maxDD;
            expect(canAddMore).toBe(false);
        });

        it('should allow toggle when under limit', () => {
            const maxDD = 2;
            const currentDD = 1;
            const canAddMore = currentDD < maxDD;
            expect(canAddMore).toBe(true);
        });
    });

    // ============================================
    // Persistence Tests
    // ============================================

    describe('Persistence Logic', () => {
        it('save should serialize data to localStorage', () => {
            const data = {
                name: "Test Game",
                settings: DEFAULT_SETTINGS,
                teams: [{ id: '1', name: 'Team 1', score: 0 }],
                rounds: [],
                finalRound: { category: "", question: "", answer: "" },
            };

            localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(data));

            expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
                AUTOSAVE_KEY,
                expect.any(String)
            );
        });

        it('load should parse valid localStorage data', () => {
            const savedData = {
                name: "Saved Game",
                settings: { ...DEFAULT_SETTINGS, defaultTimerSeconds: 10 },
                teams: [
                    { id: '1', name: 'Player 1', score: 100 },
                    { id: '2', name: 'Player 2', score: 200 },
                ],
                rounds: [],
                finalRound: { category: "History", question: "Q?", answer: "A" },
            };

            mockLocalStorage._store[AUTOSAVE_KEY] = JSON.stringify(savedData);
            const loaded = JSON.parse(localStorage.getItem(AUTOSAVE_KEY)!);

            expect(loaded.name).toBe("Saved Game");
            expect(loaded.settings.defaultTimerSeconds).toBe(10);
            expect(loaded.teams.length).toBe(2);
        });

        it('load should handle invalid JSON gracefully', () => {
            mockLocalStorage._store[AUTOSAVE_KEY] = "not valid json {{{";

            let error: Error | null = null;
            try {
                JSON.parse(localStorage.getItem(AUTOSAVE_KEY)!);
            } catch (e) {
                error = e as Error;
            }

            expect(error).not.toBeNull();
        });

        it('load should handle missing data gracefully', () => {
            const result = localStorage.getItem(AUTOSAVE_KEY);
            expect(result).toBeNull();
        });

        it('clearSaved should remove from localStorage', () => {
            mockLocalStorage._store[AUTOSAVE_KEY] = "some data";
            localStorage.removeItem(AUTOSAVE_KEY);

            expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(AUTOSAVE_KEY);
        });

        it('should handle legacy teamColors property', () => {
            const legacyData = {
                name: "Legacy Game",
                settings: {
                    ...DEFAULT_SETTINGS,
                    teamColors: ['red', 'blue'], // Legacy property
                },
                teams: [],
                rounds: [],
                finalRound: { category: "", question: "", answer: "" },
            };

            mockLocalStorage._store[AUTOSAVE_KEY] = JSON.stringify(legacyData);
            const loaded = JSON.parse(localStorage.getItem(AUTOSAVE_KEY)!);

            // Should be able to destructure and remove teamColors
            const { teamColors, ...cleanSettings } = loaded.settings;
            expect(teamColors).toEqual(['red', 'blue']);
            expect(cleanSettings.teamColors).toBeUndefined();
        });
    });

    // ============================================
    // Export/Import Tests
    // ============================================

    describe('Export/Import Logic', () => {
        it('exportGame should create valid JSON structure', () => {
            const gameData = {
                name: "Export Test",
                settings: DEFAULT_SETTINGS,
                teams: [
                    { id: '1', name: 'Team 1', score: 0 },
                    { id: '2', name: 'Team 2', score: 0 },
                ],
                rounds: [createRound("Round 1", 1, DEFAULT_SETTINGS)],
                finalRound: { category: "Cat", question: "Q?", answer: "A" },
            };

            const json = JSON.stringify(gameData, null, 2);
            const parsed = JSON.parse(json);

            expect(parsed.name).toBe("Export Test");
            expect(parsed.teams.length).toBe(2);
            expect(parsed.rounds.length).toBe(1);
        });

        it('exportGame should create Blob with correct type', () => {
            const json = JSON.stringify({ test: true });
            const blob = new Blob([json], { type: "application/json" });

            expect(blob.type).toBe("application/json");
            expect(blob.size).toBeGreaterThan(0);
        });

        it('importGame should validate required fields', () => {
            const validData = {
                settings: DEFAULT_SETTINGS,
                teams: [],
                rounds: [],
            };

            const invalidData1 = { teams: [], rounds: [] }; // Missing settings
            const invalidData2 = { settings: DEFAULT_SETTINGS, rounds: [] }; // Missing teams
            const invalidData3 = { settings: DEFAULT_SETTINGS, teams: [] }; // Missing rounds

            const hasRequired = (data: Record<string, unknown>) =>
                !!(data.settings && data.teams && data.rounds);

            expect(hasRequired(validData)).toBe(true);
            expect(hasRequired(invalidData1)).toBe(false);
            expect(hasRequired(invalidData2)).toBe(false);
            expect(hasRequired(invalidData3)).toBe(false);
        });

        it('should generate sanitized filename', () => {
            const gameName = "My Test Game 2024";
            const filename = `${gameName.replace(/\s+/g, "_") || "game"}.json`;
            expect(filename).toBe("My_Test_Game_2024.json");
        });

        it('should use default filename when name is empty', () => {
            const gameName = "";
            const filename = `${gameName.replace(/\s+/g, "_") || "game"}.json`;
            expect(filename).toBe("game.json");
        });
    });

    // ============================================
    // Round Management Tests
    // ============================================

    describe('Round Management Logic', () => {
        it('setRoundCount should reduce to 1 round', () => {
            let rounds = [
                createRound("Round 1", 1, DEFAULT_SETTINGS),
                createRound("Round 2", 2, DEFAULT_SETTINGS),
            ];

            if (rounds.length > 1) {
                rounds = [rounds[0]];
            }

            expect(rounds.length).toBe(1);
        });

        it('setRoundCount should add second round', () => {
            let rounds = [createRound("Round 1", 1, DEFAULT_SETTINGS)];

            if (rounds.length === 1) {
                rounds = [...rounds, createRound("Round 2", 2, DEFAULT_SETTINGS)];
            }

            expect(rounds.length).toBe(2);
            expect(rounds[1].pointMultiplier).toBe(2);
        });

        it('getPointsForRound should apply multiplier', () => {
            const base = [10, 20, 30, 40, 50];
            const round1Points = base.map(v => v * 1);
            const round2Points = base.map(v => v * 2);

            expect(round1Points).toEqual([10, 20, 30, 40, 50]);
            expect(round2Points).toEqual([20, 40, 60, 80, 100]);
        });

        it('updatePreset should reset to default values', () => {
            let settings = { ...DEFAULT_SETTINGS, pointValues: [5, 10, 15, 20, 25] };

            if (settings.pointValuePreset === "round1") {
                settings.pointValues = [10, 20, 30, 40, 50];
            }

            // Simulate preset change
            settings.pointValuePreset = "round1";
            settings.pointValues = [10, 20, 30, 40, 50];

            expect(settings.pointValues).toEqual([10, 20, 30, 40, 50]);
        });
    });

    // ============================================
    // Derived State Tests
    // ============================================

    describe('Derived State Logic', () => {
        it('totalQuestions should count all questions', () => {
            const rounds = [
                createRound("R1", 1, DEFAULT_SETTINGS),
                createRound("R2", 2, DEFAULT_SETTINGS),
            ];

            const total = rounds.reduce(
                (total, round) =>
                    total + round.categories.reduce(
                        (catTotal, cat) => catTotal + cat.questions.length,
                        0
                    ),
                0
            );

            const expectedPerRound = DEFAULT_SETTINGS.categoriesPerRound * DEFAULT_SETTINGS.questionsPerCategory;
            expect(total).toBe(expectedPerRound * 2);
        });

        it('filledQuestions should count only non-empty', () => {
            const rounds = [createRound("R1", 1, DEFAULT_SETTINGS)];
            rounds[0].categories[0].questions[0].question = "Filled";
            rounds[0].categories[0].questions[1].question = "Also filled";
            rounds[0].categories[1].questions[0].question = "Third";

            const filled = rounds.reduce(
                (total, round) =>
                    total + round.categories.reduce(
                        (catTotal, cat) =>
                            catTotal + cat.questions.filter((q) => q.question.trim()).length,
                        0
                    ),
                0
            );

            expect(filled).toBe(3);
        });
    });
});
