/**
 * Referral progression on a referred user's FIRST qualifying purchase.
 *
 * This is the logic that already lived inline in
 * `POST /referral-tracking/webhook/subscription-purchase`, lifted into a service
 * so the payment flow can run exactly the same steps. Both callers share this
 * one implementation — there is no second copy and no second referral system.
 *
 * What "first purchase" means here is the existing referral lifecycle itself: a
 * referral only progresses while it is still `registered` or `email_verified`.
 * Once it reaches `subscription_purchased` (or `active`), it no longer matches,
 * so a second purchase, a retried callback or a duplicate gateway webhook simply
 * finds nothing to update. That makes the operation naturally idempotent — no
 * extra bookkeeping, and no way to award the same referral twice.
 *
 * A user who was never referred has no matching referral row, so nothing at all
 * happens for them.
 */

import { getModels } from '../models';
import { getPlatformReferralSettings } from './referralSettings';

export interface FirstPurchaseInput {
  /** Email of the buyer — matched against the referral's referredEmail. */
  email: string;
  /** The buyer's user id, recorded on the referral when known. */
  userId?: string;
  subscriptionPlanId?: string;
  subscriptionPlanName?: string;
  subscriptionAmount?: number;
}

/** Ids of the referrals that progressed (empty when there was nothing to do). */
export interface FirstPurchaseResult {
  updated: number;
  referrals: string[];
}

/**
 * Copy a configured default reward onto the referral when it has none yet.
 *
 * The referral rows created by the registration flow carry no reward, so without
 * this the "earned" marking below would have nothing to mark and the referrer
 * would receive no credit. The values come from the platform referral settings
 * (Super Admin → Referral Settings) — the application's existing definition of
 * what a referral is worth.
 */
function seedRewardFromSettings(current: any, configured: any): any | null {
  if (current && current.type) return current;
  if (!configured || !configured.type) return null;
  return {
    type: configured.type,
    value: configured.value ?? 0,
    valueType: configured.valueType || 'fixed',
    status: 'pending',
  };
}

/**
 * Progress every open referral for this buyer to "purchased", marking rewards
 * earned when the platform is configured to auto-approve.
 *
 * Never throws: it runs on a payment path where a verified payment must not be
 * undone by a referral problem. Failures are logged and reported as zero updates.
 */
export async function applyReferralOnFirstPurchase(
  input: FirstPurchaseInput,
): Promise<FirstPurchaseResult> {
  const empty: FirstPurchaseResult = { updated: 0, referrals: [] };
  try {
    if (!input?.email) return empty;

    const { ReferralTracking } = getModels();

    // Only referrals that have not yet had a purchase recorded. This single
    // filter is what makes the whole operation first-purchase-only.
    const referrals = await ReferralTracking.find({
      referredEmail: input.email.toLowerCase(),
      status: { $in: ['registered', 'email_verified'] },
    });
    if (referrals.length === 0) return empty;

    const settings = await getPlatformReferralSettings();
    const autoApprove = settings?.autoApprove ?? true;
    const now = new Date();
    const updated: string[] = [];

    for (const referral of referrals) {
      const r = referral as any;

      r.status = 'subscription_purchased';
      r.subscriptionPurchaseDate = now;
      if (input.userId) r.referredUserId = input.userId;
      if (input.subscriptionPlanId) r.subscriptionPlanId = input.subscriptionPlanId;
      if (input.subscriptionPlanName) r.subscriptionPlanName = input.subscriptionPlanName;
      if (input.subscriptionAmount !== undefined) r.subscriptionAmount = input.subscriptionAmount;

      // Fill in the configured rewards if the referral has none yet, so there is
      // something to mark earned below.
      const referrerReward = seedRewardFromSettings(r.referrerReward, settings?.defaultReferrerReward);
      const refereeReward = seedRewardFromSettings(r.refereeReward, settings?.defaultRefereeReward);
      if (referrerReward) r.referrerReward = referrerReward;
      if (refereeReward) r.refereeReward = refereeReward;

      if (autoApprove) {
        r.status = 'active';
        r.activationDate = now;

        if (r.referrerReward) {
          r.referrerReward.status = 'earned';
          r.referrerReward.earnedAt = now;
        }
        if (r.refereeReward) {
          r.refereeReward.status = 'earned';
          r.refereeReward.earnedAt = now;
        }
        // The module's table reads totalRewardValue for the "Reward Earned"
        // column; keep it in step with what was just earned.
        r.totalRewardValue = (r.referrerReward?.value || 0) + (r.refereeReward?.value || 0);
      }

      await referral.save();
      updated.push(String(r._id));

      if (settings?.notifications?.onSubscriptionPurchase) {
        try {
          const { sendReferralNotificationEmail } = require('./email/systemEmails');
          await sendReferralNotificationEmail({
            to: r.referrerEmail,
            eventType: 'subscription_purchase',
            referredName: r.referredName,
            details: input.subscriptionPlanName || 'subscription',
          });
        } catch (emailErr: any) {
          console.error('[ReferralPurchase] Notification email error:', emailErr.message);
        }
      }

      if (autoApprove && settings?.notifications?.onRewardEarned) {
        try {
          const { sendReferralNotificationEmail } = require('./email/systemEmails');
          await sendReferralNotificationEmail({
            to: r.referrerEmail,
            eventType: 'reward_earned',
            referredName: r.referredName,
          });
        } catch (emailErr: any) {
          console.error('[ReferralPurchase] Reward notification error:', emailErr.message);
        }
      }
    }

    console.log(`[ReferralPurchase] First purchase applied to ${updated.length} referral(s) for ${input.email}`);
    return { updated: updated.length, referrals: updated };
  } catch (error: any) {
    console.error('[ReferralPurchase] Failed to apply referral on purchase:', error?.message);
    return empty;
  }
}

/**
 * Resolve the buyer behind a paid subscription and run the first-purchase
 * referral step for them.
 *
 * Called from the payment flow, which knows the company rather than the person.
 * The referred user is the company's owning user — the account created at
 * registration, which is where the referral relationship was stored.
 */
export async function applyReferralForPaidSubscription(subscription: any): Promise<void> {
  try {
    if (!subscription?.companyId) return;
    const { User } = getModels();

    // The registered account carries the referral relationship. Prefer a user
    // that actually has one; fall back to the first member so the email-based
    // match below still gets a chance.
    const members = await User.find({ companyIds: subscription.companyId })
      .sort({ createdAt: 1 })
      .limit(10)
      .lean();
    if (!members || members.length === 0) return;

    const buyer = members.find((m: any) => m.referredByUserId || m.referredByCode) || members[0];
    if (!buyer?.email) return;

    await applyReferralOnFirstPurchase({
      email: buyer.email,
      userId: String((buyer as any)._id),
      subscriptionPlanId: subscription.packageId ? String(subscription.packageId) : undefined,
      subscriptionPlanName: subscription.packageName || subscription.planName,
      subscriptionAmount: subscription.amount ?? subscription.totalAmount,
    });
  } catch (error: any) {
    console.error('[ReferralPurchase] Failed to resolve buyer for referral:', error?.message);
  }
}
