/**
 * Test Helpers for Billing Tests
 * Reusable utilities for creating test subscriptions, users, and Stripe events
 */

import { prisma } from "@/lib/db";
import type { User, Subscription } from "@prisma/client";
import type Stripe from "stripe";

/**
 * Create a test user with optional Stripe customer ID
 */
export async function createTestUser(overrides?: Partial<User>): Promise<User> {
  const defaultData = {
    email: `test-${Date.now()}@example.com`,
    name: "Test User",
    password: "hashedPassword123",
    role: "CUSTOMER" as const,
    stripeCustomerId: `cus_test_${Date.now()}`,
  };

  return await prisma.users.create({
    data: {
      ...defaultData,
      ...overrides,
    },
  });
}

/**
 * Create a test subscription for a user
 */
export async function createTestSubscription(
  userId: string,
  options?: {
    tierId?: string;
    status?: string;
    daysRemaining?: number;
    setupFeePaid?: boolean;
    billingPeriod?: "monthly" | "yearly";
  },
): Promise<Subscription> {
  const {
    tierId = "tier1",
    status = "active",
    daysRemaining = 14,
    setupFeePaid = true,
    billingPeriod = "monthly",
  } = options || {};

  const user = await prisma.users.findUnique({
    where: { id: userId },
  });

  if (!user) {
    throw new Error(`User ${userId} not found`);
  }

  const currentPeriodEnd = new Date(
    Date.now() + daysRemaining * 24 * 60 * 60 * 1000,
  );

  return await prisma.subscriptions.create({
    data: {
      userId,
      stripeCustomerId: user.stripeCustomerId || `cus_${userId}`,
      stripeSubscriptionId: `sub_test_${Date.now()}`,
      stripePriceId: `price_${tierId}_${billingPeriod}`,
      tierId,
      status,
      billingPeriod,
      currentPeriodEnd,
      setupFeePaid,
      setupFeeAmount: setupFeePaid ? 199 : null,
    },
  });
}

/**
 * Cleanup test user and all related data
 */
export async function cleanupTestUser(userId: string): Promise<void> {
  await prisma.subscriptions.deleteMany({ where: { userId } });
  await prisma.audit_logs.deleteMany({ where: { userId } });
  await prisma.users.delete({ where: { id: userId } });
}

/**
 * Create a mock Stripe subscription.updated event
 */
export function createMockSubscriptionUpdatedEvent(
  subscriptionId: string,
  customerId: string,
  options?: {
    newPriceId?: string;
    newTierId?: string;
    status?: string;
  },
): Stripe.Event {
  const {
    newPriceId = "price_professional_monthly",
    newTierId = "tier2",
    status = "active",
  } = options || {};

  return {
    id: `evt_test_${Date.now()}`,
    object: "event",
    api_version: "2023-10-16",
    created: Math.floor(Date.now() / 1000),
    type: "customer.subscription.updated",
    data: {
      object: {
        id: subscriptionId,
        customer: customerId,
        status,
        current_period_end: Math.floor(Date.now() / 1000) + 14 * 24 * 60 * 60,
        current_period_start: Math.floor(Date.now() / 1000) - 16 * 24 * 60 * 60,
        cancel_at_period_end: false,
        billing_cycle_anchor: Math.floor(Date.now() / 1000),
        currency: "gbp",
        items: {
          data: [
            {
              id: "si_test_123",
              price: {
                id: newPriceId,
                unit_amount: 20900,
              },
            },
          ],
        },
        metadata: {
          tier_id: newTierId,
        },
      } as Stripe.Subscription,
    },
    livemode: false,
    pending_webhooks: 0,
    request: {
      id: `req_test_${Date.now()}`,
      idempotency_key: `key_test_${Date.now()}`,
    },
  };
}

/**
 * Create a mock Stripe invoice.payment_succeeded event
 */
export function createMockInvoicePaymentEvent(
  customerId: string,
  subscriptionId: string,
  options?: {
    amount?: number;
    hasProration?: boolean;
    prorationAmount?: number;
  },
): Stripe.Event {
  const {
    amount = 20900,
    hasProration = false,
    prorationAmount = -10000,
  } = options || {};

  const lineItems: any[] = [
    {
      id: "il_regular_123",
      description: "Subscription",
      amount,
      proration: false,
      period: {
        start: Math.floor(Date.now() / 1000),
        end: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
      },
    },
  ];

  if (hasProration) {
    lineItems.push({
      id: "il_proration_123",
      description: "Unused time",
      amount: prorationAmount,
      proration: true,
      period: {
        start: Math.floor(Date.now() / 1000),
        end: Math.floor(Date.now() / 1000) + 14 * 24 * 60 * 60,
      },
    });
  }

  return {
    id: `evt_invoice_${Date.now()}`,
    object: "event",
    api_version: "2023-10-16",
    created: Math.floor(Date.now() / 1000),
    type: "invoice.payment_succeeded",
    data: {
      object: {
        id: `in_${Date.now()}`,
        customer: customerId,
        subscription: subscriptionId,
        amount_paid: amount + (hasProration ? prorationAmount : 0),
        amount_due: amount + (hasProration ? prorationAmount : 0),
        currency: "gbp",
        billing_reason: hasProration
          ? "subscription_update"
          : "subscription_cycle",
        lines: {
          data: lineItems,
        },
        next_payment_attempt: null,
      } as any,
    },
    livemode: false,
    pending_webhooks: 0,
    request: {
      id: `req_invoice_${Date.now()}`,
      idempotency_key: `key_invoice_${Date.now()}`,
    },
  };
}

