"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
// --- Backing stores for mocks ---
const historyStore = new Map();
function hKey(cId, uId) { return `${cId}:${uId}`; }
const mockDna = {
    id: 'char-1',
    createdAt: '2024-01-01T00:00:00Z',
    updatedAt: '2024-01-01T00:00:00Z',
    status: 'active',
    identity: {
        name: 'Test', age: 22, nationality: 'TR', city: 'Istanbul',
        occupation: 'Model', personality_tags: ['friendly'], mbti: 'ENFP', backstory: '',
    },
    physicalMeasurements: { height_cm: 170, weight_kg: 55, body_type: 'slim', measurements: { bust: 85, waist: 60, hips: 90 } },
    skinMap: { base: '#F5D0A9', shadow: '#D4A574', highlight: '#FFE4C4' },
    faceAnatomy: { eye_shape: 'almond', eye_color: '#8B4513', eyebrow_shape: 'arched', nose_type: 'straight', lip_shape: 'full', cheekbones: 'high', jawline: 'soft', distinctive_marks: [] },
    hairDNA: { style: 'wavy', length: 'long', texture: 'silky', root_color: '#3B2F2F', mid_color: '#5C4033', highlight_color: '#8B6914' },
    bodyBiometrics: { body_type_detail: 'hourglass', posture: 'upright', skin_texture: 'smooth' },
    profileView: { best_angle: 'left-45', signature_pose: 'hand-on-hip' },
    expressionMotion: { default_expression: 'smile', smile_type: 'warm', gaze_style: 'direct' },
    makeupStyle: { philosophy: 'natural', skin_finish: 'dewy', colors: { primary: '#FF69B4', secondary: '#FFB6C1', accent: '#FF1493' } },
    fashionStyle: { aesthetic: 'casual-chic', main_colors: ['#FFFFFF', '#000000'], signature_pieces: ['jeans', 'blazer'] },
    technicalSettings: { camera_body: 'Canon R5', portrait_lens: '85mm f/1.4', full_body_lens: '35mm f/1.4', film_stock: 'Portra 400' },
};
const mockProfile = {
    characterId: 'char-1',
    style: 'neseli',
    toneOfVoice: { formality: 'casual', emoji_frequency: 'high', message_length: 'medium', humor_level: 'frequent', flirtation_level: 'moderate' },
    languagePatterns: { common_phrases: ['süper'], filler_words: ['ya'], reaction_templates: {} },
    salesBehavior: { min_messages_before_pitch: 10, cooldown_minutes: 60, pitch_style: 'playful' },
};
// --- Mocks ---
vitest_1.vi.mock('../../utils/logger.js', () => ({
    createModuleLogger: () => ({ info: vitest_1.vi.fn(), warn: vitest_1.vi.fn(), error: vitest_1.vi.fn(), debug: vitest_1.vi.fn() }),
}));
const mockChatCompletion = vitest_1.vi.fn().mockResolvedValue({
    id: 'resp-1',
    choices: [{ message: { role: 'assistant', content: 'Merhaba! 😊' }, finish_reason: 'stop' }],
    usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 },
});
vitest_1.vi.mock('../openrouter/client.js', () => ({
    createOpenRouterClient: () => ({
        chatCompletion: mockChatCompletion,
    }),
}));
const mockQueueAdd = vitest_1.vi.fn().mockResolvedValue({ id: 'q-1' });
vitest_1.vi.mock('../queue-system/index.js', () => ({
    queue: { add: (...args) => mockQueueAdd(...args) },
}));
vitest_1.vi.mock('../timer-system/index.js', () => ({
    calculateDelay: vitest_1.vi.fn(() => 0.01),
    DEFAULT_TIMER_CONFIG: {
        baseDelay: { min: 5, max: 30 },
        busyScenarioDelay: { min: 15, max: 45 },
        typingSpeedCps: 5,
        nightHours: { start: 23, end: 7 },
        nightMultiplier: 2.5,
    },
}));
vitest_1.vi.mock('../prompt-library/index.js', () => ({
    getContextAwarePrompt: vitest_1.vi.fn().mockResolvedValue(null),
}));
vitest_1.vi.mock('../data-store/index.js', () => ({
    dataStore: {
        getCharacter: vitest_1.vi.fn(async (id) => id === 'char-1' ? structuredClone(mockDna) : null),
        getConversationProfile: vitest_1.vi.fn(async (id) => id === 'char-1' ? structuredClone(mockProfile) : null),
        getConversationPersona: vitest_1.vi.fn(async () => null),
        getTelegramEntities: vitest_1.vi.fn(async () => []),
        saveConversationHistory: vitest_1.vi.fn(async (h) => {
            historyStore.set(hKey(h.characterId, h.telegramUserId), structuredClone(h));
        }),
        getConversationHistory: vitest_1.vi.fn(async (cId, uId) => {
            const v = historyStore.get(hKey(cId, uId));
            return v ? structuredClone(v) : null;
        }),
        deleteConversation: vitest_1.vi.fn(async (cId, uId) => historyStore.delete(hKey(cId, uId))),
    },
}));
vitest_1.vi.mock('./sales-redirect.js', () => ({
    salesRedirector: {
        shouldPitch: vitest_1.vi.fn().mockReturnValue(false),
        generatePitchHint: vitest_1.vi.fn().mockReturnValue('test pitch hint'),
        recordPitch: vitest_1.vi.fn((h) => h),
    },
}));
const index_js_1 = require("./index.js");
const sales_redirect_js_1 = require("./sales-redirect.js");
let engine;
(0, vitest_1.beforeEach)(() => {
    vitest_1.vi.useFakeTimers();
    engine = new index_js_1.ConversationEngine();
    historyStore.clear();
    mockChatCompletion.mockClear();
    mockQueueAdd.mockClear();
    vitest_1.vi.mocked(sales_redirect_js_1.salesRedirector.shouldPitch).mockReturnValue(false);
});
(0, vitest_1.afterEach)(() => {
    engine._clearBuffers();
    vitest_1.vi.useRealTimers();
});
(0, vitest_1.describe)('ConversationEngine.handleIncomingMessage', () => {
    (0, vitest_1.it)('should buffer a single message and flush after delay', async () => {
        await engine.handleIncomingMessage('char-1', 100, 'hello', 'Ali');
        // Buffer should exist
        const buf = engine._getBuffer('char-1', 100);
        (0, vitest_1.expect)(buf).toBeDefined();
        (0, vitest_1.expect)(buf.texts).toEqual(['hello']);
        // Advance timer to trigger flush
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        // Advance a bit more for the response delay setTimeout
        await vitest_1.vi.advanceTimersByTimeAsync(100);
        // Buffer should be cleared
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeUndefined();
        // Should have called OpenRouter
        (0, vitest_1.expect)(mockChatCompletion).toHaveBeenCalledTimes(1);
        // Should have queued the response (after delay)
        (0, vitest_1.expect)(mockQueueAdd).toHaveBeenCalledWith('chat_response', 'char-1', vitest_1.expect.objectContaining({
            userId: 100,
            text: vitest_1.expect.any(String),
        }));
    });
    (0, vitest_1.it)('should merge consecutive messages within buffer window', async () => {
        await engine.handleIncomingMessage('char-1', 100, 'hello');
        await engine.handleIncomingMessage('char-1', 100, 'how are you?');
        await engine.handleIncomingMessage('char-1', 100, 'I missed you');
        const buf = engine._getBuffer('char-1', 100);
        (0, vitest_1.expect)(buf.texts).toEqual(['hello', 'how are you?', 'I missed you']);
        // Advance timer
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        // Advance for response delay
        await vitest_1.vi.advanceTimersByTimeAsync(100);
        // OpenRouter should receive merged text in history
        (0, vitest_1.expect)(mockChatCompletion).toHaveBeenCalledTimes(1);
    });
    (0, vitest_1.it)('should reset timer when new message arrives within buffer window', async () => {
        await engine.handleIncomingMessage('char-1', 100, 'first');
        // Advance 2 seconds (within 3s window)
        await vitest_1.vi.advanceTimersByTimeAsync(2000);
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeDefined();
        // Send another message - should reset timer
        await engine.handleIncomingMessage('char-1', 100, 'second');
        // Advance 2 more seconds (4s total, but only 2s since last message)
        await vitest_1.vi.advanceTimersByTimeAsync(2000);
        // Buffer should still exist (timer was reset)
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeDefined();
        // Advance past the new timer
        await vitest_1.vi.advanceTimersByTimeAsync(1100);
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeUndefined();
        // Advance for response delay
        await vitest_1.vi.advanceTimersByTimeAsync(100);
        (0, vitest_1.expect)(mockChatCompletion).toHaveBeenCalledTimes(1);
    });
    (0, vitest_1.it)('should handle separate buffers for different users', async () => {
        await engine.handleIncomingMessage('char-1', 100, 'user1 msg');
        await engine.handleIncomingMessage('char-1', 200, 'user2 msg');
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeDefined();
        (0, vitest_1.expect)(engine._getBuffer('char-1', 200)).toBeDefined();
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        // Advance for response delays
        await vitest_1.vi.advanceTimersByTimeAsync(100);
        (0, vitest_1.expect)(mockChatCompletion).toHaveBeenCalledTimes(2);
        (0, vitest_1.expect)(mockQueueAdd).toHaveBeenCalledTimes(2);
    });
    (0, vitest_1.it)('should handle separate buffers for different characters', async () => {
        // char-2 won't have DNA/profile, so generateResponse will throw
        // but char-1 should work fine
        await engine.handleIncomingMessage('char-1', 100, 'msg for char1');
        const buf = engine._getBuffer('char-1', 100);
        (0, vitest_1.expect)(buf.texts).toEqual(['msg for char1']);
    });
});
(0, vitest_1.describe)('ConversationEngine.setMessageBuffer', () => {
    (0, vitest_1.it)('should use custom buffer duration', async () => {
        engine.setMessageBuffer('char-1', 100, 5000);
        await engine.handleIncomingMessage('char-1', 100, 'hello');
        // After 3s, buffer should still exist (custom is 5s)
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeDefined();
        // After 5s total, buffer should flush
        await vitest_1.vi.advanceTimersByTimeAsync(2000);
        // Advance for response delay
        await vitest_1.vi.advanceTimersByTimeAsync(100);
        (0, vitest_1.expect)(engine._getBuffer('char-1', 100)).toBeUndefined();
        (0, vitest_1.expect)(mockChatCompletion).toHaveBeenCalledTimes(1);
    });
    (0, vitest_1.it)('should clamp negative buffer to 0', () => {
        engine.setMessageBuffer('char-1', 100, -1000);
        // No error thrown
    });
});
(0, vitest_1.describe)('ConversationEngine.generateResponse', () => {
    (0, vitest_1.it)('should throw when character not found', async () => {
        await (0, vitest_1.expect)(engine.generateResponse('nonexistent', 100)).rejects.toThrow('Character not found');
    });
    (0, vitest_1.it)('should throw when profile not found', async () => {
        // Mock getCharacter to return something but profile to return null
        const { dataStore } = await import('../data-store/index.js');
        vitest_1.vi.mocked(dataStore.getConversationProfile).mockResolvedValueOnce(null);
        vitest_1.vi.mocked(dataStore.getCharacter).mockResolvedValueOnce(structuredClone(mockDna));
        vitest_1.vi.mocked(dataStore.getConversationPersona).mockResolvedValueOnce(null);
        await (0, vitest_1.expect)(engine.generateResponse('char-1', 100)).rejects.toThrow('Conversation profile not found');
    });
    (0, vitest_1.it)('should build system prompt and call OpenRouter', async () => {
        const response = await engine.generateResponse('char-1', 100);
        (0, vitest_1.expect)(response).toBe('Merhaba! 😊');
        (0, vitest_1.expect)(mockChatCompletion).toHaveBeenCalledTimes(1);
        const callArgs1 = mockChatCompletion.mock.calls[0];
        const messages1 = callArgs1[0];
        (0, vitest_1.expect)(messages1[0].role).toBe('system');
        (0, vitest_1.expect)(messages1[0].content).toContain('Test'); // character name from DNA
    });
    (0, vitest_1.it)('should include sales pitch hint when shouldPitch returns true', async () => {
        // Set up history so shouldPitch can work
        historyStore.set(hKey('char-1', 100), {
            characterId: 'char-1',
            telegramUserId: 100,
            userInfo: {},
            messages: [],
            lastActivity: new Date().toISOString(),
            totalMessages: 0,
            totalStarsSpent: 0,
        });
        vitest_1.vi.mocked(sales_redirect_js_1.salesRedirector.shouldPitch).mockReturnValue(true);
        await engine.generateResponse('char-1', 100);
        const callArgs2 = mockChatCompletion.mock.calls[0];
        const messages2 = callArgs2[0];
        const systemMessages = messages2.filter((m) => m.role === 'system');
        const pitchMessage = systemMessages.find((m) => typeof m.content === 'string' && m.content.includes('Satış yönlendirme ipucu'));
        (0, vitest_1.expect)(pitchMessage).toBeDefined();
    });
    (0, vitest_1.it)('should include recent history messages in the request', async () => {
        // Add some messages to history
        historyStore.set(hKey('char-1', 100), {
            characterId: 'char-1',
            telegramUserId: 100,
            userInfo: {},
            messages: [
                { id: '1', role: 'user', content: 'selam', timestamp: new Date().toISOString() },
                { id: '2', role: 'assistant', content: 'merhaba!', timestamp: new Date().toISOString() },
            ],
            lastActivity: new Date().toISOString(),
            totalMessages: 2,
            totalStarsSpent: 0,
        });
        await engine.generateResponse('char-1', 100);
        const callArgs3 = mockChatCompletion.mock.calls[0];
        const messages3 = callArgs3[0];
        // system + 2 history messages
        (0, vitest_1.expect)(messages3.length).toBeGreaterThanOrEqual(3);
        (0, vitest_1.expect)(messages3[1].role).toBe('user');
        (0, vitest_1.expect)(messages3[1].content).toBe('selam');
        (0, vitest_1.expect)(messages3[2].role).toBe('assistant');
        (0, vitest_1.expect)(messages3[2].content).toBe('merhaba!');
    });
});
(0, vitest_1.describe)('Message merging in flush', () => {
    (0, vitest_1.it)('should join multiple messages with newline', async () => {
        await engine.handleIncomingMessage('char-1', 100, 'line1');
        await engine.handleIncomingMessage('char-1', 100, 'line2');
        await engine.handleIncomingMessage('char-1', 100, 'line3');
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        // Check that the merged message was saved
        const history = historyStore.get(hKey('char-1', 100));
        (0, vitest_1.expect)(history).toBeDefined();
        const userMsg = history.messages.find(m => m.role === 'user');
        (0, vitest_1.expect)(userMsg?.content).toBe('line1\nline2\nline3');
    });
    (0, vitest_1.it)('should update userName when provided', async () => {
        await engine.handleIncomingMessage('char-1', 100, 'hello', 'TestUser');
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        const history = historyStore.get(hKey('char-1', 100));
        (0, vitest_1.expect)(history).toBeDefined();
        (0, vitest_1.expect)(history.userInfo.name).toBe('TestUser');
    });
});
(0, vitest_1.describe)('Error handling in flush', () => {
    (0, vitest_1.it)('should not throw when generateResponse fails during flush', async () => {
        mockChatCompletion.mockRejectedValueOnce(new Error('API down'));
        await engine.handleIncomingMessage('char-1', 100, 'hello');
        // Should not throw
        await vitest_1.vi.advanceTimersByTimeAsync(3100);
        // Queue should not have been called since generation failed
        (0, vitest_1.expect)(mockQueueAdd).not.toHaveBeenCalled();
    });
});
//# sourceMappingURL=index.test.js.map