import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import type { SuccessfulPayment } from '../../types/telegram.js';

// --- Mocks ---
vi.mock('../../utils/index.js', () => ({
  createModuleLogger: () => ({
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    debug: vi.fn(),
  }),
}));

const mockCronTask = { stop: vi.fn() };
vi.mock('node-cron', () => ({
  default: {
    schedule: vi.fn((_expr: string, cb: () => void) => {
      (mockCronTask as any)._callback = cb;
      return mockCronTask;
    }),
  },
}));

vi.mock('../timer-system/index.js', () => ({
  calculateDelay: vi.fn(() => 2),
  DEFAULT_TIMER_CONFIG: {
    baseDelay: { min: 5, max: 30 },
    busyScenarioDelay: { min: 15, max: 45 },
    typingSpeedCps: 5,
    nightHours: { start: 23, end: 7 },
    nightMultiplier: 2.5,
  },
}));

import cron from 'node-cron';
import { AutomationManager } from './index.js';
import { calculateDelay } from '../timer-system/index.js';

// --- Mock factories ---
function createMockCharacterEngine() {
  return {
    getCharacter: vi.fn().mockResolvedValue({
      id: 'char-1',
      status: 'active',
      identity: { name: 'TestChar' },
    }),
    listCharacters: vi.fn().mockResolvedValue([
      { id: 'char-1', status: 'active' },
      { id: 'char-2', status: 'inactive' },
    ]),
  };
}

function createMockConversationEngine() {
  return {
    handleIncomingMessage: vi.fn().mockResolvedValue(undefined),
  };
}

function createMockImageEngine() {
  return {
    generateImage: vi.fn().mockResolvedValue('task-123'),
    templateManager: {
      listTemplates: vi.fn().mockResolvedValue([
        { id: 'tpl-1', name: 'Beach' },
        { id: 'tpl-2', name: 'Cafe' },
      ]),
    },
  };
}

function createMockBotClient() {
  return {
    sendChatAction: vi.fn().mockResolvedValue(undefined),
    sendMessage: vi.fn().mockResolvedValue(undefined),
    createChannel: vi.fn().mockResolvedValue({ chatId: 100, inviteLink: 'https://t.me/test' }),
    getChatMemberCount: vi.fn().mockResolvedValue(0),
    sendPhoto: vi.fn().mockResolvedValue(undefined),
    sendPaidMedia: vi.fn().mockResolvedValue(undefined),
  };
}

function createMockChannelManager() {
  return {
    createChannels: vi.fn().mockResolvedValue({
      characterId: 'char-1',
      normalChannel: { chatId: 100 },
      vipChannel: { chatId: 200 },
    }),
  };
}

function createMockStarsManager() {
  return {
    handleSuccessfulPayment: vi.fn().mockResolvedValue({ id: 'tx-1' }),
  };
}

function createMockTelegramManager(botClient?: any) {
  const client = botClient || createMockBotClient();
  const channelMgr = createMockChannelManager();
  const starsMgr = createMockStarsManager();
  return {
    getBotClient: vi.fn().mockReturnValue(client),
    getChannelManager: vi.fn().mockReturnValue(channelMgr),
    getStarsManager: vi.fn().mockReturnValue(starsMgr),
    _botClient: client,
    _channelMgr: channelMgr,
    _starsMgr: starsMgr,
  };
}

function createMockQueueSystem() {
  return {
    add: vi.fn().mockResolvedValue({ id: 'q-1' }),
  };
}

function createMockAnalyticsEngine() {
  return {
    calculateDailyAnalytics: vi.fn().mockResolvedValue({}),
  };
}

const DEFAULT_TIMER = {
  baseDelay: { min: 5, max: 30 },
  busyScenarioDelay: { min: 15, max: 45 },
  typingSpeedCps: 5,
  nightHours: { start: 23, end: 7 },
  nightMultiplier: 2.5,
};

