import { Response } from "express";
import { Types } from "mongoose";
import { STATUS_CODES } from "../constants/statusCodes";
import { SUBSCRIPTION_PROVIDER_REVENUECAT } from "../constants/subscription";
import type {
  SubscriptionEntitlement,
  SubscriptionStatus,
} from "../constants/subscription";
import { CustomRequest } from "../interfaces/auth";
import UserModel from "../models/UserModel";
import SubscriptionPlanModel from "../models/SubscriptionPlanModel";
import RevenueCatSubscriptionEventModel from "../models/RevenueCatSubscriptionEventModel";
import ResponseUtil from "../utils/Response/responseUtils";
import { isRevenueCatApiConfigured } from "../config/revenuecat";
import { fetchCustomerEntitlementsFromApi } from "../services/revenuecatClient";
import { selectActiveEntitlement } from "../services/revenuecatWebhookService";
import {
  hasFamilyAccess,
  hasPremiumAccess,
} from "../utils/subscriptionAccess";

type BillingUserFields = {
  subscriptionProvider?: string | null;
  subscriptionStatus?: SubscriptionStatus;
  activeEntitlement?: SubscriptionEntitlement | null;
  subscriptionProductId?: string | null;
  subscriptionStartedAt?: Date | null;
  subscriptionExpiresAt?: Date | null;
};

type BillingPlanPricing = {
  name: string;
  slug: string;
  tier?: string;
  subscriptionType?: string;
  interval: string;
  priceCents: number;
  currency: string;
};

type BillingPricingPayload = {
  planName: string | null;
  tier: string | null;
  subscriptionType: string | null;
  interval: string | null;
  price: number | null;
  currency: string | null;
};

function emptyBillingPricing(): BillingPricingPayload {
  return {
    planName: null,
    tier: null,
    subscriptionType: null,
    interval: null,
    price: null,
    currency: null,
  };
}

function resolveEventPriceCents(
  eventPriceCents?: number | null,
  planPriceCents?: number | null,
): number | null {
  if (eventPriceCents != null && eventPriceCents > 0) {
    return eventPriceCents;
  }
  return planPriceCents ?? null;
}

function billingPricingFromPlanAndEvent(
  plan?: BillingPlanPricing | null,
  eventPrice?: { priceCents?: number | null; currency?: string | null } | null,
): BillingPricingPayload {
  if (!plan && !eventPrice?.priceCents) {
    return emptyBillingPricing();
  }

  const priceCents = resolveEventPriceCents(
    eventPrice?.priceCents,
    plan?.priceCents,
  );
  const currency = (eventPrice?.currency ?? plan?.currency ?? "usd").toLowerCase();

  return {
    planName: plan?.name ?? null,
    tier: plan?.tier ?? null,
    subscriptionType: plan?.subscriptionType ?? null,
    interval: plan?.interval ?? null,
    price: priceCents != null ? parseFloat((priceCents / 100).toFixed(2)) : null,
    currency,
  };
}

function billingPayloadFromUser(
  user: BillingUserFields,
  pricing: BillingPricingPayload = emptyBillingPricing(),
) {
  const subscriptionStatus = user.subscriptionStatus ?? "inactive";
  const activeEntitlement = user.activeEntitlement ?? null;
  const accessUser = {
    subscriptionStatus,
    activeEntitlement,
    subscriptionExpiresAt: user.subscriptionExpiresAt,
  };

  return {
    subscriptionProvider: user.subscriptionProvider ?? null,
    subscriptionStatus,
    activeEntitlement,
    subscriptionProductId: user.subscriptionProductId ?? null,
    subscriptionStartedAt: user.subscriptionStartedAt ?? null,
    subscriptionExpiresAt: user.subscriptionExpiresAt ?? null,
    hasPremiumAccess: hasPremiumAccess(accessUser),
    hasFamilyAccess: hasFamilyAccess(accessUser),
    ...pricing,
  };
}

