import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { ConversationProfile } from '../../types/character.js';
import type { ConversationHistory, Message } from '../../types/conversation.js';
import { SalesRedirector, salesRedirector } from './sales-redirect.js';

function makeMessage(overrides: Partial<Message> & { role: Message['role'] }): Message {
  return {
    id: `msg-${Math.random().toString(36).slice(2, 8)}`,
    content: 'merhaba',
    timestamp: new Date().toISOString(),
    ...overrides,
  };
}

function makeHistory(messageCount: number, opts?: { withPitch?: boolean; pitchTimestamp?: string }): ConversationHistory {
  const messages: Message[] = [];
  const baseTime = new Date('2025-01-15T12:00:00Z').getTime();

  for (let i = 0; i < messageCount; i++) {
    const role = i % 2 === 0 ? 'user' : 'assistant';
    const ts = new Date(baseTime + i * 60_000).toISOString();
    messages.push(makeMessage({ role, timestamp: ts, content: role === 'user' ? 'selam nasılsın' : 'iyiyim sen?' }));
  }

  if (opts?.withPitch && messages.length > 0) {
    // Add a pitch on the last assistant message
    const lastAssistantIdx = messages.length - (messages.length % 2 === 0 ? 1 : 2);
    if (lastAssistantIdx >= 0 && messages[lastAssistantIdx]!.role === 'assistant') {
      messages[lastAssistantIdx] = {
        ...messages[lastAssistantIdx]!,
        timestamp: opts.pitchTimestamp ?? messages[lastAssistantIdx]!.timestamp,
        metadata: { starsPitchMade: true },
      };
    }
  }

  return {
    characterId: 'char-1',
    telegramUserId: 12345,
    userName: 'TestUser',
    userInfo: { name: 'Test', interests: [], notes: [] },
    messages,
    lastActivity: messages.length > 0 ? messages[messages.length - 1]!.timestamp : new Date().toISOString(),
    totalMessages: messages.length,
    totalStarsSpent: 0,
  };
}

function makeProfile(overrides?: Partial<ConversationProfile['salesBehavior']>): ConversationProfile {
  return {
    characterId: 'char-1',
    style: 'neseli',
    toneOfVoice: {
      formality: 'casual',
      emoji_frequency: 'high',
      message_length: 'short',
      humor_level: 'frequent',
      flirtation_level: 'subtle',
    },
    languagePatterns: {
      common_phrases: ['haha'],
      filler_words: ['yani'],
      reaction_templates: {},
    },
    salesBehavior: {
      min_messages_before_pitch: 5,
      cooldown_minutes: 30,
      pitch_style: 'playful',
      ...overrides,
    },
  };
}

