import { Types } from "mongoose";
import {
  SUBSCRIPTION_PROVIDER_REVENUECAT,
  type SubscriptionEntitlement,
  type SubscriptionStatus,
} from "../constants/subscription";
import { revenuecatConfig } from "../config/revenuecat";
import UserModel from "../models/UserModel";
import RevenueCatWebhookEventModel from "../models/RevenueCatWebhookEventModel";
import RevenueCatSubscriptionEventModel from "../models/RevenueCatSubscriptionEventModel";
import { normalizeRevenueCatProductId } from "../utils/revenuecatProductId";

const TAG = "[revenuecat:webhook]";

export type RevenueCatWebhookEventPayload = {
  type?: string;
  id?: string;
  app_user_id?: string;
  product_id?: string;
  new_product_id?: string | null;
  entitlement_ids?: string[] | null;
  purchased_at_ms?: number | null;
  expiration_at_ms?: number | null;
  store?: string;
  environment?: string;
  price?: number | null;
  currency?: string | null;
  price_in_purchased_currency?: number | null;
};

/** RevenueCat `price` is major units (e.g. 3.99 USD) → cents. */
export function revenueCatPriceToCents(
  price: number | null | undefined,
): number | null {
  if (price == null || typeof price !== "number" || !Number.isFinite(price)) {
    return null;
  }
  return Math.round(price * 100);
}

export type ProcessWebhookResult = {
  ok: boolean;
  userFound: boolean;
  duplicate?: boolean;
  message?: string;
};

function msToDate(ms: number | null | undefined): Date | null {
  if (ms == null || typeof ms !== "number" || !Number.isFinite(ms)) return null;
  return new Date(ms);
}

/** Map RevenueCat event type → subscriptionStatus on User. */
export function mapEventTypeToSubscriptionStatus(
  eventType: string,
  entitlementIds: string[],
): SubscriptionStatus {
  switch (eventType) {
    case "INITIAL_PURCHASE":
    case "RENEWAL":
    case "UNCANCELLATION":
    case "PRODUCT_CHANGE":
      return "active";
    case "CANCELLATION":
      return "cancelled";
    case "EXPIRATION":
      return "expired";
    case "BILLING_ISSUE":
      return "billing_issue";
    case "TRANSFER":
      return entitlementIds.length > 0 ? "active" : "inactive";
    default:
      return "inactive";
  }
}

/**
 * Family beats premium when both appear in entitlement_ids.
 * Uses configured lookup keys from env (default premium / family).
 */
export function selectActiveEntitlement(
  entitlementIds: string[] | null | undefined,
): SubscriptionEntitlement | null {
  const ids = entitlementIds ?? [];
  const family = revenuecatConfig.familyEntitlement;
  const premium = revenuecatConfig.premiumEntitlement;
  if (ids.includes(family)) return "family";
  if (ids.includes(premium)) return "premium";
  return null;
}

function isExpirationInFuture(expirationAt: Date | null): boolean {
  if (!expirationAt) return false;
  return expirationAt.getTime() > Date.now();
}

async function findUserByAppUserId(appUserId: string) {
  if (Types.ObjectId.isValid(appUserId) && String(new Types.ObjectId(appUserId)) === appUserId) {
    const byId = await UserModel.findById(appUserId);
    if (byId && !byId.isDeleted) return byId;
  }
  return UserModel.findOne({
    revenueCatUserId: appUserId,
    isDeleted: { $ne: true },
  });
}

