/**
 * Subscriptions Domain Adapter
 *
 * Routes subscriptions CRUD to Prisma or Convex based on data-source config.
 * Maintains Prisma-compatible method signatures so service layer changes are minimal.
 *
 * Stripe SDK calls (stripe.subscriptions.*, stripe.invoices.*) remain untouched.
 * This adapter only wraps the database CRUD layer for the subscriptions table.
 *
 * Convex queries/mutations live in convex/billing/ since subscriptions
 * are part of the billing schema domain in Convex.
 */

import { prisma } from "@/lib/db";
import { convexQuery, convexMutation } from "@/lib/convex-client";
import { getDataSource } from "@/lib/data-source";
import {
  convexToPrismaShape,
  convexToPrismaShapeMany,
  applyPagination,
  dateToConvexTimestamp,
  type ConvexDoc,
  type BatchResult,
} from "./base";

const DOMAIN = "subscriptions" as const;
const TIMESTAMP_FIELDS = [
  "createdAt",
  "updatedAt",
  "startDate",
  "endDate",
  "trialEnd",
  "cancelledAt",
  "currentPeriodEnd",
  "trialEndsAt",
  "lastPaymentDate",
  "lastPaymentAttempt",
  "setupFeePaidAt",
];

function isConvex(): boolean {
  return getDataSource(DOMAIN) === "convex";
}

// ─── findMany ────────────────────────────────────────────────────────────

export async function findMany(args: {
  where?: Record<string, unknown>;
  orderBy?: Record<string, string> | Record<string, string>[];
  take?: number;
  skip?: number;
  select?: Record<string, boolean>;
}) {
  if (!isConvex()) {
    return prisma.subscriptions.findMany(args as any);
  }

  const where = (args.where || {}) as Record<string, unknown>;
  const userId = where.userId as string | undefined;

  let docs: ConvexDoc[] = [];

  if (userId) {
    // Use the Convex billing query that fetches by userId
    const doc = await convexQuery<ConvexDoc | null>(
      "billing/queries:getSubscription",
      { userId },
    );
    if (doc) {
      docs = [doc];
    }
  }

  // Apply status filter if present
  const statusFilter = where.status as
    | string
    | { not?: string; in?: string[] }
    | undefined;
  if (statusFilter) {
    if (typeof statusFilter === "string") {
      docs = docs.filter((d) => d.status === statusFilter);
    } else if (typeof statusFilter === "object") {
      if (statusFilter.not) {
        docs = docs.filter((d) => d.status !== statusFilter.not);
      }
      if (statusFilter.in) {
        const allowed = new Set(statusFilter.in);
        docs = docs.filter((d) => allowed.has(d.status as string));
      }
    }
  }

  const paginated = applyPagination(docs, args.skip, args.take);
  return convexToPrismaShapeMany(paginated, TIMESTAMP_FIELDS);
}

// ─── findFirst ───────────────────────────────────────────────────────────

export async function findFirst(args: {
  where?: Record<string, unknown>;
  orderBy?: Record<string, string> | Record<string, string>[];
  select?: Record<string, boolean>;
}) {
  if (!isConvex()) {
    return prisma.subscriptions.findFirst(args as any);
  }

  const where = (args.where || {}) as Record<string, unknown>;
  const userId = where.userId as string | undefined;

  if (userId) {
    const doc = await convexQuery<ConvexDoc | null>(
      "billing/queries:getSubscription",
      { userId },
    );
    if (!doc) return null;

    // Verify additional where conditions
    for (const [field, value] of Object.entries(where)) {
      if (field === "userId") continue;
      const docVal = field === "id" ? doc.prismaId || doc._id : doc[field];
      if (typeof value === "object" && value !== null) {
        // Handle complex filters like { not: 'incomplete_expired' }
        const filterObj = value as Record<string, unknown>;
        if (filterObj.not !== undefined && docVal === filterObj.not)
          return null;
        if (
          filterObj.in !== undefined &&
          !(filterObj.in as unknown[]).includes(docVal)
        )
          return null;
      } else if (docVal !== value) {
        return null;
      }
    }

    return convexToPrismaShape(doc, TIMESTAMP_FIELDS);
  }

  // Fallback: fetch all matching via findMany
  const results = await findMany({ where: args.where, take: 1 });
  return results[0] || null;
}