function createManager(overrides: Record<string, any> = {}) {
  const charEngine = overrides.characterEngine || createMockCharacterEngine();
  const convEngine = overrides.conversationEngine || createMockConversationEngine();
  const imgEngine = overrides.imageEngine || createMockImageEngine();
  const telMgr = overrides.telegramManager || createMockTelegramManager();
  const queueSys = overrides.queueSystem || createMockQueueSystem();
  const timer = overrides.timerConfig || DEFAULT_TIMER;
  const analytics = overrides.analyticsEngine || createMockAnalyticsEngine();

  return {
    manager: new AutomationManager(
      charEngine as any,
      convEngine as any,
      imgEngine as any,
      telMgr as any,
      queueSys as any,
      timer,
      analytics as any,
    ),
    charEngine,
    convEngine,
    imgEngine,
    telMgr,
    queueSys,
    analytics,
  };
}

// =============================================
// Tests
// =============================================

let ctx: ReturnType<typeof createManager>;

beforeEach(() => {
  vi.clearAllMocks();
  vi.useFakeTimers();
  ctx = createManager();
});

afterEach(() => {
  ctx.manager.stopAutomation();
  vi.useRealTimers();
});

describe('AutomationManager — Status management', () => {
  it('should default to inactive for unknown characters', () => {
    expect(ctx.manager.getAutomationStatus('unknown')).toBe('inactive');
  });

  it('should set and get automation status', () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('active');
  });

  it('should track error status', () => {
    ctx.manager.setAutomationStatus('char-1', 'error');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('error');
  });

  it('should return all statuses', () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    ctx.manager.setAutomationStatus('char-2', 'inactive');
    const all = ctx.manager.getAllStatuses();
    expect(all.get('char-1')).toBe('active');
    expect(all.get('char-2')).toBe('inactive');
    expect(all.size).toBe(2);
  });
});

describe('AutomationManager — Character lifecycle', () => {
  it('onCharacterCreated should set status to active and create channels', async () => {
    await ctx.manager.onCharacterCreated('char-1');

    expect(ctx.manager.getAutomationStatus('char-1')).toBe('active');
    expect(ctx.telMgr.getBotClient).toHaveBeenCalledWith('char-1');
    expect(ctx.telMgr._channelMgr.createChannels).toHaveBeenCalledWith(
      'char-1',
      expect.anything(),
      'TestChar — Channel',
      'TestChar — VIP',
    );
  });

  it('onCharacterCreated should set error status on failure', async () => {
    ctx.telMgr._channelMgr.createChannels.mockRejectedValue(new Error('API error'));
    await ctx.manager.onCharacterCreated('char-1');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('error');
  });

  it('onCharacterCreated should skip channels when no bot client', async () => {
    ctx.telMgr.getBotClient.mockReturnValue(undefined);
    await ctx.manager.onCharacterCreated('char-1');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('active');
    expect(ctx.telMgr._channelMgr.createChannels).not.toHaveBeenCalled();
  });

  it('onCharacterActivated should set status to active', async () => {
    await ctx.manager.onCharacterActivated('char-1');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('active');
  });

  it('onCharacterDeactivated should set status to inactive', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    await ctx.manager.onCharacterDeactivated('char-1');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('inactive');
  });
});

describe('AutomationManager — Message handling', () => {
  it('should pass message to conversation engine when active', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    await ctx.manager.handleIncomingMessage('char-1', 42, 'hello');

    expect(ctx.convEngine.handleIncomingMessage).toHaveBeenCalledWith('char-1', 42, 'hello');
  });

  it('should ignore messages when automation is not active', async () => {
    // Default status is inactive
    await ctx.manager.handleIncomingMessage('char-1', 42, 'hello');
    expect(ctx.convEngine.handleIncomingMessage).not.toHaveBeenCalled();
  });

  it('should send typing indicator after partial delay', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    await ctx.manager.handleIncomingMessage('char-1', 42, 'hello');

    // Advance timers to trigger the typing setTimeout
    await vi.advanceTimersByTimeAsync(1500);

    expect(ctx.telMgr._botClient.sendChatAction).toHaveBeenCalledWith(42, 'typing');
  });

  it('should set error status on conversation engine failure', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    ctx.convEngine.handleIncomingMessage.mockRejectedValue(new Error('AI error'));

    await ctx.manager.handleIncomingMessage('char-1', 42, 'hello');
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('error');
  });
});