export async function processRevenueCatWebhookEvent(
  event: RevenueCatWebhookEventPayload,
): Promise<ProcessWebhookResult> {
  const eventType = event.type ?? "UNKNOWN";
  const appUserId = event.app_user_id?.trim() ?? "";
  const productId = event.product_id?.trim() ?? null;
  const newProductId = event.new_product_id?.trim() ?? null;
  // For PRODUCT_CHANGE, new_product_id is the subscription the user is switching TO.
  // Fall back to product_id when new_product_id is absent (older RC webhook format).
  const effectiveProductId = (eventType === "PRODUCT_CHANGE" && newProductId) ? newProductId : productId;
  const entitlementIds = event.entitlement_ids ?? [];
  const eventId = event.id?.trim();

  console.log(
    `${TAG} Received type=${eventType} app_user_id=${appUserId || "—"} product_id=${productId ?? "—"} entitlements=${JSON.stringify(entitlementIds)}`,
  );

  if (!appUserId) {
    console.warn(`${TAG} Missing app_user_id`);
    return { ok: false, userFound: false, message: "Missing app_user_id" };
  }

  if (eventId) {
    const duplicate = await RevenueCatWebhookEventModel.findOne({ eventId }).lean();
    if (duplicate) {
      console.log(`${TAG} Duplicate event id=${eventId} — skipping`);
      return { ok: true, userFound: true, duplicate: true, message: "Duplicate event" };
    }
  }

  const user = await findUserByAppUserId(appUserId);
  if (!user) {
    console.warn(`${TAG} User not found for app_user_id=${appUserId} event=${eventType}`);
    if (eventId) {
      await RevenueCatWebhookEventModel.create({
        eventId,
        eventType,
        appUserId,
      }).catch(() => undefined);
    }
    return { ok: true, userFound: false, message: "User not found" };
  }

  const purchasedAt = msToDate(event.purchased_at_ms);
  const expirationAt = msToDate(event.expiration_at_ms);
  const priceRaw =
    event.price_in_purchased_currency ?? event.price ?? null;
  const priceCents = revenueCatPriceToCents(priceRaw);
  const currency = event.currency?.trim().toLowerCase() ?? "usd";

  let subscriptionStatus = mapEventTypeToSubscriptionStatus(eventType, entitlementIds);
  let activeEntitlement = selectActiveEntitlement(entitlementIds);

  if (eventType === "EXPIRATION") {
    subscriptionStatus = "expired";
    activeEntitlement = null;
  } else if (eventType === "CANCELLATION") {
    subscriptionStatus = "cancelled";
    if (!isExpirationInFuture(expirationAt)) {
      activeEntitlement = null;
    }
  } else if (eventType === "BILLING_ISSUE") {
    subscriptionStatus = "billing_issue";
    if (!isExpirationInFuture(expirationAt)) {
      activeEntitlement = null;
    }
  } else if (eventType !== "TRANSFER" && subscriptionStatus === "inactive") {
    const existing = user.subscriptionStatus;
    if (existing && existing !== "inactive") {
      subscriptionStatus = existing;
    }
  }

  const logicalProduct = normalizeRevenueCatProductId(effectiveProductId);

  // Outgoing PRODUCT_CHANGE: RC may fire a separate event for the old product when its period
  // ends (expirationAt in the past, no new_product_id). When new_product_id is present it is
  // always the single authoritative event and is never outgoing.
  const isOutgoingProductChange =
    eventType === "PRODUCT_CHANGE" &&
    !newProductId &&
    expirationAt !== null &&
    expirationAt.getTime() <= Date.now();

  if (!isOutgoingProductChange) {
    user.revenueCatUserId = appUserId;
    user.subscriptionProvider = SUBSCRIPTION_PROVIDER_REVENUECAT;
    user.subscriptionStatus = subscriptionStatus;
    user.activeEntitlement = activeEntitlement;
    user.subscriptionProductId = logicalProduct ?? effectiveProductId;
    if (purchasedAt && (eventType === "INITIAL_PURCHASE" || !user.subscriptionStartedAt)) {
      user.subscriptionStartedAt = purchasedAt;
    }
    if (expirationAt && (eventType === "EXPIRATION" || expirationAt.getTime() > Date.now())) {
      user.subscriptionExpiresAt = expirationAt;
    }
    user.lastRevenueCatEventAt = new Date();
    await user.save();
    console.log(
      `${TAG} User updated id=${user._id} status=${subscriptionStatus} entitlement=${activeEntitlement ?? "none"} product=${effectiveProductId ?? "—"} logical=${logicalProduct ?? "—"}`,
    );
  } else {
    console.log(
      `${TAG} Outgoing PRODUCT_CHANGE skipped for UserModel id=${user._id} product=${productId ?? "—"} — history still recorded`,
    );
  }

  if (eventId) {
    await Promise.all([
      RevenueCatWebhookEventModel.create({
        eventId,
        eventType,
        appUserId,
      }).catch((err) => {
        console.warn(`${TAG} Could not record event id (race?):`, err);
      }),
      RevenueCatSubscriptionEventModel.create({
        revenueCatEventId: eventId,
        userId: user._id,
        appUserId,
        eventType,
        productId: logicalProduct ?? effectiveProductId,
        entitlementIds,
        activeEntitlement,
        subscriptionStatus,
        purchasedAt,
        expirationAt,
        store: event.store ?? null,
        environment: event.environment ?? null,
        priceCents,
        currency: priceCents != null ? currency : null,
      }).catch((err) => {
        console.warn(`${TAG} Could not record subscription event (race?):`, err);
      }),
    ]);
  }

  return { ok: true, userFound: true, message: "User updated" };
}
