import request from 'supertest';
import express from 'express';
import { createServer } from 'http';
import { LLMService } from '../src/services/LLMService';

// Mock the LLMService
jest.mock('../src/services/LLMService');

// Mock the ws module
const mockHandleWebSocketMessage = jest.fn();
const mockSessions = new Map<string, any>();
const mockBroadcastToSession = jest.fn();

jest.mock('../src/ws', () => ({
    sessions: mockSessions,
    handleWebSocketMessage: mockHandleWebSocketMessage,
    broadcastToSession: mockBroadcastToSession,
    SessionState: {
        SETUP: 'SETUP',
        GATHERING: 'GATHERING',
        HARMONIZING: 'HARMONIZING',
        FINAL: 'FINAL',
        ERROR: 'ERROR',
    },
}));

// Mock the routes
const app = express();
app.use(express.json());

// Import the actual router after mocks are set up
import sessionsRouter from '../src/routes/sessions';
app.use('/', sessionsRouter);

describe('POST /sessions', () => {
    beforeEach(() => {
        mockSessions.clear();
    });

    it('should create a new session and return a session ID', async () => {
        const response = await request(app)
            .post('/sessions')
            .send();

        expect(response.status).toBe(201);
        expect(response.body).toHaveProperty('sessionId');
        expect(typeof response.body.sessionId).toBe('string');
        expect(mockSessions.has(response.body.sessionId)).toBe(true);
    });
});

describe('POST /sessions/:sessionId/responses', () => {
    const testSessionId = 'test-session-id';
    const testUserId = 'test-user-id';

    beforeEach(() => {
        mockSessions.clear();
        mockHandleWebSocketMessage.mockClear();
        mockBroadcastToSession.mockClear();

        // Initialize a session for testing
        mockSessions.set(testSessionId, {
            state: 'GATHERING',
            topic: 'Test Topic',
            expectedResponses: 1,
            submittedCount: 0,
            responses: new Map(),
            clients: new Map(),
            finalResult: null,
        });
    });

    it('should accept a response with afraidToAsk and call handleWebSocketMessage', async () => {
        const responsePayload = {
            userId: testUserId,
            wants: ['More features'],
            accepts: ['Bug fixes'],
            afraidToAsk: 'A raise',
        };

        const response = await request(app)
            .post(`/sessions/${testSessionId}/responses`)
            .send(responsePayload);

        expect(response.status).toBe(202);
        expect(response.body).toEqual({ message: 'Response submission acknowledged and processed.' });
        expect(mockHandleWebSocketMessage).toHaveBeenCalledTimes(1);
        expect(mockHandleWebSocketMessage).toHaveBeenCalledWith(
            expect.any(Object), // dummyWs
            testSessionId,
            {
                type: 'SUBMIT_RESPONSE',
                clientId: testUserId,
                payload: {
                    response: {
                        wants: ['More features'],
                        accepts: ['Bug fixes'],
                        afraidToAsk: 'A raise',
                    },
                },
            }
        );
    });

    it('should return 404 if session is not found', async () => {
        const responsePayload = {
            userId: testUserId,
            wants: ['More features'],
            accepts: ['Bug fixes'],
            afraidToAsk: 'A raise',
        };

        const response = await request(app)
            .post(`/sessions/non-existent-session/responses`)
            .send(responsePayload);

        expect(response.status).toBe(404);
        expect(response.body).toEqual({ message: 'Session not found.' });
        expect(mockHandleWebSocketMessage).not.toHaveBeenCalled();
    });

    it('should return 500 if handleWebSocketMessage throws an error', async () => {
        mockHandleWebSocketMessage.mockRejectedValueOnce(new Error('WebSocket processing error'));

        const responsePayload = {
            userId: testUserId,
            wants: ['More features'],
            accepts: ['Bug fixes'],
            afraidToAsk: 'A raise',
        };

        const response = await request(app)
            .post(`/sessions/${testSessionId}/responses`)
            .send(responsePayload);

        expect(response.status).toBe(500);
        expect(response.body).toHaveProperty('message', 'Error processing response.');
        expect(response.body).toHaveProperty('error', 'WebSocket processing error');
        expect(mockHandleWebSocketMessage).toHaveBeenCalledTimes(1);
    });
});

describe('POST /sessions/:sessionId/terminate', () => {
    const testSessionId = 'session-to-terminate';

    beforeEach(() => {
        mockSessions.clear();
        // Initialize a session for testing termination
        mockSessions.set(testSessionId, {
            state: 'FINAL',
            topic: 'Test Topic',
            expectedResponses: 1,
            submittedCount: 1,
            responses: new Map([['client1', { wants: ['test'], accepts: [], noGoes: [], afraidToAsk: 'secret' }]]),
            clients: new Map(),
            finalResult: { goTo: 'test' },
        });
    });

    it('should terminate the session and purge its data', async () => {
        expect(mockSessions.has(testSessionId)).toBe(true);

        const response = await request(app)
            .post(`/sessions/${testSessionId}/terminate`)
            .send();

        expect(response.status).toBe(200);
        expect(response.body).toEqual({ message: 'Session terminated and data purged successfully.' });
        expect(mockSessions.has(testSessionId)).toBe(false);
    });

    it('should return 404 if session is not found', async () => {
        const response = await request(app)
            .post(`/sessions/non-existent-session/terminate`)
            .send();

        expect(response.status).toBe(404);
        expect(response.body).toEqual({ message: 'Session not found.' });
    });
});
