/**
 * Billing Dunning module — types and schemas.
 * Handles failed payment recovery: retry schedules, grace periods,
 * customer notifications, and eventual subscription downgrade/cancellation.
 */

import { z } from 'zod';

// ── Dunning Campaign Types ───────────────────────────────────────

export type DunningStatus =
  | 'active' // payment failed, retries in progress
  | 'grace_period' // retries exhausted, user has grace period
  | 'resolved' // payment succeeded or manually resolved
  | 'downgraded' // subscription downgraded after grace period
  | 'cancelled'; // subscription cancelled after all recovery failed

export type DunningAction =
  | 'retry_payment'
  | 'send_reminder'
  | 'send_warning'
  | 'send_final_notice'
  | 'downgrade'
  | 'cancel';

export interface DunningCampaignDoc {
  id: string;
  productId: string;
  userId: string;
  subscriptionId: string;
  /** Stripe payment intent / invoice that failed */
  failedPaymentId: string;
  /** Amount in cents */
  amountCents: number;
  currency: string;
  status: DunningStatus;
  /** Number of retry attempts made */
  retryCount: number;
  /** Max retries before entering grace period */
  maxRetries: number;
  /** Next scheduled retry timestamp */
  nextRetryAt: string | null;
  /** Grace period end timestamp */
  gracePeriodEndsAt: string | null;
  /** Timeline of all actions taken */
  actions: DunningActionEntry[];
  /** Why the payment failed (from Stripe) */
  failureReason: string;
  /** Stripe decline code if available */
  declineCode: string | null;
  /** Whether the user has been notified */
  notificationsSent: number;
  resolvedAt: string | null;
  createdAt: string;
  updatedAt: string;
}

export interface DunningActionEntry {
  action: DunningAction;
  timestamp: string;
  success: boolean;
  details: string;
}

export interface DunningPolicyDoc {
  id: string;
  productId: string;
  /** Retry schedule in hours from initial failure (e.g. [24, 72, 168]) */
  retryScheduleHours: number[];
  /** Grace period in days after retries exhausted */
  gracePeriodDays: number;
  /** Whether to auto-downgrade after grace period (vs cancel) */
  autoDowngrade: boolean;
  /** Plan to downgrade to (if autoDowngrade is true) */
  downgradeToPlan: string;
  /** Email templates to send at each retry */
  reminderTemplates: string[];
  /** Whether to send a final warning before downgrade/cancel */
  sendFinalWarning: boolean;
  /** Days before grace period end to send final warning */
  finalWarningDaysBeforeEnd: number;
  updatedAt: string;
  updatedBy: string;
}

// ── Schemas ──────────────────────────────────────────────────────

export const CreateDunningCampaignSchema = z.object({
  userId: z.string().min(1),
  subscriptionId: z.string().min(1),
  failedPaymentId: z.string().min(1),
  amountCents: z.number().int().min(1),
  currency: z.string().length(3).default('usd'),
  failureReason: z.string().min(1).max(500),
  declineCode: z.string().max(64).nullable().default(null),
});

export const ResolveCampaignSchema = z.object({
  resolution: z.enum(['payment_succeeded', 'manual_resolution', 'waived']),
  notes: z.string().max(500).optional(),
});

export const UpdateDunningPolicySchema = z.object({
  retryScheduleHours: z.array(z.number().int().min(1).max(720)).min(1).max(10).optional(),
  gracePeriodDays: z.number().int().min(1).max(90).optional(),
  autoDowngrade: z.boolean().optional(),
  downgradeToPlan: z.string().min(1).max(64).optional(),
  reminderTemplates: z.array(z.string().max(128)).max(10).optional(),
  sendFinalWarning: z.boolean().optional(),
  finalWarningDaysBeforeEnd: z.number().int().min(1).max(30).optional(),
});

export type CreateDunningCampaignInput = z.infer<typeof CreateDunningCampaignSchema>;
export type ResolveCampaignInput = z.infer<typeof ResolveCampaignSchema>;
export type UpdateDunningPolicyInput = z.infer<typeof UpdateDunningPolicySchema>;

// ── Helpers ──────────────────────────────────────────────────────

/** Default retry schedule: 1 day, 3 days, 7 days after failure */
export const DEFAULT_RETRY_SCHEDULE_HOURS = [24, 72, 168];
export const DEFAULT_GRACE_PERIOD_DAYS = 7;

export function getNextRetryTime(
  retryCount: number,
  scheduleHours: number[],
  failedAt: string
): string | null {
  if (retryCount >= scheduleHours.length) return null;
  const hoursFromFailure = scheduleHours[retryCount]!;
  const retryDate = new Date(failedAt);
  retryDate.setHours(retryDate.getHours() + hoursFromFailure);
  return retryDate.toISOString();
}

export function isGracePeriodExpired(gracePeriodEndsAt: string | null): boolean {
  if (!gracePeriodEndsAt) return false;
  return new Date() > new Date(gracePeriodEndsAt);
}