/**
 * Wait for subscription to be updated in database
 */
export async function waitForSubscriptionUpdate(
  subscriptionId: string,
  expectedTierId: string,
  maxWaitMs: number = 5000,
): Promise<Subscription | null> {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWaitMs) {
    const subscription = await prisma.subscriptions.findUnique({
      where: { id: subscriptionId },
    });

    if (subscription?.tierId === expectedTierId) {
      return subscription;
    }

    // Wait 100ms before checking again
    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  return null;
}

/**
 * Verify audit log entry exists for subscription change
 */
export async function verifyAuditLogEntry(
  userId: string,
  action: "upgrade" | "downgrade",
  fromPlan: string,
  toPlan: string,
): Promise<boolean> {
  const auditLog = await prisma.audit_logs.findFirst({
    where: {
      userId,
      type: "subscription_change",
      action,
    },
    orderBy: { createdAt: "desc" },
  });

  if (!auditLog) {
    return false;
  }

  const metadata = auditLog.metadata as any;
  return metadata?.fromPlan === fromPlan && metadata?.toPlan === toPlan;
}

/**
 * Calculate expected proration for test validation
 */
export function calculateExpectedProration(
  currentPriceGBP: number,
  newPriceGBP: number,
  daysRemaining: number,
  currency: "GBP" | "SAR" | "AED" | "QAR" = "GBP",
): number {
  const rates = {
    GBP: 1,
    SAR: 4.76,
    AED: 4.66,
    QAR: 4.62,
  };

  const currentPrice = currentPriceGBP * rates[currency];
  const newPrice = newPriceGBP * rates[currency];

  const currentDaily = currentPrice / 30;
  const newDaily = newPrice / 30;

  const unusedCredit = currentDaily * daysRemaining;
  const newCost = newDaily * daysRemaining;

  return Math.max(0, Math.round((newCost - unusedCredit) * 100) / 100);
}

/**
 * Create mock request with auth headers
 */
export function createMockAuthRequest(
  url: string,
  method: string,
  body?: any,
  userId?: string,
): Request {
  return new Request(url, {
    method,
    headers: {
      "Content-Type": "application/json",
      "x-user-id": userId || "test-user-id",
      "x-forwarded-for": "127.0.0.1",
    },
    body: body ? JSON.stringify(body) : undefined,
  });
}

/**
 * Validate subscription state
 */
export async function validateSubscriptionState(
  subscriptionId: string,
  expectedState: {
    tierId?: string;
    status?: string;
    setupFeePaid?: boolean;
  },
): Promise<{ valid: boolean; errors: string[] }> {
  const subscription = await prisma.subscriptions.findUnique({
    where: { id: subscriptionId },
  });

  const errors: string[] = [];

  if (!subscription) {
    errors.push("Subscription not found");
    return { valid: false, errors };
  }

  if (expectedState.tierId && subscription.tierId !== expectedState.tierId) {
    errors.push(
      `Expected tierId ${expectedState.tierId}, got ${subscription.tierId}`,
    );
  }

  if (expectedState.status && subscription.status !== expectedState.status) {
    errors.push(
      `Expected status ${expectedState.status}, got ${subscription.status}`,
    );
  }

  if (
    expectedState.setupFeePaid !== undefined &&
    subscription.setupFeePaid !== expectedState.setupFeePaid
  ) {
    errors.push(
      `Expected setupFeePaid ${expectedState.setupFeePaid}, got ${subscription.setupFeePaid}`,
    );
  }

  return {
    valid: errors.length === 0,
    errors,
  };
}

/**
 * Simulate time passage (for testing day calculations)
 */
export function addDaysToDate(date: Date, days: number): Date {
  const result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}

/**
 * Get all billing-related data for a user (for debugging)
 */
export async function getBillingSnapshot(userId: string) {
  const user = await prisma.users.findUnique({
    where: { id: userId },
    include: {
      subscriptions: true,
      auditLogs: {
        where: { type: "subscription_change" },
        orderBy: { createdAt: "desc" },
        take: 10,
      },
    },
  });

  return {
    user: user
      ? {
          id: user.id,
          email: user.email,
          stripeCustomerId: user.stripeCustomerId,
        }
      : null,
    subscriptions: user?.subscriptions || [],
    recentAuditLogs: user?.auditLogs || [],
  };
}

/**
 * Assert proration calculation matches expected value
 */