describe('AutomationManager — Stars payment', () => {
  const mockPayment: SuccessfulPayment = {
    currency: 'XTR',
    total_amount: 50,
    invoice_payload: '{"imageId":"img-1"}',
    telegram_payment_charge_id: 'charge-1',
    provider_payment_charge_id: 'provider-1',
  };

  it('should process payment when active', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    await ctx.manager.handleStarsPayment('char-1', 42, mockPayment);

    expect(ctx.telMgr._starsMgr.handleSuccessfulPayment).toHaveBeenCalledWith(
      'char-1',
      mockPayment,
      42,
      expect.anything(),
    );
  });

  it('should ignore payment when not active', async () => {
    await ctx.manager.handleStarsPayment('char-1', 42, mockPayment);
    expect(ctx.telMgr._starsMgr.handleSuccessfulPayment).not.toHaveBeenCalled();
  });

  it('should set error status on payment failure', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    ctx.telMgr._starsMgr.handleSuccessfulPayment.mockRejectedValue(new Error('Payment error'));

    await ctx.manager.handleStarsPayment('char-1', 42, mockPayment);
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('error');
  });

  it('should throw when no bot client for payment', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    ctx.telMgr.getBotClient.mockReturnValue(undefined);

    await ctx.manager.handleStarsPayment('char-1', 42, mockPayment);
    expect(ctx.manager.getAutomationStatus('char-1')).toBe('error');
  });
});

describe('AutomationManager — Content cycle', () => {
  it('should generate image for active character', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    await ctx.manager.runContentCycle('char-1');

    expect(ctx.imgEngine.templateManager.listTemplates).toHaveBeenCalled();
    expect(ctx.imgEngine.generateImage).toHaveBeenCalledWith(
      'char-1',
      expect.any(String),
    );
  });

  it('should skip content cycle when not active', async () => {
    await ctx.manager.runContentCycle('char-1');
    expect(ctx.imgEngine.generateImage).not.toHaveBeenCalled();
  });

  it('should handle no templates gracefully', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    ctx.imgEngine.templateManager.listTemplates.mockResolvedValue([]);

    await ctx.manager.runContentCycle('char-1');
    expect(ctx.imgEngine.generateImage).not.toHaveBeenCalled();
  });

  it('should not throw on image generation failure', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    ctx.imgEngine.generateImage.mockRejectedValue(new Error('KIE error'));

    // Should not throw
    await ctx.manager.runContentCycle('char-1');
  });
});

describe('AutomationManager — Scheduling', () => {
  it('should start automation with default cron expression', () => {
    ctx.manager.startAutomation();

    expect(cron.schedule).toHaveBeenCalledWith('0 */4 * * *', expect.any(Function));
    expect(ctx.manager.isRunning()).toBe(true);
  });

  it('should start automation with custom cron expression', () => {
    ctx.manager.startAutomation('0 */2 * * *');

    expect(cron.schedule).toHaveBeenCalledWith('0 */2 * * *', expect.any(Function));
    expect(ctx.manager.isRunning()).toBe(true);
  });

  it('should stop previous cron task when restarting', () => {
    ctx.manager.startAutomation();
    ctx.manager.startAutomation('0 */6 * * *');

    expect(mockCronTask.stop).toHaveBeenCalled();
  });

  it('should stop automation', () => {
    ctx.manager.startAutomation();
    ctx.manager.stopAutomation();

    expect(mockCronTask.stop).toHaveBeenCalled();
    expect(ctx.manager.isRunning()).toBe(false);
  });

  it('should handle stopAutomation when not started', () => {
    // Should not throw
    ctx.manager.stopAutomation();
    expect(ctx.manager.isRunning()).toBe(false);
  });

  it('cron callback should run content cycle for active characters only', async () => {
    ctx.manager.setAutomationStatus('char-1', 'active');
    // char-2 is inactive by default

    ctx.manager.startAutomation();

    // Execute the cron callback
    const cronCallback = (mockCronTask as any)._callback;
    await cronCallback();

    // Only char-1 (active status + active automation) should get content cycle
    expect(ctx.imgEngine.generateImage).toHaveBeenCalledTimes(1);
    expect(ctx.imgEngine.generateImage).toHaveBeenCalledWith('char-1', expect.any(String));
  });
});
