/**
 * Referral Invite System Tests
 */

import { describe, it, expect, beforeEach } from 'vitest';
import {
  generateInviteCode,
  createReferralInvite,
  isInviteExpired,
  getInviteTemplates,
  applyTemplate,
  calculateInviteStats,
  trackInviteAcceptance,
  trackInviteOpen,
  trackInviteClick,
} from './referralInviteSystem';

describe('Referral Invite System', () => {
  describe('generateInviteCode', () => {
    it('should generate unique invite codes', () => {
      const code1 = generateInviteCode();
      const code2 = generateInviteCode();

      expect(code1).not.toBe(code2);
    });

    it('should generate codes with correct format', () => {
      const code = generateInviteCode();

      expect(code).toMatch(/^INVITE_[A-Z0-9]{8}$/);
    });

    it('should generate codes with correct length', () => {
      const code = generateInviteCode();

      expect(code.length).toBe(15); // "INVITE_" (7) + 8 chars
    });
  });

  describe('createReferralInvite', () => {
    it('should create invite with email channel', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      expect(invite.referrerId).toBe(1);
      expect(invite.channel).toBe('email');
      expect(invite.inviteeEmail).toBe('friend@example.com');
      expect(invite.status).toBe('pending');
    });

    it('should create invite with SMS channel', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', undefined, '+1234567890', 'sms');

      expect(invite.channel).toBe('sms');
      expect(invite.inviteePhone).toBe('+1234567890');
    });

    it('should create invite with WhatsApp channel', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', undefined, '+1234567890', 'whatsapp');

      expect(invite.channel).toBe('whatsapp');
      expect(invite.inviteePhone).toBe('+1234567890');
    });

    it('should generate unique invite code', async () => {
      const invite1 = await createReferralInvite(1, 'John', 'john@example.com', 'friend1@example.com', undefined, 'email');
      const invite2 = await createReferralInvite(1, 'John', 'john@example.com', 'friend2@example.com', undefined, 'email');

      expect(invite1.inviteCode).not.toBe(invite2.inviteCode);
    });

    it('should set expiration to 30 days', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');
      const expirationMs = invite.expiresAt.getTime() - invite.createdAt.getTime();
      const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;

      // Allow 1 second tolerance
      expect(Math.abs(expirationMs - thirtyDaysMs)).toBeLessThan(1000);
    });

    it('should include invite link', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      expect(invite.inviteLink).toContain('https://coinkrazy.com/join?ref=');
      expect(invite.inviteLink).toContain(invite.inviteCode);
    });

    it('should include custom message', async () => {
      const customMsg = 'Join me on CoinKrazy!';
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email', customMsg);

      expect(invite.customMessage).toBe(customMsg);
    });

    it('should initialize tracking data', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      expect(invite.trackingData.sent).toBe(false);
      expect(invite.trackingData.opened).toBe(false);
      expect(invite.trackingData.clicked).toBe(false);
      expect(invite.trackingData.converted).toBe(false);
    });
  });

  describe('isInviteExpired', () => {
    it('should return false for new invite', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      expect(isInviteExpired(invite)).toBe(false);
    });

    it('should return true for expired invite', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      // Set expiration to past
      invite.expiresAt = new Date(Date.now() - 1000);

      expect(isInviteExpired(invite)).toBe(true);
    });
  });

  describe('getInviteTemplates', () => {
    it('should return templates', () => {
      const templates = getInviteTemplates();

      expect(templates.length).toBeGreaterThan(0);
    });

    it('should have email templates', () => {
      const templates = getInviteTemplates();
      const emailTemplates = templates.filter((t) => t.channel === 'email');

      expect(emailTemplates.length).toBeGreaterThan(0);
    });

    it('should have SMS templates', () => {
      const templates = getInviteTemplates();
      const smsTemplates = templates.filter((t) => t.channel === 'sms');

      expect(smsTemplates.length).toBeGreaterThan(0);
    });

    it('should have WhatsApp templates', () => {
      const templates = getInviteTemplates();
      const whatsappTemplates = templates.filter((t) => t.channel === 'whatsapp');

      expect(whatsappTemplates.length).toBeGreaterThan(0);
    });

    it('should include required template fields', () => {
      const templates = getInviteTemplates();

      templates.forEach((template) => {
        expect(template.id).toBeDefined();
        expect(template.name).toBeDefined();
        expect(template.body).toBeDefined();
        expect(template.channel).toBeDefined();
        expect(template.variables).toBeDefined();
      });
    });
  });

  describe('applyTemplate', () => {
    it('should replace referrer name', async () => {
      const templates = getInviteTemplates();
      const template = templates[0];
      const invite = await createReferralInvite(1, 'John Doe', 'john@example.com', 'friend@example.com', undefined, 'email');

      const message = applyTemplate(template, invite);

      expect(message).toContain('John Doe');
    });

    it('should replace invite link', async () => {
      const templates = getInviteTemplates();
      const template = templates[0];
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      const message = applyTemplate(template, invite);

      expect(message).toContain(invite.inviteLink);
    });

    it('should handle multiple variable replacements', async () => {
      const templates = getInviteTemplates();
      const template = templates[0];
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      const message = applyTemplate(template, invite);

      // Should not contain unreplaced variables
      expect(message).not.toContain('{{');
      expect(message).not.toContain('}}');
    });
  });

  describe('trackInviteOpen', () => {
    it('should mark invite as opened', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      trackInviteOpen(invite);

      expect(invite.trackingData.opened).toBe(true);
    });
  });

  describe('trackInviteClick', () => {
    it('should mark invite as clicked', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      trackInviteClick(invite);

      expect(invite.trackingData.clicked).toBe(true);
    });
  });

  describe('trackInviteAcceptance', () => {
    it('should mark invite as accepted', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');

      trackInviteAcceptance(invite, 2);

      expect(invite.status).toBe('accepted');
      expect(invite.acceptedByUserId).toBe(2);
      expect(invite.trackingData.converted).toBe(true);
    });

    it('should set acceptance timestamp', async () => {
      const invite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');
      const beforeTime = new Date();

      trackInviteAcceptance(invite, 2);

      const afterTime = new Date();

      expect(invite.acceptedAt).toBeDefined();
      expect(invite.acceptedAt!.getTime()).toBeGreaterThanOrEqual(beforeTime.getTime());
      expect(invite.acceptedAt!.getTime()).toBeLessThanOrEqual(afterTime.getTime());
    });
  });

  describe('calculateInviteStats', () => {
    it('should calculate stats for empty invite list', () => {
      const stats = calculateInviteStats([]);

      expect(stats.totalInvites).toBe(0);
      expect(stats.sentInvites).toBe(0);
      expect(stats.acceptedInvites).toBe(0);
      expect(stats.conversionRate).toBe(0);
    });

    it('should count total invites', async () => {
      const invites = [];
      for (let i = 0; i < 5; i++) {
        invites.push(await createReferralInvite(1, 'John', 'john@example.com', `friend${i}@example.com`, undefined, 'email'));
      }

      const stats = calculateInviteStats(invites);

      expect(stats.totalInvites).toBe(5);
    });

    it('should count sent invites', async () => {
      const invite1 = await createReferralInvite(1, 'John', 'john@example.com', 'friend1@example.com', undefined, 'email');
      const invite2 = await createReferralInvite(1, 'John', 'john@example.com', 'friend2@example.com', undefined, 'email');

      invite1.trackingData.sent = true;
      invite2.trackingData.sent = true;

      const stats = calculateInviteStats([invite1, invite2]);

      expect(stats.sentInvites).toBe(2);
    });

    it('should count accepted invites', async () => {
      const invite1 = await createReferralInvite(1, 'John', 'john@example.com', 'friend1@example.com', undefined, 'email');
      const invite2 = await createReferralInvite(1, 'John', 'john@example.com', 'friend2@example.com', undefined, 'email');

      trackInviteAcceptance(invite1, 2);
      trackInviteAcceptance(invite2, 3);

      const stats = calculateInviteStats([invite1, invite2]);

      expect(stats.acceptedInvites).toBe(2);
    });

    it('should calculate conversion rate', async () => {
      const invite1 = await createReferralInvite(1, 'John', 'john@example.com', 'friend1@example.com', undefined, 'email');
      const invite2 = await createReferralInvite(1, 'John', 'john@example.com', 'friend2@example.com', undefined, 'email');

      invite1.trackingData.sent = true;
      invite2.trackingData.sent = true;

      trackInviteAcceptance(invite1, 2);

      const stats = calculateInviteStats([invite1, invite2]);

      expect(stats.conversionRate).toBe(50); // 1 out of 2 sent
    });

    it('should count by channel', async () => {
      const emailInvite = await createReferralInvite(1, 'John', 'john@example.com', 'friend@example.com', undefined, 'email');
      const smsInvite = await createReferralInvite(1, 'John', 'john@example.com', undefined, '+1234567890', 'sms');
      const whatsappInvite = await createReferralInvite(1, 'John', 'john@example.com', undefined, '+1234567890', 'whatsapp');

      const stats = calculateInviteStats([emailInvite, smsInvite, whatsappInvite]);

      expect(stats.byChannel.email).toBe(1);
      expect(stats.byChannel.sms).toBe(1);
      expect(stats.byChannel.whatsapp).toBe(1);
    });
  });
});