describe('SalesRedirector', () => {
  let redirector: SalesRedirector;

  beforeEach(() => {
    redirector = new SalesRedirector();
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2025-01-15T14:00:00Z'));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('shouldPitch', () => {
    it('should return false when message count is below minimum', () => {
      const history = makeHistory(3);
      const profile = makeProfile({ min_messages_before_pitch: 5 });

      expect(redirector.shouldPitch(history, profile)).toBe(false);
    });

    it('should return true when message count meets minimum and no prior pitch', () => {
      const history = makeHistory(10);
      const profile = makeProfile({ min_messages_before_pitch: 5 });

      expect(redirector.shouldPitch(history, profile)).toBe(true);
    });

    it('should return false when cooldown has not elapsed since last pitch', () => {
      // Pitch made 10 minutes ago, cooldown is 30 minutes
      const tenMinutesAgo = new Date(Date.now() - 10 * 60_000).toISOString();
      const history = makeHistory(10, { withPitch: true, pitchTimestamp: tenMinutesAgo });
      const profile = makeProfile({ min_messages_before_pitch: 5, cooldown_minutes: 30 });

      expect(redirector.shouldPitch(history, profile)).toBe(false);
    });

    it('should return true when cooldown has elapsed since last pitch', () => {
      // Pitch made 60 minutes ago, cooldown is 30 minutes
      const sixtyMinutesAgo = new Date(Date.now() - 60 * 60_000).toISOString();
      const history = makeHistory(10, { withPitch: true, pitchTimestamp: sixtyMinutesAgo });
      const profile = makeProfile({ min_messages_before_pitch: 5, cooldown_minutes: 30 });

      expect(redirector.shouldPitch(history, profile)).toBe(true);
    });

    it('should return false when conversation has zero messages', () => {
      const history = makeHistory(0);
      const profile = makeProfile({ min_messages_before_pitch: 0 });

      expect(redirector.shouldPitch(history, profile)).toBe(false);
    });
  });

  describe('generatePitchHint', () => {
    it('should return a string hint for playful style', () => {
      const profile = makeProfile({ pitch_style: 'playful' });
      const hint = redirector.generatePitchHint(profile);

      expect(typeof hint).toBe('string');
      expect(hint.length).toBeGreaterThan(0);
    });

    it('should return a string hint for subtle style', () => {
      const profile = makeProfile({ pitch_style: 'subtle' });
      const hint = redirector.generatePitchHint(profile);

      expect(typeof hint).toBe('string');
      expect(hint.length).toBeGreaterThan(0);
    });

    it('should return a string hint for direct style', () => {
      const profile = makeProfile({ pitch_style: 'direct' });
      const hint = redirector.generatePitchHint(profile);

      expect(typeof hint).toBe('string');
      expect(hint.length).toBeGreaterThan(0);
    });
  });

  describe('recordPitch', () => {
    it('should mark the last assistant message with starsPitchMade', () => {
      const history = makeHistory(6); // 3 user + 3 assistant
      const updated = redirector.recordPitch(history);

      // Find last assistant message
      const lastAssistant = [...updated.messages].reverse().find((m) => m.role === 'assistant');
      expect(lastAssistant?.metadata?.starsPitchMade).toBe(true);
    });

    it('should not mutate the original history', () => {
      const history = makeHistory(6);
      const originalMessages = history.messages.map((m) => ({ ...m }));
      redirector.recordPitch(history);

      // Original should be unchanged
      for (let i = 0; i < history.messages.length; i++) {
        expect(history.messages[i]!.metadata?.starsPitchMade).toBeUndefined();
      }
    });

    it('should preserve existing metadata on the message', () => {
      const history = makeHistory(4);
      // Manually set some metadata on last assistant
      const lastAssistantIdx = history.messages.length - 1;
      if (history.messages[lastAssistantIdx]!.role === 'assistant') {
        history.messages[lastAssistantIdx] = {
          ...history.messages[lastAssistantIdx]!,
          metadata: { starsAccepted: true },
        };
      }

      const updated = redirector.recordPitch(history);
      const lastAssistant = [...updated.messages].reverse().find((m) => m.role === 'assistant');
      expect(lastAssistant?.metadata?.starsPitchMade).toBe(true);
      expect(lastAssistant?.metadata?.starsAccepted).toBe(true);
    });
  });

  describe('recordAcceptance', () => {
    it('should mark the last pitched message as accepted', () => {
      let history = makeHistory(6);
      history = redirector.recordPitch(history);
      const updated = redirector.recordAcceptance(history);

      const pitchedMsg = updated.messages.find((m) => m.metadata?.starsPitchMade);
      expect(pitchedMsg?.metadata?.starsAccepted).toBe(true);
    });

    it('should not mutate the original history', () => {
      let history = makeHistory(6);
      history = redirector.recordPitch(history);
      const pitchedHistory = { ...history, messages: [...history.messages] };
      redirector.recordAcceptance(pitchedHistory);

      // The pitched message in pitchedHistory should still not have starsAccepted
      const pitched = pitchedHistory.messages.find((m) => m.metadata?.starsPitchMade);
      expect(pitched?.metadata?.starsAccepted).toBeUndefined();
    });
  });

  describe('getConversionStats', () => {
    it('should return zero stats for empty history', () => {
      const history = makeHistory(0);
      const stats = redirector.getConversionStats(history);

      expect(stats).toEqual({ pitches: 0, acceptances: 0, rate: 0 });
    });

    it('should count pitches and acceptances correctly', () => {
      const history = makeHistory(10);
      // Mark two messages as pitched, one as accepted
      history.messages[1] = { ...history.messages[1]!, metadata: { starsPitchMade: true } };
      history.messages[5] = { ...history.messages[5]!, metadata: { starsPitchMade: true, starsAccepted: true } };

      const stats = redirector.getConversionStats(history);

      expect(stats.pitches).toBe(2);
      expect(stats.acceptances).toBe(1);
      expect(stats.rate).toBe(0.5);
    });

    it('should return rate 0 when there are pitches but no acceptances', () => {
      const history = makeHistory(6);
      history.messages[1] = { ...history.messages[1]!, metadata: { starsPitchMade: true } };

      const stats = redirector.getConversionStats(history);

      expect(stats.pitches).toBe(1);
      expect(stats.acceptances).toBe(0);
      expect(stats.rate).toBe(0);
    });
  });

  describe('singleton', () => {
    it('should export a singleton instance', () => {
      expect(salesRedirector).toBeInstanceOf(SalesRedirector);
    });
  });
});