async function enrichBillingPayload(userId: string, user: BillingUserFields) {
  const productId = user.subscriptionProductId;
  if (!productId) {
    return billingPayloadFromUser(user);
  }

  const userObjId = new Types.ObjectId(userId);
  const [plan, latestEvent] = await Promise.all([
    SubscriptionPlanModel.findOne({ revenueCatProductId: productId })
      .select(
        "name slug tier subscriptionType interval priceCents currency revenueCatProductId",
      )
      .lean(),
    RevenueCatSubscriptionEventModel.findOne({ userId: userObjId, productId })
      .sort({ purchasedAt: -1, priceCents: -1, processedAt: -1 })
      .select("priceCents currency")
      .lean(),
  ]);

  return billingPayloadFromUser(
    user,
    billingPricingFromPlanAndEvent(plan, latestEvent),
  );
}

function formatPlan(plan: {
  name: string;
  slug: string;
  description?: string;
  priceCents: number;
  currency: string;
  interval: string;
  features?: string[];
  tier?: string;
  subscriptionType?: string;
  monthlyPrice?: number;
  yearlyPrice?: number;
  revenueCatProductId?: string;
  revenueCatEntitlement?: string;
}) {
  return {
    name: plan.name,
    slug: plan.slug,
    description: plan.description ?? "",
    priceCents: plan.priceCents,
    price: parseFloat((plan.priceCents / 100).toFixed(2)),
    currency: (plan.currency ?? "USD").toLowerCase(),
    interval: plan.interval,
    tier: plan.tier ?? null,
    subscriptionType: plan.subscriptionType ?? null,
    monthlyPrice: plan.monthlyPrice ?? null,
    yearlyPrice: plan.yearlyPrice ?? null,
    revenueCatProductId: plan.revenueCatProductId ?? null,
    revenueCatEntitlement: plan.revenueCatEntitlement ?? null,
    features: plan.features ?? [],
  };
}

/**
 * GET /api/v1/billing/plans — public catalog (RevenueCat product ids for mobile SDK).
 */
export const getBillingPlans = async (_req: CustomRequest, res: Response) => {
  try {
    const plans = await SubscriptionPlanModel.find({
      isActive: true,
      revenueCatProductId: { $type: "string", $ne: "" },
    })
      .sort({ tier: 1, interval: 1, priceCents: 1 })
      .lean();

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        plans: plans.map(formatPlan),
        provider: SUBSCRIPTION_PROVIDER_REVENUECAT,
      },
      "Subscription plans",
    );
  } catch (err) {
    ResponseUtil.handleError(res, err);
  }
};

/**
 * GET /api/v1/billing/history — authenticated RevenueCat webhook event history.
 * Newest first by purchasedAt, then processedAt, then createdAt.
 */
export const getBillingHistory = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(res, STATUS_CODES.UNAUTHORIZED, "Unauthorized");
    }

    const page = Math.max(parseInt((req.query.page as string) || "1", 10), 1);
    const limit = Math.min(
      Math.max(parseInt((req.query.limit as string) || "20", 10), 1),
      100,
    );
    const skip = (page - 1) * limit;
    const userObjId = new Types.ObjectId(userId);

    const [events, total] = await Promise.all([
      RevenueCatSubscriptionEventModel.find({ userId: userObjId })
        .sort({ purchasedAt: -1, processedAt: -1, createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .lean(),
      RevenueCatSubscriptionEventModel.countDocuments({ userId: userObjId }),
    ]);

    const productIds = Array.from(
      new Set(
        events
          .map((e) => e.productId)
          .filter((id): id is string => typeof id === "string" && id.length > 0),
      ),
    );

    const plans = productIds.length
      ? await SubscriptionPlanModel.find({
          revenueCatProductId: { $in: productIds },
        })
          .select(
            "name slug tier subscriptionType interval priceCents currency revenueCatProductId revenueCatEntitlement",
          )
          .lean()
      : [];

    const planByProductId = new Map(
      plans.map((p) => [p.revenueCatProductId as string, p]),
    );

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
        events: events.map((e) => formatBillingHistoryEvent(e, planByProductId)),
      },
      "Subscription history",
    );
  } catch (err) {
    ResponseUtil.handleError(res, err);
  }
};

