/**
 * Billing Dunning module — unit tests.
 */

import { describe, it, expect } from 'vitest';
import {
  CreateDunningCampaignSchema,
  ResolveCampaignSchema,
  UpdateDunningPolicySchema,
  getNextRetryTime,
  isGracePeriodExpired,
  DEFAULT_RETRY_SCHEDULE_HOURS,
} from './types.js';

// ── Schema validation ──

describe('CreateDunningCampaignSchema', () => {
  it('validates minimal campaign', () => {
    const result = CreateDunningCampaignSchema.safeParse({
      userId: 'user-1',
      subscriptionId: 'sub_abc',
      failedPaymentId: 'pi_xyz',
      amountCents: 1999,
      failureReason: 'card_declined',
    });
    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.currency).toBe('usd');
      expect(result.data.declineCode).toBeNull();
    }
  });

  it('validates with all fields', () => {
    const result = CreateDunningCampaignSchema.safeParse({
      userId: 'user-2',
      subscriptionId: 'sub_def',
      failedPaymentId: 'pi_123',
      amountCents: 4999,
      currency: 'eur',
      failureReason: 'insufficient_funds',
      declineCode: 'insufficient_funds',
    });
    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.currency).toBe('eur');
      expect(result.data.declineCode).toBe('insufficient_funds');
    }
  });

  it('rejects zero amount', () => {
    expect(
      CreateDunningCampaignSchema.safeParse({
        userId: 'u1',
        subscriptionId: 's1',
        failedPaymentId: 'p1',
        amountCents: 0,
        failureReason: 'test',
      }).success
    ).toBe(false);
  });

  it('rejects empty userId', () => {
    expect(
      CreateDunningCampaignSchema.safeParse({
        userId: '',
        subscriptionId: 's1',
        failedPaymentId: 'p1',
        amountCents: 100,
        failureReason: 'test',
      }).success
    ).toBe(false);
  });

  it('rejects invalid currency length', () => {
    expect(
      CreateDunningCampaignSchema.safeParse({
        userId: 'u1',
        subscriptionId: 's1',
        failedPaymentId: 'p1',
        amountCents: 100,
        currency: 'dollars',
        failureReason: 'test',
      }).success
    ).toBe(false);
  });
});

describe('ResolveCampaignSchema', () => {
  it('validates payment_succeeded', () => {
    const result = ResolveCampaignSchema.safeParse({
      resolution: 'payment_succeeded',
    });
    expect(result.success).toBe(true);
  });

  it('validates manual_resolution with notes', () => {
    const result = ResolveCampaignSchema.safeParse({
      resolution: 'manual_resolution',
      notes: 'Customer contacted support, card updated',
    });
    expect(result.success).toBe(true);
  });

  it('validates waived', () => {
    expect(ResolveCampaignSchema.safeParse({ resolution: 'waived' }).success).toBe(true);
  });

  it('rejects invalid resolution', () => {
    expect(ResolveCampaignSchema.safeParse({ resolution: 'deleted' }).success).toBe(false);
  });
});

describe('UpdateDunningPolicySchema', () => {
  it('validates partial update', () => {
    const result = UpdateDunningPolicySchema.safeParse({
      gracePeriodDays: 14,
      autoDowngrade: false,
    });
    expect(result.success).toBe(true);
  });

  it('validates retry schedule', () => {
    const result = UpdateDunningPolicySchema.safeParse({
      retryScheduleHours: [12, 48, 120, 240],
    });
    expect(result.success).toBe(true);
  });

  it('rejects empty retry schedule', () => {
    expect(UpdateDunningPolicySchema.safeParse({ retryScheduleHours: [] }).success).toBe(false);
  });

  it('rejects grace period over 90 days', () => {
    expect(UpdateDunningPolicySchema.safeParse({ gracePeriodDays: 91 }).success).toBe(false);
  });

  it('rejects retry hours over 720 (30 days)', () => {
    expect(UpdateDunningPolicySchema.safeParse({ retryScheduleHours: [721] }).success).toBe(false);
  });
});

// ── Helper functions ──

describe('getNextRetryTime', () => {
  const baseTime = '2026-01-15T10:00:00.000Z';

  it('returns first retry time', () => {
    const result = getNextRetryTime(0, DEFAULT_RETRY_SCHEDULE_HOURS, baseTime);
    expect(result).not.toBeNull();
    const expected = new Date(baseTime);
    expected.setHours(expected.getHours() + 24);
    expect(result).toBe(expected.toISOString());
  });

  it('returns second retry time', () => {
    const result = getNextRetryTime(1, DEFAULT_RETRY_SCHEDULE_HOURS, baseTime);
    expect(result).not.toBeNull();
    const expected = new Date(baseTime);
    expected.setHours(expected.getHours() + 72);
    expect(result).toBe(expected.toISOString());
  });

  it('returns third retry time', () => {
    const result = getNextRetryTime(2, DEFAULT_RETRY_SCHEDULE_HOURS, baseTime);
    expect(result).not.toBeNull();
    const expected = new Date(baseTime);
    expected.setHours(expected.getHours() + 168);
    expect(result).toBe(expected.toISOString());
  });

  it('returns null when retries exhausted', () => {
    const result = getNextRetryTime(3, DEFAULT_RETRY_SCHEDULE_HOURS, baseTime);
    expect(result).toBeNull();
  });

  it('returns null for custom short schedule', () => {
    expect(getNextRetryTime(1, [24], baseTime)).toBeNull();
  });
});

describe('isGracePeriodExpired', () => {
  it('returns false for null grace period', () => {
    expect(isGracePeriodExpired(null)).toBe(false);
  });

  it('returns true for past date', () => {
    expect(isGracePeriodExpired('2020-01-01T00:00:00.000Z')).toBe(true);
  });

  it('returns false for future date', () => {
    const future = new Date();
    future.setFullYear(future.getFullYear() + 1);
    expect(isGracePeriodExpired(future.toISOString())).toBe(false);
  });
});
