"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
// --- Mock data store ---
const channelStore = new Map();
vitest_1.vi.mock('../data-store/index.js', () => ({
    dataStore: {
        saveCharacterChannelConfig: vitest_1.vi.fn(async (id, config) => {
            channelStore.set(id, structuredClone(config));
        }),
        getCharacterChannelConfig: vitest_1.vi.fn(async (id) => {
            const v = channelStore.get(id);
            return v ? structuredClone(v) : null;
        }),
    },
}));
vitest_1.vi.mock('../../utils/index.js', () => ({
    createModuleLogger: () => ({
        info: vitest_1.vi.fn(),
        warn: vitest_1.vi.fn(),
        error: vitest_1.vi.fn(),
        debug: vitest_1.vi.fn(),
    }),
}));
const channel_manager_js_1 = require("./channel-manager.js");
function createMockBotClient(overrides) {
    return {
        createChannel: vitest_1.vi.fn(async (title) => ({
            chatId: Math.floor(Math.random() * -1000000),
            inviteLink: `https://t.me/+${title.replace(/\s/g, '')}`,
        })),
        sendMessage: vitest_1.vi.fn(async () => { }),
        sendPhoto: vitest_1.vi.fn(async () => { }),
        sendPaidMedia: vitest_1.vi.fn(async () => { }),
        getChatMemberCount: vitest_1.vi.fn(async () => 42),
        ...overrides,
    };
}
function createMockDataStore() {
    return {
        saveCharacterChannelConfig: vitest_1.vi.fn(async (id, config) => {
            channelStore.set(id, structuredClone(config));
        }),
        getCharacterChannelConfig: vitest_1.vi.fn(async (id) => {
            const v = channelStore.get(id);
            return v ? structuredClone(v) : null;
        }),
    };
}
let manager;
let mockBot;
let mockStore;
(0, vitest_1.beforeEach)(() => {
    channelStore.clear();
    vitest_1.vi.clearAllMocks();
    mockBot = createMockBotClient();
    mockStore = createMockDataStore();
    manager = new channel_manager_js_1.ChannelManager(mockStore, (id) => (id === 'char-1' ? mockBot : undefined));
});
(0, vitest_1.afterEach)(() => {
    manager.stopAllSchedules();
});
(0, vitest_1.describe)('ChannelManager.createChannels', () => {
    (0, vitest_1.it)('should create normal and VIP channels', async () => {
        const config = await manager.createChannels('char-1', mockBot, 'Test Normal', 'Test VIP');
        (0, vitest_1.expect)(config.characterId).toBe('char-1');
        (0, vitest_1.expect)(config.normalChannel).toBeDefined();
        (0, vitest_1.expect)(config.normalChannel.title).toBe('Test Normal');
        (0, vitest_1.expect)(config.normalChannel.memberCount).toBe(0);
        (0, vitest_1.expect)(config.vipChannel).toBeDefined();
        (0, vitest_1.expect)(config.vipChannel.title).toBe('Test VIP');
        (0, vitest_1.expect)(config.vipChannel.starPrice).toBe(50);
        (0, vitest_1.expect)(config.createdAt).toBeTruthy();
        (0, vitest_1.expect)(config.updatedAt).toBeTruthy();
    });
    (0, vitest_1.it)('should call createChannel on bot client twice', async () => {
        await manager.createChannels('char-1', mockBot, 'Normal', 'VIP');
        (0, vitest_1.expect)(mockBot.createChannel).toHaveBeenCalledTimes(2);
        (0, vitest_1.expect)(mockBot.createChannel).toHaveBeenCalledWith('Normal', 'Public promotional content');
        (0, vitest_1.expect)(mockBot.createChannel).toHaveBeenCalledWith('VIP', 'VIP exclusive content — Stars access');
    });
    (0, vitest_1.it)('should persist config to data store', async () => {
        await manager.createChannels('char-1', mockBot, 'Normal', 'VIP');
        (0, vitest_1.expect)(mockStore.saveCharacterChannelConfig).toHaveBeenCalledWith('char-1', vitest_1.expect.objectContaining({ characterId: 'char-1' }));
    });
    (0, vitest_1.it)('should store invite links from bot client', async () => {
        const bot = createMockBotClient({
            createChannel: vitest_1.vi.fn(async (title) => ({
                chatId: -100,
                inviteLink: `https://t.me/+invite_${title}`,
            })),
        });
        const config = await manager.createChannels('char-1', bot, 'N', 'V');
        (0, vitest_1.expect)(config.normalChannel.inviteLink).toBe('https://t.me/+invite_N');
        (0, vitest_1.expect)(config.vipChannel.inviteLink).toBe('https://t.me/+invite_V');
    });
});
(0, vitest_1.describe)('ChannelManager.getChannelConfig', () => {
    (0, vitest_1.it)('should return null for unknown character', async () => {
        const result = await manager.getChannelConfig('unknown');
        (0, vitest_1.expect)(result).toBeNull();
    });
    (0, vitest_1.it)('should return config after creation', async () => {
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        const config = await manager.getChannelConfig('char-1');
        (0, vitest_1.expect)(config).not.toBeNull();
        (0, vitest_1.expect)(config.characterId).toBe('char-1');
    });
    (0, vitest_1.it)('should load from store if not cached', async () => {
        const stored = {
            characterId: 'char-2',
            normalChannel: { chatId: -200, title: 'Stored Normal', memberCount: 10 },
            vipChannel: { chatId: -201, title: 'Stored VIP', memberCount: 5, starPrice: 100 },
            createdAt: '2024-01-01T00:00:00Z',
            updatedAt: '2024-01-01T00:00:00Z',
        };
        channelStore.set('char-2', stored);
        const manager2 = new channel_manager_js_1.ChannelManager(mockStore, () => undefined);
        const config = await manager2.getChannelConfig('char-2');
        (0, vitest_1.expect)(config).not.toBeNull();
        (0, vitest_1.expect)(config.normalChannel.title).toBe('Stored Normal');
    });
});
(0, vitest_1.describe)('ChannelManager.updateChannelConfig', () => {
    (0, vitest_1.beforeEach)(async () => {
        await manager.createChannels('char-1', mockBot, 'Normal', 'VIP');
    });
    (0, vitest_1.it)('should update publishSchedule', async () => {
        const original = await manager.getChannelConfig('char-1');
        const updated = await manager.updateChannelConfig('char-1', { publishSchedule: '0 9 * * *' });
        (0, vitest_1.expect)(updated.publishSchedule).toBe('0 9 * * *');
        (0, vitest_1.expect)(updated.updatedAt).toBeTruthy();
        (0, vitest_1.expect)(updated.createdAt).toBe(original.createdAt);
    });
    (0, vitest_1.it)('should merge normalChannel updates', async () => {
        const original = await manager.getChannelConfig('char-1');
        const updated = await manager.updateChannelConfig('char-1', {
            normalChannel: { chatId: original.normalChannel.chatId, title: 'Updated Normal', memberCount: 100 },
        });
        (0, vitest_1.expect)(updated.normalChannel.title).toBe('Updated Normal');
        (0, vitest_1.expect)(updated.normalChannel.memberCount).toBe(100);
    });
    (0, vitest_1.it)('should merge vipChannel updates', async () => {
        const updated = await manager.updateChannelConfig('char-1', {
            vipChannel: { chatId: -999, title: 'Premium VIP', memberCount: 50, starPrice: 200 },
        });
        (0, vitest_1.expect)(updated.vipChannel.starPrice).toBe(200);
        (0, vitest_1.expect)(updated.vipChannel.title).toBe('Premium VIP');
    });
    (0, vitest_1.it)('should not allow overriding characterId or createdAt', async () => {
        const original = await manager.getChannelConfig('char-1');
        const updated = await manager.updateChannelConfig('char-1', {
            characterId: 'hacked',
            createdAt: '1999-01-01T00:00:00Z',
        });
        (0, vitest_1.expect)(updated.characterId).toBe('char-1');
        (0, vitest_1.expect)(updated.createdAt).toBe(original.createdAt);
    });
    (0, vitest_1.it)('should throw for non-existent character', async () => {
        await (0, vitest_1.expect)(manager.updateChannelConfig('unknown', {})).rejects.toThrow('Channel config not found');
    });
    (0, vitest_1.it)('should persist updated config', async () => {
        await manager.updateChannelConfig('char-1', { publishSchedule: '*/5 * * * *' });
        (0, vitest_1.expect)(mockStore.saveCharacterChannelConfig).toHaveBeenCalled();
    });
});
(0, vitest_1.describe)('ChannelManager.publishToNormal', () => {
    (0, vitest_1.beforeEach)(async () => {
        await manager.createChannels('char-1', mockBot, 'Normal', 'VIP');
    });
    (0, vitest_1.it)('should send text message to normal channel', async () => {
        await manager.publishToNormal('char-1', { text: 'Hello world!' });
        (0, vitest_1.expect)(mockBot.sendMessage).toHaveBeenCalledWith(vitest_1.expect.any(Number), 'Hello world!');
    });
    (0, vitest_1.it)('should send photo to normal channel', async () => {
        await manager.publishToNormal('char-1', { photoUrl: 'https://example.com/photo.jpg', caption: 'Check this out' });
        (0, vitest_1.expect)(mockBot.sendPhoto).toHaveBeenCalledWith(vitest_1.expect.any(Number), 'https://example.com/photo.jpg', 'Check this out');
    });
    (0, vitest_1.it)('should prefer photo over text when both provided', async () => {
        await manager.publishToNormal('char-1', { text: 'fallback', photoUrl: 'https://example.com/photo.jpg' });
        (0, vitest_1.expect)(mockBot.sendPhoto).toHaveBeenCalled();
        (0, vitest_1.expect)(mockBot.sendMessage).not.toHaveBeenCalled();
    });
    (0, vitest_1.it)('should throw if normal channel not configured', async () => {
        await (0, vitest_1.expect)(manager.publishToNormal('unknown', { text: 'hi' })).rejects.toThrow('Normal channel not configured');
    });
    (0, vitest_1.it)('should throw if bot client not available', async () => {
        // Create channels with a direct bot, then try to publish with a manager that can't find the bot
        const noBot = new channel_manager_js_1.ChannelManager(mockStore, () => undefined);
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        await (0, vitest_1.expect)(noBot.publishToNormal('char-1', { text: 'hi' })).rejects.toThrow('Bot client not available');
    });
});
(0, vitest_1.describe)('ChannelManager.publishToVIP', () => {
    (0, vitest_1.beforeEach)(async () => {
        await manager.createChannels('char-1', mockBot, 'Normal', 'VIP');
    });
    (0, vitest_1.it)('should send paid media to VIP channel', async () => {
        await manager.publishToVIP('char-1', 'https://example.com/vip.jpg', 100, 'Exclusive!');
        (0, vitest_1.expect)(mockBot.sendPaidMedia).toHaveBeenCalledWith({
            chat_id: vitest_1.expect.any(Number),
            star_count: 100,
            media: [{ type: 'photo', media: 'https://example.com/vip.jpg' }],
            caption: 'Exclusive!',
        });
    });
    (0, vitest_1.it)('should use default starPrice when starCount is 0', async () => {
        await manager.publishToVIP('char-1', 'https://example.com/vip.jpg', 0);
        (0, vitest_1.expect)(mockBot.sendPaidMedia).toHaveBeenCalledWith(vitest_1.expect.objectContaining({ star_count: 50 }));
    });
    (0, vitest_1.it)('should throw if VIP channel not configured', async () => {
        await (0, vitest_1.expect)(manager.publishToVIP('unknown', 'url', 10)).rejects.toThrow('VIP channel not configured');
    });
    (0, vitest_1.it)('should throw if bot client not available', async () => {
        const noBot = new channel_manager_js_1.ChannelManager(mockStore, () => undefined);
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        await (0, vitest_1.expect)(noBot.publishToVIP('char-1', 'url', 10)).rejects.toThrow('Bot client not available');
    });
});
(0, vitest_1.describe)('ChannelManager.getStats', () => {
    (0, vitest_1.beforeEach)(async () => {
        await manager.createChannels('char-1', mockBot, 'Normal', 'VIP');
    });
    (0, vitest_1.it)('should return member counts from Telegram API', async () => {
        const stats = await manager.getStats('char-1');
        (0, vitest_1.expect)(stats).not.toBeNull();
        (0, vitest_1.expect)(stats.normalMembers).toBe(42);
        (0, vitest_1.expect)(stats.vipMembers).toBe(42);
    });
    (0, vitest_1.it)('should call getChatMemberCount for both channels', async () => {
        await manager.getStats('char-1');
        (0, vitest_1.expect)(mockBot.getChatMemberCount).toHaveBeenCalledTimes(2);
    });
    (0, vitest_1.it)('should return null for unknown character', async () => {
        const stats = await manager.getStats('unknown');
        (0, vitest_1.expect)(stats).toBeNull();
    });
    (0, vitest_1.it)('should return null if bot client not available', async () => {
        const noBot = new channel_manager_js_1.ChannelManager(mockStore, () => undefined);
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        const stats = await noBot.getStats('char-1');
        (0, vitest_1.expect)(stats).toBeNull();
    });
    (0, vitest_1.it)('should persist updated member counts', async () => {
        await manager.getStats('char-1');
        (0, vitest_1.expect)(mockStore.saveCharacterChannelConfig).toHaveBeenCalled();
    });
    (0, vitest_1.it)('should handle API errors gracefully', async () => {
        const failBot = createMockBotClient({
            getChatMemberCount: vitest_1.vi.fn(async () => { throw new Error('API error'); }),
        });
        const failManager = new channel_manager_js_1.ChannelManager(mockStore, () => failBot);
        await failManager.createChannels('char-1', failBot, 'N', 'V');
        const stats = await failManager.getStats('char-1');
        (0, vitest_1.expect)(stats).not.toBeNull();
        (0, vitest_1.expect)(stats.normalMembers).toBe(0);
        (0, vitest_1.expect)(stats.vipMembers).toBe(0);
    });
});
(0, vitest_1.describe)('ChannelManager scheduling', () => {
    (0, vitest_1.it)('should start a cron schedule', async () => {
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        (0, vitest_1.expect)(() => manager.startSchedule('char-1', '0 9 * * *')).not.toThrow();
    });
    (0, vitest_1.it)('should throw for invalid cron expression', () => {
        (0, vitest_1.expect)(() => manager.startSchedule('char-1', 'not-a-cron')).toThrow('Invalid cron expression');
    });
    (0, vitest_1.it)('should stop a schedule', async () => {
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        manager.startSchedule('char-1', '0 9 * * *');
        (0, vitest_1.expect)(() => manager.stopSchedule('char-1')).not.toThrow();
    });
    (0, vitest_1.it)('should stop all schedules', async () => {
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        manager.startSchedule('char-1', '0 9 * * *');
        (0, vitest_1.expect)(() => manager.stopAllSchedules()).not.toThrow();
    });
    (0, vitest_1.it)('should replace existing schedule on restart', async () => {
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        manager.startSchedule('char-1', '0 9 * * *');
        manager.startSchedule('char-1', '0 18 * * *');
        // No error means old was stopped and new was started
        manager.stopAllSchedules();
    });
    (0, vitest_1.it)('should start schedule when publishSchedule is updated', async () => {
        await manager.createChannels('char-1', mockBot, 'N', 'V');
        await manager.updateChannelConfig('char-1', { publishSchedule: '*/30 * * * *' });
        // Schedule should be running — stop it to verify no error
        manager.stopAllSchedules();
    });
});
//# sourceMappingURL=channel-manager.test.js.map