import prisma from "../db.server";

export interface CreditAllocationSummary {
  id: string;
  sourceType: string;
  sourceId: string | null;
  creditsAllocated: number;
  creditsUsed: number;
  creditsRemaining: number;
  expiresAt: Date | null;
  createdAt: Date;
  status: string;
}

export interface CreditBalanceResult {
  availableBalance: number;
  totalAllocated: number;
  totalUsed: number;
  monthlyLimit: number;
  monthlyUsed: number;
  allocations: CreditAllocationSummary[];
}

export interface AllocateCreditsOptions {
  shopId: string;
  sourceType: "MONTHLY_PLAN" | "ADDITIONAL_PURCHASE" | "PROMO" | "ADMIN_ADJUSTMENT";
  amount: number;
  sourceId?: string;
  expiresAt?: Date | null;
  idempotencyKey?: string;
}

export interface ConsumeCreditsOptions {
  shopId: string;
  creditsNeeded?: number;
  eventType?: string;
  provider?: string;
  model?: string;
  requestId?: string;
  metadata?: any;
}

/**
 * Calculates current available credit balance and allocation breakdown across active ledger records.
 */
export async function getAvailableCredits(shopId: string): Promise<CreditBalanceResult> {
  const now = new Date();

  // 1. Expire outdated allocations
  try {
    await prisma.creditAllocation.updateMany({
      where: {
        shopId,
        status: "ACTIVE",
        expiresAt: { lt: now },
      },
      data: { status: "EXPIRED" },
    });

    // Mark exhausted allocations
    await prisma.creditAllocation.updateMany({
      where: {
        shopId,
        status: "ACTIVE",
        creditsRemaining: { lte: 0 },
      },
      data: { status: "EXHAUSTED" },
    });
  } catch (err) {
    console.warn("[Credit Ledger] Expiration check warning:", err);
  }

  // 2. Fetch active allocations
  const activeAllocations = await prisma.creditAllocation.findMany({
    where: { shopId, status: "ACTIVE" },
    orderBy: [
      { expiresAt: "asc" }, // FEFO: Soonest expiring first
      { createdAt: "asc" }, // FIFO: Oldest first
    ],
  });

  const availableBalance = activeAllocations.reduce((sum, a) => sum + Math.max(0, a.creditsRemaining), 0);

  // 3. Fetch total historical allocation & usage
  const allAllocations = await prisma.creditAllocation.findMany({
    where: { shopId },
  });

  const totalAllocated = allAllocations.reduce((sum, a) => sum + a.creditsAllocated, 0);
  const totalUsed = allAllocations.reduce((sum, a) => sum + a.creditsUsed, 0);

  // 4. Fetch subscription details for monthly usage context
  const activeSub = await prisma.subscription.findFirst({
    where: { shopId, status: "ACTIVE" },
    orderBy: { createdAt: "desc" },
  });

  const monthlyLimit = activeSub?.monthlyLimit || 100;
  const monthlyUsed = activeSub?.currentUsage || 0;

  const summaries: CreditAllocationSummary[] = activeAllocations.map((a) => ({
    id: a.id,
    sourceType: a.sourceType,
    sourceId: a.sourceId,
    creditsAllocated: a.creditsAllocated,
    creditsUsed: a.creditsUsed,
    creditsRemaining: a.creditsRemaining,
    expiresAt: a.expiresAt,
    createdAt: a.createdAt,
    status: a.status,
  }));

  return {
    availableBalance,
    totalAllocated,
    totalUsed,
    monthlyLimit,
    monthlyUsed,
    allocations: summaries,
  };
}

/**
 * Idempotently allocates new credits to a shop.
 * ADDITIVE RULE: Appends credits to the ledger without zeroing or replacing existing remaining credits.
 */
export async function allocateCredits({
  shopId,
  sourceType,
  amount,
  sourceId,
  expiresAt,
  idempotencyKey,
}: AllocateCreditsOptions) {
  if (amount <= 0) {
    throw new Error("Credit allocation amount must be greater than zero.");
  }

  // Idempotency Protection: Prevent double-crediting
  if (idempotencyKey) {
    const existing = await prisma.creditAllocation.findUnique({
      where: { idempotencyKey },
    });
    if (existing) {
      console.log(`[Credit Ledger] Idempotent hit: Allocation ${existing.id} already exists for key ${idempotencyKey}`);
      return existing;
    }
  }

  const newAllocation = await prisma.creditAllocation.create({
    data: {
      shopId,
      sourceType,
      sourceId,
      creditsAllocated: amount,
      creditsUsed: 0,
      creditsRemaining: amount,
      expiresAt: expiresAt || null,
      status: "ACTIVE",
      idempotencyKey: idempotencyKey || null,
    },
  });

  console.log(`[Credit Ledger] Allocated +${amount} credits (${sourceType}) to shop ${shopId}. Allocation ID: ${newAllocation.id}`);

  return newAllocation;
}

/**
 * Reserve and consume credits before executing AI request (FEFO / FIFO strategy).
 */
