/**
 * Integration Tests: Teacher Agent Flow
 *
 * Tests the complete flow from user message → intent detection → prompt generation → LLM response.
 * These tests verify the integration between all components of the Teacher Agent system.
 *
 * Test Scope:
 * - Full flow: User message → Store → Service → LLM → Response
 * - Intent classification with prompt context
 * - Error handling across the entire pipeline
 * - State management throughout the flow
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { useChatStore } from '../lib/store/chat-store';
import { LLMService } from '../services/llm-service';
import { classifyIntent } from '../lib/llm/intent-detector';
import { generateTeacherPrompt } from '../lib/llm/prompt-engine';
import { db } from '../lib/db';
import type { ChatMessage } from '../lib/db';

describe('Teacher Agent Integration Tests', () => {
    beforeEach(async () => {
        await db.delete();
        await db.open();
        useChatStore.setState({ messages: [], isLoading: false, isProcessing: false, currentIntent: null });
        vi.restoreAllMocks();
    });

    afterEach(async () => {
        await db.delete();
    });

    describe('Complete Flow: Venting Intent', () => {
        it('should classify venting intent correctly and generate empathetic prompt', () => {
            const userInput = "I'm so frustrated with this bug, I can't figure it out";
            const chatHistory: ChatMessage[] = [];

            // Step 1: Classify intent
            const intent = classifyIntent(userInput);
            expect(intent).toBe('venting');

            // Step 2: Generate prompt
            const prompt = generateTeacherPrompt(userInput, chatHistory, intent);
            expect(prompt).toContain('empath');
            expect(prompt).toContain(userInput);
        });

        it('should flow through store with venting intent using streaming', async () => {
            // Mock LLM service streaming response
            // Mock LLM service streaming response
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementation(
                async (_input, _history, callbacks) => {
                    if (callbacks.onIntent) await callbacks.onIntent('venting');
                    if (callbacks.onToken) {
                        await callbacks.onToken("I hear ");
                        await callbacks.onToken("your frustration.");
                    }
                    if (callbacks.onComplete) await callbacks.onComplete("I hear your frustration.");
                }
            );

            // Send user message through store
            await useChatStore.getState().addMessage("I'm stuck on this bug", 'user');

            // Verify complete flow
            const { messages, currentIntent, isProcessing } = useChatStore.getState();

            // Should have user message + AI response
            expect(messages.length).toBeGreaterThanOrEqual(2);
            expect(messages[0]).toMatchObject({
                role: 'user',
                content: "I'm stuck on this bug",
            });
            expect(messages[1]).toMatchObject({
                role: 'assistant',
                content: "I hear your frustration.",
                intent: 'venting',
            });
            expect(currentIntent).toBe('venting');
            expect(isProcessing).toBe(false); // Should reset after completion
        });
    });

    describe('Complete Flow: Insight Intent', () => {
        it('should classify insight intent correctly and generate celebratory prompt', () => {
            const userInput = "I finally figured it out! The trick was to use async/await";
            const chatHistory: ChatMessage[] = [];

            // Step 1: Classify intent
            const intent = classifyIntent(userInput);
            expect(intent).toBe('insight');

            // Step 2: Generate prompt
            const prompt = generateTeacherPrompt(userInput, chatHistory, intent);
            expect(prompt).toContain('celebrat');
            expect(prompt).toContain(userInput);
        });

        it('should flow through store with insight intent using streaming', async () => {
            // Mock LLM service streaming response
            // Mock LLM service streaming response
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementation(
                async (_input, _history, callbacks) => {
                    if (callbacks.onIntent) await callbacks.onIntent('insight');
                    if (callbacks.onToken) {
                        await callbacks.onToken("That's ");
                        await callbacks.onToken("awesome!");
                    }
                    if (callbacks.onComplete) await callbacks.onComplete("That's awesome!");
                }
            );

            await useChatStore.getState().addMessage("I finally get it now", 'user');

            const { messages, currentIntent } = useChatStore.getState();

            expect(messages.length).toBeGreaterThanOrEqual(2);
            expect(messages[0].role).toBe('user');
            expect(messages[1].role).toBe('assistant');
            expect(messages[1].intent).toBe('insight');
            expect(messages[1].content).toBe("That's awesome!");
            expect(currentIntent).toBe('insight');
        });
    });

    describe('Complete Flow with Chat History', () => {
        it('should include chat history in prompt generation', async () => {
            // Set up initial conversation
            await useChatStore.getState().addMessage("I'm having trouble with React hooks", 'user');

            // Mock LLM service for first message
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementationOnce(
                async (_input, _history, callbacks) => {
                    if (callbacks.onIntent) await callbacks.onIntent('venting');
                    if (callbacks.onComplete) await callbacks.onComplete("What specific hook are you struggling with?");
                }
            );

            // Wait a bit for first response (mock is async)
            await new Promise(resolve => setTimeout(resolve, 10));

            // Mock for second message
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementationOnce(
                async (_input, _history, callbacks) => {
                    if (callbacks.onIntent) await callbacks.onIntent('venting');
                    if (callbacks.onComplete) await callbacks.onComplete("Ah, useEffect can be tricky. What's your dependency array?");
                }
            );

            // Send follow-up message
            await useChatStore.getState().addMessage("It's useEffect, I don't understand the dependencies", 'user');

            const { messages } = useChatStore.getState();

            // Should have: user1, assistant1, user2, assistant2
            expect(messages.length).toBeGreaterThanOrEqual(3);
            expect(messages[0].content).toContain('React hooks');
            expect(messages[messages.length - 1].role).toBe('assistant');
        });
    });

    describe('Error Handling Integration', () => {
        it('should handle LLM service errors gracefully', async () => {
            // Mock LLM service error
            // Mock LLM service error
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementation(
                async (_input, _history, callbacks) => {
                    if (callbacks.onError) {
                        await callbacks.onError({
                            success: false,
                            error: {
                                code: 'NETWORK_ERROR',
                                message: 'Could not connect to AI service',
                            }
                        });
                    }
                }
            );

            await useChatStore.getState().addMessage("Test message", 'user');

            const { messages, isProcessing } = useChatStore.getState();

            // Should have user message + error message
            expect(messages.length).toBeGreaterThanOrEqual(2);
            expect(messages[0].role).toBe('user');
            expect(messages[1].role).toBe('assistant');
            expect(messages[1].content).toContain('Could not connect');
            expect(isProcessing).toBe(false); // Should reset even on error
        });

        it('should handle unexpected errors with fallback', async () => {
            // Mock LLM service to throw
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockRejectedValue(
                new Error('Unexpected error')
            );

            await useChatStore.getState().addMessage("Test message", 'user');

            const { messages, isProcessing } = useChatStore.getState();

            // Should have user message + fallback error message
            expect(messages.length).toBeGreaterThanOrEqual(2);
            expect(messages[0].role).toBe('user');
            expect(messages[1].role).toBe('assistant');
            expect(isProcessing).toBe(false);
        });
    });

    describe('State Management Integration', () => {
        it('should set isProcessing and isTyping states correctly', async () => {
            // Mock streaming response
            const mockLLM = vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementation(
                async (prompt, history, callbacks) => {
                    // Simulate async processing
                    await new Promise(resolve => setTimeout(resolve, 10));

                    // 1. Detect Intent
                    if (callbacks.onIntent) {
                        await callbacks.onIntent('venting');
                    }

                    // 2. Stream tokens (SLOW DOWN to ensure test captures processing state)
                    const tokens = ['I', ' hear', ' you.'];
                    for (const token of tokens) {
                        await new Promise(resolve => setTimeout(resolve, 50));
                        if (callbacks.onToken) {
                            await callbacks.onToken(token);
                        }
                    }

                    // 3. Complete
                    if (callbacks.onComplete) {
                        await callbacks.onComplete('I hear you.');
                    }
                }
            );

            const addPromise = useChatStore.getState().addMessage("Test", 'user');

            // Check state initial state (Typing + Processing)
            await new Promise(resolve => setTimeout(resolve, 10));
            let { isProcessing, isTyping } = useChatStore.getState();
            expect(isProcessing).toBe(true);
            expect(isTyping).toBe(true);

            // Wait for first token check (Typing stops, Processing continues)
            // Wait for first token check (Typing stops, Processing continues)
            // Wait 70ms - should have started streaming (first token at ~60ms) but not finished (total time ~160ms)
            await new Promise(resolve => setTimeout(resolve, 70));
            ({ isProcessing, isTyping } = useChatStore.getState());
            expect(isProcessing).toBe(true);
            expect(isTyping).toBe(false); // Should be false after first token

            // Wait for completion
            await addPromise;
            ({ isProcessing, isTyping } = useChatStore.getState());
            expect(isProcessing).toBe(false);
            expect(isTyping).toBe(false);

            mockLLM.mockRestore();
        });

        it('should reset state even on error', async () => {
            vi.spyOn(LLMService, 'getTeacherResponseStream').mockImplementation(
                async (_input, _history, callbacks) => {
                    await new Promise(resolve => setTimeout(resolve, 10));
                    if (callbacks.onError) {
                        await callbacks.onError({
                            success: false,
                            error: { code: 'ERROR', message: 'Error' },
                        });
                    }
                }
            );

            await useChatStore.getState().addMessage("Test", 'user');

            const { isProcessing, isTyping } = useChatStore.getState();
            expect(isProcessing).toBe(false);
            expect(isTyping).toBe(false);
        });
    });

    describe('Prompt-Response Integration', () => {
        it('should generate appropriate prompts for different intents', () => {
            const ventingInput = "This is so frustrating";
            const insightInput = "I finally solved it";

            const ventingIntent = classifyIntent(ventingInput);
            const insightIntent = classifyIntent(insightInput);

            const ventingPrompt = generateTeacherPrompt(ventingInput, [], ventingIntent);
            const insightPrompt = generateTeacherPrompt(insightInput, [], insightIntent);

            // Venting prompt should emphasize empathy
            expect(ventingPrompt.toLowerCase()).toMatch(/empath|validate|support/);

            // Insight prompt should emphasize celebration and deepening
            expect(insightPrompt.toLowerCase()).toMatch(/celebrat|deepen|understand/);

            // Prompts should be different
            expect(ventingPrompt).not.toBe(insightPrompt);
        });
    });

    describe('Accuracy Requirements', () => {
        it('should maintain >85% accuracy on common venting patterns', () => {
            const ventingPatterns = [
                "I'm so frustrated",
                "This is broken",
                "I don't understand",
                "I'm stuck",
                "This error is driving me crazy",
                "Why isn't this working?",
                "I hate this bug",
                "I'm confused about this",
                "This makes no sense",
                "I keep getting this error",
            ];

            let correct = 0;
            ventingPatterns.forEach(pattern => {
                if (classifyIntent(pattern) === 'venting') correct++;
            });

            const accuracy = (correct / ventingPatterns.length) * 100;
            expect(accuracy).toBeGreaterThanOrEqual(85);
        });

        it('should maintain >85% accuracy on common insight patterns', () => {
            const insightPatterns = [
                "I finally figured it out",
                "I get it now",
                "The solution is to use useEffect",
                "I finally solved it",
                "Now I understand",
                "The trick was to add dependencies",
                "I fixed it by changing the state",
                "It's working now",
                "I found the solution",
                "This makes sense now",
            ];

            let correct = 0;
            insightPatterns.forEach(pattern => {
                if (classifyIntent(pattern) === 'insight') correct++;
            });

            const accuracy = (correct / insightPatterns.length) * 100;
            expect(accuracy).toBeGreaterThanOrEqual(85);
        });
    });
});
