import { describe, it, expect, beforeEach, vi } from 'vitest'
import { ChatService } from './chat-service'
import { db } from '../lib/db'

describe('ChatService', () => {
    beforeEach(async () => {
        await db.delete()
        await db.open()
    })

    it('should save a message and return plain object', async () => {
        const message = {
            role: 'user' as const,
            content: 'Hello world',
            timestamp: Date.now()
        }

        // @ts-ignore - ChatService might be static or instance, testing static for now or instance
        const saved = await ChatService.saveMessage(message)

        expect(saved.id).toBeDefined()
        expect(saved.content).toBe('Hello world')

        // Verify it's in DB
        const count = await db.chatLogs.count()
        expect(count).toBe(1)
    })

    it('should retrieve history', async () => {
        // Story 3.2: Include sessionId when adding chat logs
        await db.chatLogs.add({ sessionId: 'session-1', role: 'user', content: 'Msg 1', timestamp: 1000 })
        await db.chatLogs.add({ sessionId: 'session-1', role: 'assistant', content: 'Msg 2', timestamp: 2000 })

        const history = await ChatService.getHistory()
        expect(history).toHaveLength(2)
        expect(history[0].content).toBe('Msg 1')
        expect(history[1].content).toBe('Msg 2')
    })

    it('should approve draft and copy to clipboard', async () => {
        // Mock DraftService
        const { DraftService } = await import('../lib/db/draft-service');
        const updateSpy = vi.spyOn(DraftService, 'updateDraftStatus').mockResolvedValue(true);
        const getSpy = vi.spyOn(DraftService, 'getDraftById').mockResolvedValue({
            id: 123,
            title: 'Test Draft',
            content: 'Draft content',
            tags: ['tag1'],
            sessionId: 'sess1',
            createdAt: Date.now(),
            status: 'draft'
        } as any);

        // Mock clipboard
        const writeTextMock = vi.fn().mockResolvedValue(undefined);
        Object.assign(navigator, {
            clipboard: {
                writeText: writeTextMock
            }
        });

        const result = await ChatService.approveDraft(123);

        expect(result).toBe(true);
        expect(updateSpy).toHaveBeenCalledWith(123, 'completed');
        expect(writeTextMock).toHaveBeenCalledWith('# Test Draft\n\nDraft content\n\nTags: tag1'); // Verify markdown formatting

        // Cleanup
        updateSpy.mockRestore();
        getSpy.mockRestore();
    })

    // Story 2.3: Refinement Loop Tests
    describe('Refinement Loop (Story 2.3)', () => {
        it('should start refinement mode with system message', async () => {
            const { DraftService } = await import('../lib/db/draft-service');
            const mockDraft = {
                id: 123,
                sessionId: 'session-1',
                title: 'Test Draft',
                content: 'Original content',
                tags: ['test'],
                createdAt: Date.now(),
                status: 'draft'
            };

            vi.spyOn(DraftService, 'getDraftById').mockResolvedValue(mockDraft as any);

            await ChatService.startRefinement(123);

            // Verify system message was added
            const history = await ChatService.getHistory();
            const systemMessage = history.find(msg => msg.content === 'What should we change?');
            expect(systemMessage).toBeDefined();
        });

        it('should submit refinement feedback and regenerate draft', async () => {
            const { DraftService } = await import('../lib/db/draft-service');
            const { LLMService } = await import('./llm-service');

            const regeneratedContent = '# Refined Test Draft\n\nRefined content - shorter\n\n**Tags:** [test, refined]';

            vi.spyOn(LLMService, 'regenerateDraft').mockResolvedValue({
                success: true,
                draft: {
                    content: regeneratedContent
                }
            } as any);

            const saveDraftSpy = vi.spyOn(DraftService, 'saveDraft').mockResolvedValue(456 as any);

            // Story 3.2: Include sessionId for cascade delete
            const mockOriginalDraft = {
                id: 123,
                sessionId: 'session-1',
                title: 'Test Draft',
                content: 'Original content',
                tags: ['test'],
                createdAt: Date.now(),
                status: 'draft'
            };

            await ChatService.submitRefinementFeedback('Make it shorter', mockOriginalDraft as any);

            expect(LLMService.regenerateDraft).toHaveBeenCalled();
            expect(saveDraftSpy).toHaveBeenCalledWith(
                expect.objectContaining({
                    status: 'regenerated',
                    title: 'Refined Test Draft'
                })
            );
        });

        it('should handle empty feedback gracefully', async () => {
            const { LLMService } = await import('./llm-service');

            vi.spyOn(LLMService, 'regenerateDraft').mockResolvedValue({
                success: true,
                draft: {
                    content: '# Test Draft\n\nOriginal content\n\n**Tags:** [test]'
                }
            } as any);

            // Story 3.2: Include sessionId for cascade delete
            const mockOriginalDraft = {
                id: 123,
                sessionId: 'session-1',
                title: 'Test Draft',
                content: 'Original content',
                tags: ['test'],
                createdAt: Date.now(),
                status: 'draft'
            };

            // Should not throw with empty feedback
            await expect(ChatService.submitRefinementFeedback('', mockOriginalDraft as any)).resolves.not.toThrow();
        });

        it('should handle very long feedback', async () => {
            const { LLMService } = await import('./llm-service');
            const longFeedback = 'Make it shorter. '.repeat(100);

            vi.spyOn(LLMService, 'regenerateDraft').mockResolvedValue({
                success: true,
                draft: {
                    content: '# Test Draft\n\nContent\n\n**Tags:** [test]'
                }
            } as any);

            // Story 3.2: Include sessionId for cascade delete
            const mockOriginalDraft = {
                id: 123,
                sessionId: 'session-1',
                title: 'Test Draft',
                content: 'Original content',
                tags: ['test'],
                createdAt: Date.now(),
                status: 'draft'
            };

            await expect(ChatService.submitRefinementFeedback(longFeedback, mockOriginalDraft as any)).resolves.not.toThrow();
        });

        it('should throw error when regeneration fails', async () => {
            const { LLMService } = await import('./llm-service');

            vi.spyOn(LLMService, 'regenerateDraft').mockResolvedValue({
                success: false,
                error: { message: 'LLM service unavailable' }
            } as any);

            // Story 3.2: Include sessionId for cascade delete
            const mockOriginalDraft = {
                id: 123,
                sessionId: 'session-1',
                title: 'Test Draft',
                content: 'Original content',
                tags: ['test'],
                createdAt: Date.now(),
                status: 'draft'
            };

            await expect(ChatService.submitRefinementFeedback('Make it shorter', mockOriginalDraft as any)).rejects.toThrow();
        });
    });
})