export function assertProrationCorrect(
  actual: number,
  expected: number,
  tolerance: number = 0.5, // Allow 0.50 difference for rounding
): void {
  const difference = Math.abs(actual - expected);

  if (difference > tolerance) {
    throw new Error(
      `Proration mismatch: expected ${expected}, got ${actual} (difference: ${difference})`,
    );
  }
}

/**
 * Mock Stripe subscription object
 */
export function createMockStripeSubscription(
  options?: Partial<Stripe.Subscription>,
): Stripe.Subscription {
  return {
    id: options?.id || "sub_mock_123",
    object: "subscription",
    customer: options?.customer || "cus_mock_123",
    status: (options?.status as any) || "active",
    current_period_end:
      options?.current_period_end ||
      Math.floor(Date.now() / 1000) + 14 * 24 * 60 * 60,
    current_period_start:
      options?.current_period_start ||
      Math.floor(Date.now() / 1000) - 16 * 24 * 60 * 60,
    cancel_at_period_end: options?.cancel_at_period_end || false,
    billing_cycle_anchor:
      options?.billing_cycle_anchor || Math.floor(Date.now() / 1000),
    currency: options?.currency || "gbp",
    items: options?.items || {
      object: "list",
      data: [
        {
          id: "si_mock_123",
          object: "subscription_item",
          price: {
            id: "price_mock_123",
            object: "price",
            unit_amount: 20900,
          } as any,
        } as any,
      ],
      has_more: false,
      url: "/v1/subscription_items",
    },
    metadata: options?.metadata || {},
    // Add other required Stripe.Subscription fields with defaults
  } as Stripe.Subscription;
}

/**
 * Test data constants
 */
export const TEST_TIERS = {
  tier1: {
    id: "tier1",
    priceGBP: 179,
    setupGBP: 199,
    name: "Starter",
  },
  tier2: {
    id: "tier2",
    priceGBP: 209,
    setupGBP: 199,
    name: "Professional",
  },
  tier3: {
    id: "tier3",
    priceGBP: 249,
    setupGBP: 199,
    name: "Business",
  },
  tier4: {
    id: "tier4",
    priceGBP: 349,
    setupGBP: 199,
    name: "Enterprise",
  },
  tier5: {
    id: "tier5",
    priceGBP: 449,
    setupGBP: 299,
    name: "Ultimate",
  },
};

/**
 * Test scenarios for different billing situations
 */
export const TEST_SCENARIOS = {
  earlyUpgrade: {
    currentTier: "tier1",
    newTier: "tier2",
    daysRemaining: 28,
    description: "Upgrade early in billing cycle (28 days left)",
  },
  midCycleUpgrade: {
    currentTier: "tier1",
    newTier: "tier3",
    daysRemaining: 15,
    description: "Upgrade mid-cycle (15 days left)",
  },
  lateUpgrade: {
    currentTier: "tier2",
    newTier: "tier4",
    daysRemaining: 2,
    description: "Upgrade late in cycle (2 days left)",
  },
  earlyDowngrade: {
    currentTier: "tier4",
    newTier: "tier2",
    daysRemaining: 27,
    description: "Downgrade early in cycle (27 days left)",
  },
  midCycleDowngrade: {
    currentTier: "tier3",
    newTier: "tier1",
    daysRemaining: 14,
    description: "Downgrade mid-cycle (14 days left)",
  },
  lateDowngrade: {
    currentTier: "tier5",
    newTier: "tier3",
    daysRemaining: 3,
    description: "Downgrade late in cycle (3 days left)",
  },
};

/**
 * Verify billing history entry exists
 */
export async function verifyBillingHistoryEntry(
  customerId: string,
  event: string,
  maxAgeMs: number = 60000,
): Promise<boolean> {
  // Note: This queries Supabase billing_history
  // In unit tests, you might need to mock this or use Prisma
  return true; // Placeholder - implement based on your billing_history setup
}

/**
 * Calculate total cost for user journey
 * Useful for validating no over-charging
 */
export function calculateTotalCostJourney(
  journey: Array<{
    tier: string;
    daysOnTier: number;
    priceGBP: number;
  }>,
  setupFeeGBP: number = 0,
): number {
  let total = setupFeeGBP;

  journey.forEach(({ daysOnTier, priceGBP }) => {
    const dailyRate = priceGBP / 30;
    total += dailyRate * daysOnTier;
  });

  return Math.round(total * 100) / 100;
}

/**
 * Example usage:
 *
 * // Create test user
 * const user = await createTestUser({ email: 'test@example.com' });
 *
 * // Create subscription
 * const sub = await createTestSubscription(user.id, {
 *   tierId: 'tier2',
 *   daysRemaining: 14
 * });
 *
 * // Run your tests...
 *
 * // Cleanup
 * await cleanupTestUser(user.id);
 */

// Dummy test to satisfy Jest requirement that test files have at least one test
describe("Helper Module", () => {
  it("should export utilities", () => {
    expect(true).toBe(true);
  });
});
