import { Plan } from "@prisma/client";
import { prisma } from "./db";
import { isSelfHostedEdition } from "./edition";
import { getEffectiveQuotaLimit } from "./quota-extensions";
import { getNextMonthlyQuotaReset, getPlanLimits } from "./plans";

const TWELVE_HOURS_MS = 12 * 60 * 60 * 1000;
const CREDIT_BALANCE_MAX = 1000;

function getNextFreeQuotaReset(from: Date = new Date()): Date {
  return new Date(from.getTime() + TWELVE_HOURS_MS);
}

function getNextQuotaResetForPlan(plan: Plan, from: Date = new Date()): Date {
  return plan === "FREE" ? getNextFreeQuotaReset(from) : getNextMonthlyQuotaReset(from);
}

function isMonthlyResetDate(date: Date): boolean {
  return (
    date.getDate() === 1 &&
    date.getHours() === 0 &&
    date.getMinutes() === 0 &&
    date.getSeconds() === 0 &&
    date.getMilliseconds() === 0
  );
}

export function formatQuotaResetCountdown(resetsAt: Date | string): string {
  const target = typeof resetsAt === "string" ? new Date(resetsAt) : resetsAt;
  const ms = Math.max(0, target.getTime() - Date.now());
  const totalSec = Math.ceil(ms / 1000);
  const hours = Math.floor(totalSec / 3600);
  const minutes = Math.floor((totalSec % 3600) / 60);
  const seconds = totalSec % 60;
  return `${hours}h ${minutes}m ${seconds}s`;
}

export function formatQuotaResetDisplay(plan: Plan, resetsAt: Date | string): string {
  const target = typeof resetsAt === "string" ? new Date(resetsAt) : resetsAt;
  if (plan === "PREMIUM") {
    return target.toLocaleDateString(undefined, {
      month: "long",
      day: "numeric",
      year: "numeric",
    });
  }
  return formatQuotaResetCountdown(target);
}

function getQuotaExceededMessage(plan: Plan, resetsAt: Date, videoCredits: number): string {
  if (plan === "PREMIUM") {
    const resetLabel = formatQuotaResetDisplay(plan, resetsAt);
    return `Quota exceeded. Your monthly Pro quota resets on ${resetLabel}. Request an extension in Settings if needed.`;
  }

  const countdown = formatQuotaResetCountdown(resetsAt);
  const creditHint = videoCredits > 0 ? "" : " Credits are not available in this build.";
  return `Not enough free quota or credits. Your free quota resets in ${countdown}.${creditHint}`;
}

export async function ensureQuotaReset(userId: string) {
  const user = await prisma.user.findUniqueOrThrow({ where: { id: userId } });
  const now = new Date();

  if (user.plan === "PREMIUM") {
    if (now >= user.quotaResetAt) {
      return prisma.user.update({
        where: { id: userId },
        data: {
          videosUsed: 0,
          bonusQuota: 0,
          quotaResetAt: getNextMonthlyQuotaReset(now),
        },
      });
    }

    if (!isMonthlyResetDate(user.quotaResetAt)) {
      return prisma.user.update({
        where: { id: userId },
        data: {
          quotaResetAt: getNextMonthlyQuotaReset(now),
        },
      });
    }

    return user;
  }

  if (now >= user.quotaResetAt) {
    return prisma.user.update({
      where: { id: userId },
      data: {
        videosUsed: 0,
        quotaResetAt: getNextQuotaResetForPlan(user.plan, now),
      },
    });
  }

  return user;
}

export async function getQuotaInfo(userId: string) {
  const user = await ensureQuotaReset(userId);
  const limits = getPlanLimits(user.plan);
  const limit = isSelfHostedEdition()
    ? limits.monthlyQuota
    : getEffectiveQuotaLimit(user.plan, user.bonusQuota);
  const remaining = Math.max(0, limit - user.videosUsed);
  const videoCredits =
    isSelfHostedEdition() || user.plan !== "FREE" ? 0 : user.videoCredits;
  return {
    used: user.videosUsed,
    limit,
    baseLimit: limits.monthlyQuota,
    bonusQuota: isSelfHostedEdition() ? 0 : user.bonusQuota,
    remaining,
    videoCredits,
    creditBalanceMax: CREDIT_BALANCE_MAX,
    totalAvailable: remaining + videoCredits,
    resetsAt: user.quotaResetAt.toISOString(),
    resetsIn: formatQuotaResetDisplay(user.plan, user.quotaResetAt),
    plan: user.plan,
    maxBatchSize: limits.maxBatchSize,
    watermarkOptional: limits.watermarkOptional,
    maxResolutionHeight: limits.maxResolutionHeight,
    selfHosted: isSelfHostedEdition(),
  };
}

export type ReservationSplit = {
  fromQuota: number;
  fromCredits: number;
};

/**
 * Free plan: use included quota first, then pay-as-you-go credits.
 * Pro plan: subscription quota only (PAYG is a separate product).
 */
export function planReservation(
  plan: Plan,
  remainingQuota: number,
  videoCredits: number,
  count: number,
): ReservationSplit | null {
  if (count <= 0) return { fromQuota: 0, fromCredits: 0 };

  if (plan === "PREMIUM") {
    if (count > remainingQuota) return null;
    return { fromQuota: count, fromCredits: 0 };
  }

  const fromQuota = Math.min(count, Math.max(0, remainingQuota));
  const fromCredits = count - fromQuota;
  if (fromCredits > videoCredits) return null;
  return { fromQuota, fromCredits };
}

