/**
 * Billing Dunning repository — Cosmos DB CRUD for campaigns and policies.
 * @module dunning/repository
 */

import { getContainer } from '../../lib/cosmos.js';
import type {
  DunningCampaignDoc,
  DunningPolicyDoc,
  DunningStatus,
  DunningActionEntry,
} from './types.js';

// =============================================================================
// Dunning Campaigns
// =============================================================================

export async function createCampaign(doc: DunningCampaignDoc): Promise<DunningCampaignDoc> {
  const container = getContainer('dunning_campaigns');
  const { resource } = await container.items.create(doc);
  if (!resource) throw new Error('Failed to create dunning campaign');
  return resource as unknown as DunningCampaignDoc;
}

export async function getCampaign(
  id: string,
  productId: string
): Promise<DunningCampaignDoc | null> {
  const container = getContainer('dunning_campaigns');
  try {
    const { resource } = await container.item(id, productId).read();
    return resource as unknown as DunningCampaignDoc | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function updateCampaign(
  id: string,
  productId: string,
  updates: Partial<DunningCampaignDoc>
): Promise<DunningCampaignDoc | null> {
  const existing = await getCampaign(id, productId);
  if (!existing) return null;

  const container = getContainer('dunning_campaigns');
  const updated: DunningCampaignDoc = {
    ...existing,
    ...updates,
    id: existing.id,
    productId: existing.productId,
    updatedAt: new Date().toISOString(),
  };

  const { resource } = await container.items.upsert(updated);
  if (!resource) throw new Error('Failed to update dunning campaign');
  return resource as unknown as DunningCampaignDoc;
}

export async function addCampaignAction(
  id: string,
  productId: string,
  action: DunningActionEntry
): Promise<DunningCampaignDoc | null> {
  const existing = await getCampaign(id, productId);
  if (!existing) return null;

  return updateCampaign(id, productId, {
    actions: [...existing.actions, action],
  });
}

export async function listCampaigns(
  productId: string,
  options?: { status?: DunningStatus; userId?: string; limit?: number }
): Promise<{ campaigns: DunningCampaignDoc[]; total: number }> {
  const container = getContainer('dunning_campaigns');

  let query = 'SELECT * FROM c WHERE c.productId = @productId';
  const parameters = [{ name: '@productId', value: productId }];

  if (options?.status) {
    query += ' AND c.status = @status';
    parameters.push({ name: '@status', value: options.status });
  }

  if (options?.userId) {
    query += ' AND c.userId = @userId';
    parameters.push({ name: '@userId', value: options.userId });
  }

  query += ' ORDER BY c.createdAt DESC';

  const countQuery = query.replace('SELECT *', 'SELECT VALUE COUNT(1)');
  const { resources: countResult } = await container.items
    .query<number>({ query: countQuery, parameters })
    .fetchAll();
  const total = countResult[0] ?? 0;

  const safeLimit = Math.min(Math.max(options?.limit ?? 50, 1), 200);
  query += ` OFFSET 0 LIMIT ${safeLimit}`;

  const { resources } = await container.items
    .query<DunningCampaignDoc>({ query, parameters })
    .fetchAll();

  return { campaigns: resources, total };
}

export async function getActiveCampaignForUser(
  productId: string,
  userId: string,
  subscriptionId: string
): Promise<DunningCampaignDoc | null> {
  const container = getContainer('dunning_campaigns');
  const query =
    'SELECT * FROM c WHERE c.productId = @productId AND c.userId = @userId AND c.subscriptionId = @subId AND c.status IN ("active", "grace_period")';
  const parameters = [
    { name: '@productId', value: productId },
    { name: '@userId', value: userId },
    { name: '@subId', value: subscriptionId },
  ];

  const { resources } = await container.items
    .query<DunningCampaignDoc>({ query, parameters })
    .fetchAll();

  return resources[0] ?? null;
}

export async function getCampaignsDueForRetry(productId: string): Promise<DunningCampaignDoc[]> {
  const container = getContainer('dunning_campaigns');
  const now = new Date().toISOString();
  const query =
    'SELECT * FROM c WHERE c.productId = @productId AND c.status = "active" AND c.nextRetryAt != null AND c.nextRetryAt <= @now';
  const parameters = [
    { name: '@productId', value: productId },
    { name: '@now', value: now },
  ];

  const { resources } = await container.items
    .query<DunningCampaignDoc>({ query, parameters })
    .fetchAll();

  return resources;
}

export async function getCampaignsWithExpiredGrace(
  productId: string
): Promise<DunningCampaignDoc[]> {
  const container = getContainer('dunning_campaigns');
  const now = new Date().toISOString();
  const query =
    'SELECT * FROM c WHERE c.productId = @productId AND c.status = "grace_period" AND c.gracePeriodEndsAt != null AND c.gracePeriodEndsAt <= @now';
  const parameters = [
    { name: '@productId', value: productId },
    { name: '@now', value: now },
  ];

  const { resources } = await container.items
    .query<DunningCampaignDoc>({ query, parameters })
    .fetchAll();

  return resources;
}

export async function getDunningStats(productId: string): Promise<{
  active: number;
  gracePeriod: number;
  resolved: number;
  downgraded: number;
  cancelled: number;
  totalRevenueLostCents: number;
  totalRevenueRecoveredCents: number;
}> {
  const container = getContainer('dunning_campaigns');
  const query = 'SELECT c.status, c.amountCents FROM c WHERE c.productId = @productId';
  const parameters = [{ name: '@productId', value: productId }];

  const { resources } = await container.items
    .query<{ status: DunningStatus; amountCents: number }>({ query, parameters })
    .fetchAll();

  let active = 0,
    gracePeriod = 0,
    resolved = 0,
    downgraded = 0,
    cancelled = 0;
  let totalRevenueLostCents = 0,
    totalRevenueRecoveredCents = 0;

  for (const r of resources) {
    switch (r.status) {
      case 'active':
        active++;
        break;
      case 'grace_period':
        gracePeriod++;
        break;
      case 'resolved':
        resolved++;
        totalRevenueRecoveredCents += r.amountCents;
        break;
      case 'downgraded':
        downgraded++;
        totalRevenueLostCents += r.amountCents;
        break;
      case 'cancelled':
        cancelled++;
        totalRevenueLostCents += r.amountCents;
        break;
    }
  }

  return {
    active,
    gracePeriod,
    resolved,
    downgraded,
    cancelled,
    totalRevenueLostCents,
    totalRevenueRecoveredCents,
  };
}

// =============================================================================
// Dunning Policies (singleton per product)
// =============================================================================

export async function getPolicy(productId: string): Promise<DunningPolicyDoc | null> {
  const container = getContainer('dunning_policies');
  try {
    const { resource } = await container.item(productId, productId).read();
    return resource as unknown as DunningPolicyDoc | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function upsertPolicy(doc: DunningPolicyDoc): Promise<DunningPolicyDoc> {
  const container = getContainer('dunning_policies');
  const { resource } = await container.items.upsert(doc);
  if (!resource) throw new Error('Failed to upsert dunning policy');
  return resource as unknown as DunningPolicyDoc;
}