export async function reserveAndConsumeCredits({
  shopId,
  creditsNeeded = 1,
  eventType = "AI_CHAT_REQUEST",
  provider,
  model,
  requestId,
  metadata,
}: ConsumeCreditsOptions) {
  const balance = await getAvailableCredits(shopId);

  if (balance.availableBalance < creditsNeeded) {
    console.warn(`[Credit Ledger] Insufficient credits for shop ${shopId}. Needed: ${creditsNeeded}, Available: ${balance.availableBalance}`);
    return {
      success: false,
      reason: "usage_limit_reached",
      availableBalance: balance.availableBalance,
      monthlyLimit: balance.monthlyLimit,
      monthlyUsed: balance.monthlyUsed,
    };
  }

  // Fetch active allocations ordered by soonest expiring (FEFO)
  const activeAllocations = await prisma.creditAllocation.findMany({
    where: { shopId, status: "ACTIVE", creditsRemaining: { gt: 0 } },
    orderBy: [
      { expiresAt: "asc" },
      { createdAt: "asc" },
    ],
  });

  let remainingToDeduct = creditsNeeded;
  let primaryAllocationId: string | null = null;

  for (const alloc of activeAllocations) {
    if (remainingToDeduct <= 0) break;

    const deductFromThis = Math.min(alloc.creditsRemaining, remainingToDeduct);
    const newRemaining = alloc.creditsRemaining - deductFromThis;
    const newUsed = alloc.creditsUsed + deductFromThis;
    const newStatus = newRemaining <= 0 ? "EXHAUSTED" : "ACTIVE";

    if (!primaryAllocationId) primaryAllocationId = alloc.id;

    await prisma.creditAllocation.update({
      where: { id: alloc.id },
      data: {
        creditsRemaining: newRemaining,
        creditsUsed: newUsed,
        status: newStatus,
      },
    });

    remainingToDeduct -= deductFromThis;
  }

  // Log usage event
  const usageEvent = await prisma.usageEvent.create({
    data: {
      shopId,
      creditAllocationId: primaryAllocationId,
      requestId: requestId || `req_${Date.now()}`,
      eventType,
      creditsConsumed: creditsNeeded,
      provider: provider || "openai",
      model: model || "gpt-4o-mini",
      metadata: metadata || null,
    },
  });

  // Increment monthly subscription counter
  try {
    const activeSub = await prisma.subscription.findFirst({
      where: { shopId, status: "ACTIVE" },
      orderBy: { createdAt: "desc" },
    });
    if (activeSub) {
      await prisma.subscription.update({
        where: { id: activeSub.id },
        data: { currentUsage: { increment: creditsNeeded } },
      });
    }
  } catch (e) {}

  const updatedBalance = await getAvailableCredits(shopId);

  return {
    success: true,
    consumedCredits: creditsNeeded,
    availableBalance: updatedBalance.availableBalance,
    usageEventId: usageEvent.id,
  };
}

/**
 * Perform auditable manual credit adjustment by SaaS Admin.
 */
export async function adminAdjustCredits({
  shopId,
  amount,
  reason,
  adminId = "saas_admin",
}: {
  shopId: string;
  amount: number;
  reason: string;
  adminId?: string;
}) {
  const currentBalance = await getAvailableCredits(shopId);

  if (amount === 0) {
    throw new Error("Adjustment amount cannot be zero.");
  }

  let allocation = null;

  if (amount > 0) {
    // Add positive credits
    allocation = await allocateCredits({
      shopId,
      sourceType: "ADMIN_ADJUSTMENT",
      amount,
      sourceId: `admin_${Date.now()}`,
    });
  } else {
    // Deduct credits manually
    const absDeduct = Math.abs(amount);
    const activeAllocations = await prisma.creditAllocation.findMany({
      where: { shopId, status: "ACTIVE", creditsRemaining: { gt: 0 } },
      orderBy: [{ expiresAt: "asc" }, { createdAt: "asc" }],
    });

    let remainingToDeduct = absDeduct;
    for (const alloc of activeAllocations) {
      if (remainingToDeduct <= 0) break;
      const deduct = Math.min(alloc.creditsRemaining, remainingToDeduct);
      const newRem = alloc.creditsRemaining - deduct;
      const newUsed = alloc.creditsUsed + deduct;
      await prisma.creditAllocation.update({
        where: { id: alloc.id },
        data: {
          creditsRemaining: newRem,
          creditsUsed: newUsed,
          status: newRem <= 0 ? "EXHAUSTED" : "ACTIVE",
        },
      });
      remainingToDeduct -= deduct;
    }
  }

  const newBalance = await getAvailableCredits(shopId);

  // Mandatory Audit Log
  await prisma.auditLog.create({
    data: {
      shopId,
      action: "ADMIN_MANUAL_CREDIT_ADJUSTMENT",
      resource: `Admin ${adminId} adjusted credits: ${amount > 0 ? "+" : ""}${amount}. Reason: ${reason}`,
      status: "SUCCESSFUL",
      metadata: {
        adminId,
        amount,
        reason,
        previousBalance: currentBalance.availableBalance,
        newBalance: newBalance.availableBalance,
      },
    },
  });

  return {
    success: true,
    previousBalance: currentBalance.availableBalance,
    newBalance: newBalance.availableBalance,
    adjustmentAmount: amount,
    allocation,
  };
}