export async function checkQuota(userId: string, requestedCount: number) {
  if (isSelfHostedEdition()) {
    const info = await getQuotaInfo(userId);
    if (requestedCount > info.maxBatchSize) {
      return {
        ok: false as const,
        error: `Batch limit exceeded. Max ${info.maxBatchSize} files per batch.`,
        used: info.used,
        limit: info.limit,
        remaining: info.remaining,
        videoCredits: 0,
        resetsAt: info.resetsAt,
      };
    }
    return {
      ok: true as const,
      ...info,
      reservation: { fromQuota: requestedCount, fromCredits: 0 },
    };
  }

  const user = await ensureQuotaReset(userId);
  const limits = getPlanLimits(user.plan);
  const limit = getEffectiveQuotaLimit(user.plan, user.bonusQuota);
  const remaining = Math.max(0, limit - user.videosUsed);

  if (requestedCount > limits.maxBatchSize) {
    return {
      ok: false as const,
      error: `Batch limit exceeded. Your plan allows up to ${limits.maxBatchSize} files per batch.`,
      used: user.videosUsed,
      limit,
      remaining,
      videoCredits: user.videoCredits,
      resetsAt: user.quotaResetAt.toISOString(),
    };
  }

  const split = planReservation(user.plan, remaining, user.videoCredits, requestedCount);
  if (!split) {
    return {
      ok: false as const,
      error: getQuotaExceededMessage(user.plan, user.quotaResetAt, user.videoCredits),
      used: user.videosUsed,
      limit,
      remaining,
      videoCredits: user.plan === "FREE" ? user.videoCredits : 0,
      resetsAt: user.quotaResetAt.toISOString(),
    };
  }

  const info = await getQuotaInfo(userId);
  return { ok: true as const, ...info, reservation: split };
}

/**
 * Atomically reserve plan quota first, then prepaid credits.
 * Returns how many of each were taken (for per-item billingSource).
 */
export async function reserveQuota(userId: string, count: number): Promise<ReservationSplit> {
  if (count <= 0) return { fromQuota: 0, fromCredits: 0 };

  if (isSelfHostedEdition()) {
    const limits = getPlanLimits("FREE");
    if (count > limits.maxBatchSize) {
      throw new Error(`Batch limit exceeded. Max ${limits.maxBatchSize} files per batch.`);
    }
    await ensureQuotaReset(userId);
    await prisma.user.update({
      where: { id: userId },
      data: { videosUsed: { increment: count } },
    });
    return { fromQuota: count, fromCredits: 0 };
  }

  await ensureQuotaReset(userId);

  return prisma.$transaction(async (tx) => {
    const rows = await tx.$queryRaw<
      Array<{
        id: string;
        plan: Plan;
        videosUsed: number;
        bonusQuota: number;
        videoCredits: number;
        quotaResetAt: Date;
      }>
    >`SELECT id, plan, "videosUsed", "bonusQuota", "videoCredits", "quotaResetAt" FROM "User" WHERE id = ${userId} FOR UPDATE`;

    const user = rows[0];
    if (!user) throw new Error("User not found");

    const limits = getPlanLimits(user.plan);
    if (count > limits.maxBatchSize) {
      throw new Error(
        `Batch limit exceeded. Your plan allows up to ${limits.maxBatchSize} files per batch.`,
      );
    }

    const limit = getEffectiveQuotaLimit(user.plan, user.bonusQuota);
    const remaining = Math.max(0, limit - user.videosUsed);
    const split = planReservation(user.plan, remaining, user.videoCredits, count);
    if (!split) {
      throw new Error(getQuotaExceededMessage(user.plan, user.quotaResetAt, user.videoCredits));
    }

    await tx.user.update({
      where: { id: userId },
      data: {
        ...(split.fromQuota > 0 ? { videosUsed: { increment: split.fromQuota } } : {}),
        ...(split.fromCredits > 0 ? { videoCredits: { decrement: split.fromCredits } } : {}),
      },
    });

    return split;
  });
}

export async function releaseQuota(userId: string, count: number) {
  if (count <= 0) return;
  await ensureQuotaReset(userId);
  await prisma.$executeRaw`
    UPDATE "User"
    SET "videosUsed" = GREATEST(0, "videosUsed" - ${count})
    WHERE id = ${userId}
  `;
}

export async function releaseCredits(userId: string, count: number) {
  if (count <= 0) return;
  await prisma.$executeRaw`
    UPDATE "User"
    SET "videoCredits" = LEAST(${CREDIT_BALANCE_MAX}, "videoCredits" + ${count})
    WHERE id = ${userId}
  `;
}

/** Release one reserved unit based on how the job item was billed. */
export async function releaseReservation(
  userId: string,
  billingSource: "QUOTA" | "CREDIT",
  count = 1,
) {
  if (billingSource === "CREDIT") {
    await releaseCredits(userId, count);
  } else {
    await releaseQuota(userId, count);
  }
}

export async function releaseReservationSplit(userId: string, split: ReservationSplit) {
  if (split.fromQuota > 0) await releaseQuota(userId, split.fromQuota);
  if (split.fromCredits > 0) await releaseCredits(userId, split.fromCredits);
}

/** @deprecated Prefer reserveQuota at create; kept for callers that still increment on success. */
export async function incrementQuota(userId: string, count: number) {
  await reserveQuota(userId, count);
}

export function getInitialQuotaResetAt(): Date {
  return getNextFreeQuotaReset();
}