function formatBillingHistoryEvent(
  e: {
    revenueCatEventId: string;
    eventType: string;
    productId?: string | null;
    entitlementIds?: string[];
    activeEntitlement?: string | null;
    subscriptionStatus: string;
    purchasedAt?: Date | null;
    expirationAt?: Date | null;
    store?: string | null;
    environment?: string | null;
    priceCents?: number | null;
    currency?: string | null;
    processedAt: Date;
    createdAt?: Date;
  },
  planByProductId: Map<
    string,
    {
      name: string;
      slug: string;
      tier?: string;
      subscriptionType?: string;
      interval: string;
      priceCents: number;
      currency: string;
      revenueCatProductId?: string;
      revenueCatEntitlement?: string;
    }
  >,
) {
  const plan =
    e.productId && typeof e.productId === "string"
      ? planByProductId.get(e.productId)
      : undefined;

  const priceCents = resolveEventPriceCents(e.priceCents, plan?.priceCents);
  const currency = (e.currency ?? plan?.currency ?? "usd").toLowerCase();

  return {
    id: e.revenueCatEventId,
    eventType: e.eventType,
    productId: e.productId ?? null,
    planName: plan?.name ?? null,
    tier: plan?.tier ?? null,
    subscriptionType: plan?.subscriptionType ?? null,
    interval: plan?.interval ?? null,
    entitlementIds: e.entitlementIds ?? [],
    activeEntitlement: e.activeEntitlement ?? null,
    subscriptionStatus: e.subscriptionStatus,
    price: priceCents != null ? parseFloat((priceCents / 100).toFixed(2)) : null,
    currency,
    purchasedAt: e.purchasedAt ?? null,
    expirationAt: e.expirationAt ?? null,
    store: e.store ?? null,
    environment: e.environment ?? null,
    processedAt: e.processedAt,
    createdAt: e.createdAt ?? null,
  };
}

/**
 * GET /api/v1/billing/me — authenticated subscription state from DB (webhook-synced).
 */
export const getBillingMe = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(res, STATUS_CODES.UNAUTHORIZED, "Unauthorized");
    }

    const user = await UserModel.findById(userId)
      .select(
        "subscriptionProvider subscriptionStatus activeEntitlement subscriptionProductId subscriptionStartedAt subscriptionExpiresAt isDeleted",
      )
      .lean();

    if (!user || user.isDeleted) {
      return ResponseUtil.errorResponse(res, STATUS_CODES.NOT_FOUND, "User not found");
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      await enrichBillingPayload(userId, user),
      "Billing status",
    );
  } catch (err) {
    ResponseUtil.handleError(res, err);
  }
};

/**
 * POST /api/v1/billing/revenuecat/sync
 * Pulls canonical entitlements from RevenueCat API when configured.
 * Webhook remains source of truth.
 */
export const syncRevenueCatBilling = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(res, STATUS_CODES.UNAUTHORIZED, "Unauthorized");
    }

    const user = await UserModel.findById(userId);
    if (!user || user.isDeleted) {
      return ResponseUtil.errorResponse(res, STATUS_CODES.NOT_FOUND, "User not found");
    }

    if (!isRevenueCatApiConfigured()) {
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          ...(await enrichBillingPayload(userId, user)),
          syncPending: true,
          message:
            "RevenueCat API not configured on server. Subscription will update when webhook is received.",
        },
        "Sync pending webhook",
      );
    }

    const appUserId = String(user._id);
    const snapshot = await fetchCustomerEntitlementsFromApi(appUserId);

    if (!snapshot) {
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          ...(await enrichBillingPayload(userId, user)),
          syncPending: true,
          message: "Could not reach RevenueCat API",
        },
        "Sync failed",
      );
    }

    const entitlementIds = snapshot.activeEntitlements;
    const activeEntitlement = selectActiveEntitlement(entitlementIds);

    user.revenueCatUserId = appUserId;
    user.subscriptionProvider = SUBSCRIPTION_PROVIDER_REVENUECAT;

    if (activeEntitlement) {
      user.subscriptionStatus = "active";
      user.activeEntitlement = activeEntitlement;
      if (snapshot.expiresAt && snapshot.expiresAt.getTime() > Date.now()) {
        user.subscriptionExpiresAt = snapshot.expiresAt;
      }
    } else {
      user.subscriptionStatus = "inactive";
      user.activeEntitlement = null;
    }

    user.lastRevenueCatEventAt = new Date();
    await user.save();

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        ...(await enrichBillingPayload(userId, user)),
        syncPending: false,
        syncedFromApi: true,
      },
      "Subscription synced from RevenueCat",
    );
  } catch (err) {
    ResponseUtil.handleError(res, err);
  }
};