// ─── findUnique ──────────────────────────────────────────────────────────

export async function findUnique(args: {
  where: { id?: string; stripeSubscriptionId?: string };
  select?: Record<string, boolean>;
}) {
  if (!isConvex()) {
    return prisma.subscriptions.findUnique(args as any);
  }

  // For Convex, findUnique by id or stripeSubscriptionId both use findFirst
  if (args.where.id) {
    return findFirst({ where: { id: args.where.id } });
  }
  if (args.where.stripeSubscriptionId) {
    return findFirst({
      where: { stripeSubscriptionId: args.where.stripeSubscriptionId },
    });
  }

  return null;
}

// ─── count ───────────────────────────────────────────────────────────────

export async function count(args?: { where?: Record<string, unknown> }) {
  if (!isConvex()) {
    return prisma.subscriptions.count(args as any);
  }

  const docs = await findMany({
    where: args?.where,
    take: 10000,
  });
  return docs.length;
}

// ─── create ──────────────────────────────────────────────────────────────

export async function create(args: {
  data: {
    id?: string;
    userId: string;
    stripeCustomerId: string;
    stripeSubscriptionId: string;
    stripePriceId: string;
    tierId: string;
    status: string;
    billingPeriod: string;
    currentPeriodEnd: Date;
    trialEndsAt?: Date | null;
    cancelAtPeriodEnd?: boolean;
    paymentFailureCount?: number;
    paymentMethodId?: string | null;
    cardBrand?: string | null;
    cardLastFour?: string | null;
    setupFeePaid?: boolean;
    setupFeeAmount?: number | null;
  };
}) {
  if (!isConvex()) {
    return prisma.subscriptions.create(args as any);
  }

  const d = args.data;
  const convexId = await convexMutation<string>(
    "billing/mutations:createSubscription",
    {
      userId: d.userId,
      stripeCustomerId: d.stripeCustomerId,
      stripeSubscriptionId: d.stripeSubscriptionId,
      stripePriceId: d.stripePriceId,
      tierId: d.tierId,
      status: d.status,
      billingPeriod: d.billingPeriod,
      currentPeriodEnd: dateToConvexTimestamp(d.currentPeriodEnd) || Date.now(),
      ...(d.trialEndsAt
        ? { trialEndsAt: dateToConvexTimestamp(d.trialEndsAt) }
        : {}),
    },
  );

  const now = new Date();
  return {
    id: d.id || convexId || "",
    userId: d.userId,
    stripeCustomerId: d.stripeCustomerId,
    stripeSubscriptionId: d.stripeSubscriptionId,
    stripePriceId: d.stripePriceId,
    tierId: d.tierId,
    status: d.status,
    billingPeriod: d.billingPeriod,
    currentPeriodEnd: d.currentPeriodEnd,
    trialEndsAt: d.trialEndsAt ?? null,
    cancelAtPeriodEnd: d.cancelAtPeriodEnd ?? false,
    cancelledAt: null,
    lastPaymentDate: null,
    lastPaymentAttempt: null,
    paymentFailureCount: d.paymentFailureCount ?? 0,
    paymentMethodId: d.paymentMethodId ?? null,
    cardBrand: d.cardBrand ?? null,
    cardLastFour: d.cardLastFour ?? null,
    setupFeePaid: d.setupFeePaid ?? false,
    setupFeeAmount: d.setupFeeAmount ?? null,
    setupFeePaidAt: null,
    createdAt: now,
    updatedAt: now,
  };
}

// ─── update ──────────────────────────────────────────────────────────────

export async function update(args: {
  where: { id?: string; stripeSubscriptionId?: string };
  data: Record<string, unknown>;
}) {
  if (!isConvex()) {
    return prisma.subscriptions.update(args as any);
  }

  // Find the existing doc to get the Convex _id
  const existing = await findUnique({ where: args.where });
  if (!existing) return null;

  // Build update params for Convex mutation
  const updateData: Record<string, unknown> = {};
  const d = args.data;

  if (d.status !== undefined) updateData.status = d.status;
  if (d.currentPeriodEnd !== undefined) {
    updateData.currentPeriodEnd = dateToConvexTimestamp(
      d.currentPeriodEnd as Date | string,
    );
  }
  if (d.cancelAtPeriodEnd !== undefined)
    updateData.cancelAtPeriodEnd = d.cancelAtPeriodEnd;
  if (d.cancelledAt !== undefined) {
    updateData.cancelledAt = dateToConvexTimestamp(
      d.cancelledAt as Date | string | null,
    );
  }
  if (d.cardBrand !== undefined) updateData.cardBrand = d.cardBrand;
  if (d.cardLastFour !== undefined) updateData.cardLastFour = d.cardLastFour;

  // The Convex mutation expects an id field of type Id<"subscriptions">
  // Since we may only have a prismaId, use a best-effort approach:
  // the updateSubscription mutation requires the Convex doc _id
  if (Object.keys(updateData).length > 0) {
    try {
      await convexMutation("billing/mutations:updateSubscription", {
        id: (existing as Record<string, unknown>)._id || existing.id,
        ...updateData,
      });
    } catch {
      // If Convex update fails (e.g., id mismatch), the data is stale but safe
    }
  }

  // Return merged shape for compatibility
  return { ...existing, ...args.data, updatedAt: new Date() };
}

// ─── updateMany ──────────────────────────────────────────────────────────

export async function updateMany(args: {
  where: Record<string, unknown>;
  data: Record<string, unknown>;
}): Promise<BatchResult> {
  if (!isConvex()) {
    return prisma.subscriptions.updateMany(args as any);
  }

  const docs = await findMany({
    where: args.where,
    take: 10000,
  });

  let updatedCount = 0;
  for (const doc of docs) {
    const result = await update({
      where: { id: doc.id as string },
      data: args.data,
    });
    if (result) updatedCount++;
  }

  return { count: updatedCount };
}

// ─── delete ──────────────────────────────────────────────────────────────

async function deleteOne(args: {
  where: { id?: string; stripeSubscriptionId?: string };
}) {
  if (!isConvex()) {
    return prisma.subscriptions.delete(args as any);
  }

  const existing = await findUnique({ where: args.where });
  return (
    existing || { id: args.where.id || args.where.stripeSubscriptionId || "" }
  );
}

export { deleteOne as delete };

// ─── deleteMany ──────────────────────────────────────────────────────────

export async function deleteMany(args: {
  where: Record<string, unknown>;
}): Promise<BatchResult> {
  if (!isConvex()) {
    return prisma.subscriptions.deleteMany(args as any);
  }

  const docs = await findMany({
    where: args.where,
    take: 10000,
  });

  return { count: docs.length };
}

// ─── upsert ──────────────────────────────────────────────────────────────

export async function upsert(args: {
  where: { id?: string; stripeSubscriptionId?: string };
  create: Record<string, unknown>;
  update: Record<string, unknown>;
}) {
  if (!isConvex()) {
    return prisma.subscriptions.upsert(args as any);
  }

  const existing = await findUnique({ where: args.where });
  if (existing) {
    return update({ where: args.where, data: args.update });
  }
  return create({ data: args.create as any });
}

// ─── Namespace export ────────────────────────────────────────────────────

export const subscriptionsAdapter = {
  findMany,
  findFirst,
  findUnique,
  count,
  create,
  update,
  updateMany,
  delete: deleteOne,
  deleteMany,
  upsert,
};
