import Stripe from 'stripe';
import { PlanDao } from "../dao/plan.dao";
import { PaymentDao } from "../dao/payment.dao";
import { LanguageService } from "../services/language.service";
import { UserService } from "../services/user.service";
import { SettingService } from "../services/setting.service";
import { StripeService } from "./stripe.service";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { PAYMENT, PLAN } from "../config/constants";
import Moment from "moment-timezone";
import Axios, { AxiosResponse } from "axios";
import crypto from "crypto";
import Models from "../models";
// import { socketEmit } from "../../plugins/socket";


function extractParts(str: string) {
    return str
        .split(/[-_]/)      // split on '-' or '_'
        .filter(Boolean);   // remove empty parts
}

type RazorpayEventPayload = {
    event?: string;
    type?: string;
    payload?: any;
    created_at?: number;
    contains?: string[];
};

type RazorpayWebhookKind = "subscription" | "one_time";

export class PaymentService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private planDao: PlanDao;
    private paymentDao: PaymentDao;
    private stripeService: StripeService;
    private languageService: LanguageService
    private settingService: SettingService
    // private userService: UserService
    constructor(
        private options: {
            language: string;
            scope: string[] | null;
            accountId: number | null;
            userId: number | null;
            config: userConfig | null;
        } = {
                language: process.env.DEFAULT_LANGUAGE_CODE!,
                scope: null,
                config: null,
                userId: null,
                accountId: null,
            }
    ) {
        this.language = options.language;
        this.scope = options.scope ?? [];
        this.config = options.config ?? null;
        this.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.planDao = new PlanDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.paymentDao = new PaymentDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.languageService = new LanguageService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });

        this.stripeService = new StripeService();
        this.settingService = new SettingService();
    }

    private getRazorpayConfig = () => {
        const keyId = process.env.RAZORPAY_KEY_ID;
        const keySecret = process.env.RAZORPAY_KEY_SECRET;

        if (!keyId || !keySecret) {
            throw new AppError(500, "RAZORPAY_CONFIGURATION_MISSING", {});
        }

        return { keyId, keySecret };
    }

    private razorpayRequest = async (method: "get" | "post" | "patch", path: string, data?: any) => {
        const { keyId, keySecret } = this.getRazorpayConfig();
        const client = Axios.create({
            baseURL: "https://api.razorpay.com/v1",
            auth: { username: keyId, password: keySecret },
            headers: { "Content-Type": "application/json" }
        });

        const response = await client.request({
            method,
            url: path,
            data
        });

        return response.data;
    }

    private getRazorpayEventName = (data: RazorpayEventPayload) => String(data?.event || data?.type || "").toLowerCase();

    private getRazorpayWebhookEntity = (data: RazorpayEventPayload) => {
        const payload = data?.payload || {};
        return payload.payment?.entity || payload.order?.entity || payload.subscription?.entity || payload.invoice?.entity || payload;
    }

    private getRazorpayWebhookNotes = (data: RazorpayEventPayload) => {
        const payload = data?.payload || {};
        const entity = this.getRazorpayWebhookEntity(data);
        const notes = entity?.notes || payload.payment?.entity?.notes || payload.order?.entity?.notes || payload.subscription?.entity?.notes || payload.invoice?.entity?.notes || {};
        if (notes && Object.keys(notes).length > 0) {
            return notes;
        }
        const subNotes = payload.subscription?.entity?.notes || {};
        if (Object.keys(subNotes).length > 0) {
            return subNotes;
        }
        return notes;
    }

    private normalizeRazorpayAmount = (amount: any): number | null => {
        if (amount === null || amount === undefined || amount === "") {
            return null;
        }
        const numericAmount = Number(amount);
        if (Number.isNaN(numericAmount)) {
            return null;
        }
        return Number((numericAmount / 100).toFixed(2));
    }

    private resolveWebhookKind = (eventName: string, notes: Record<string, any>, fallback: RazorpayWebhookKind): RazorpayWebhookKind => {
        if (notes.paymentType === "subscription" || notes.subscriptionId || eventName.startsWith("subscription.") || eventName.startsWith("invoice.")) {
            return "subscription";
        }
        if (notes.paymentType === "one_time") {
            return "one_time";
        }
        return fallback;
    }

    private getSubscriptionPeriodDates = (planData: any, startDate: Date = new Date()) => {
        const duration = Number(planData?.duration ?? planData?.intervalCount ?? 1);
        const frequency = String(planData?.frequency || this.normalizeLegacyFrequency(planData?.interval)).toLowerCase();
        const currentPeriodStart = startDate;
        const currentPeriodEnd = new Date(this.getEndDateWithMoment(currentPeriodStart, frequency, duration));
        return { currentPeriodStart, currentPeriodEnd, duration, frequency };
    }

    private syncUserPremiumStatus = async (userId: number) => {
        const activeSubscription = await this.paymentDao.getActiveSubscriptionByUserId(userId);
        const userService = new UserService({
            userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        await userService.updateUserSetting("isPremium", !!activeSubscription?.id);
        return !!activeSubscription?.id;
    }

    /**
     * Maps a Razorpay subscription status string to the internal DB status integer.
     */
    private mapRazorpayStatusToDb = (razorpayStatus: string): number => {
        switch ((razorpayStatus || '').toLowerCase()) {
            case 'active':
                return PAYMENT.SUBSCRIPTION_STATUS.ACTIVE;
            case 'authenticated':
                return PAYMENT.SUBSCRIPTION_STATUS.AUTHENTICATED;
            case 'halted':
                return PAYMENT.SUBSCRIPTION_STATUS.HALTED;
            case 'paused':
                return PAYMENT.SUBSCRIPTION_STATUS.PAUSED;
            case 'cancelled':
            case 'completed':
            case 'expired':
                return PAYMENT.SUBSCRIPTION_STATUS.CANCELLED;
            case 'pending':
            case 'created':
            default:
                return PAYMENT.SUBSCRIPTION_STATUS.PENDING;
        }
    }

    /**
     * Fetches real-time subscription state (status, currentPeriodStart, currentPeriodEnd)
     * directly from the Razorpay API. Returns null if the external subscription ID is
     * unavailable or the API call fails, so callers can gracefully fall back.
     */
    private fetchRazorpaySubscriptionState = async (externalSubscriptionId: string | null): Promise<{
        status: number;
        currentPeriodStart: Date | null;
        currentPeriodEnd: Date | null;
        razorpayStatus: string;
    } | null> => {
        if (!externalSubscriptionId) return null;
        try {
            const rzpSub = await this.razorpayRequest("get", `/subscriptions/${externalSubscriptionId}`);
            if (!rzpSub) return null;
            return {
                status: this.mapRazorpayStatusToDb(rzpSub.status),
                razorpayStatus: String(rzpSub.status || ''),
                currentPeriodStart: rzpSub.current_start ? new Date(rzpSub.current_start * 1000) : null,
                currentPeriodEnd: rzpSub.current_end ? new Date(rzpSub.current_end * 1000) : null
            };
        } catch (err) {
            console.error('[fetchRazorpaySubscriptionState] Failed to fetch subscription from Razorpay:', externalSubscriptionId, err);
            return null;
        }
    }

    private ensureWebhookSubscription = async (transactionInfo: TransactionInterface, subscriptionInfo: SubscriptionInterface | null, notes: Record<string, any>, eventName: string) => {
        if (subscriptionInfo?.id) {
            return subscriptionInfo;
        }

        const planData = (transactionInfo.planData || notes.planData || {}) as any;
        if (!planData?.planId && !planData?.name) {
            return null;
        }

        const period = this.getSubscriptionPeriodDates(planData, new Date());
        const newSubscription = await this.paymentDao.buySubscription({
            userId: transactionInfo.userId,
            planId: planData.planId ?? notes.planId ?? null,
            priceId: planData.priceId ?? notes.priceId ?? null,
            gateway: process.env.PAYMENT_GATEWAY || "razorpay",
            status: PAYMENT.SUBSCRIPTION_STATUS.PENDING,
            planData,
            currentPeriodStart: period.currentPeriodStart,
            currentPeriodEnd: period.currentPeriodEnd
        } as SubscriptionInterface);

        return newSubscription ? await this.paymentDao.getSubscriptionById(newSubscription.id) : null;
    }

    private upsertWebhookTransaction = async (args: {
        transactionInfo: TransactionInterface | null;
        subscriptionInfo: SubscriptionInterface | null;
        entity: any;
        notes: Record<string, any>;
        eventName: string;
        kind: RazorpayWebhookKind;
    }) => {
        const { transactionInfo, subscriptionInfo, entity, notes, eventName, kind } = args;
        const externalPaymentId = entity?.id && eventName.includes("payment") ? String(entity.id) : notes.paymentId || null;
        const externalOrderId = entity?.order_id || notes.orderId || null;
        const amount = transactionInfo?.amount ?? this.normalizeRazorpayAmount(entity?.amount ?? notes.amount);
        const currency = transactionInfo?.currency ?? String(entity?.currency || notes.currency || "").toUpperCase();
        const paidAt = eventName.includes("captured") || eventName.includes("paid") ? new Date() : null;

        if (transactionInfo?.id) {
            await this.paymentDao.updateTransactionById(transactionInfo.id, {
                externalOrderId: externalOrderId ? String(externalOrderId) : transactionInfo.externalOrderId,
                externalPaymentId: externalPaymentId ? String(externalPaymentId) : transactionInfo.externalPaymentId,
                amount: amount ?? transactionInfo.amount,
                currency: currency || transactionInfo.currency,
                status: eventName.includes("failed") ? PAYMENT.TRANSACTION_STATUS.FAILED : PAYMENT.TRANSACTION_STATUS.ACTIVE,
                paidAt,
                metaData: entity || notes,
                planData: transactionInfo.planData || notes.planData || null
            });
            return transactionInfo.id;
        }

        if (subscriptionInfo?.id || kind === "one_time") {
            const createdTransactionId = await this.paymentDao.createTransaction({
                subscriptionId: subscriptionInfo?.id ?? null,
                userId: transactionInfo?.userId || subscriptionInfo?.userId || this.userId!,
                amount: amount ?? 0,
                currency: currency || "INR",
                status: eventName.includes("failed") ? PAYMENT.TRANSACTION_STATUS.FAILED : PAYMENT.TRANSACTION_STATUS.ACTIVE,
                paidAt,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay",
                metaData: entity || notes,
                planData: transactionInfo?.planData || subscriptionInfo?.planData || notes.planData || null,
                externalOrderId: externalOrderId ? String(externalOrderId) : null,
                externalPaymentId: externalPaymentId ? String(externalPaymentId) : null
            } as TransactionInterface);
            return createdTransactionId;
        }

        return null;
    }

    private buildPlanData = (planInfo: any, priceInfo: any, planId: number, priceId: number) => ({
        planId,
        priceId,
        name: planInfo.name,
        amount: priceInfo.amount,
        currency: priceInfo.currency,
        duration: priceInfo.duration ?? priceInfo.intervalCount ?? 1,
        frequency: priceInfo.frequency ?? this.normalizeLegacyFrequency(priceInfo.interval),
        autoRenew: priceInfo.autoRenew ?? 1,
    })

    private normalizeLegacyFrequency = (interval?: string | null) => {
        switch ((interval || '').toLowerCase()) {
            case 'day':
                return 'days';
            case 'week':
                return 'weeks';
            case 'month':
            case 'half-year':
                return 'months';
            case 'year':
                return 'years';
            default:
                return 'months';
        }
    }

    private getRazorpayOrderAmount = (amount: number) => Math.round(Number(amount) * 100);

    private getEndDateWithMoment = (startDate: Date, planType: string, value: null | number = null) => {
        let date = Moment(startDate); // Initialize moment date
        const amount = value ?? 1;

        switch (planType.toLowerCase()) {
            case "trial":
                date = date.add(amount, "day");
                break;

            case "day":
            case "days":
            case "daily":
                date = date.add(amount, "day");
                break;

            case "month":
            case "months":
            case "monthly":
                date = date.add(amount, "month");
                break;

            case "half-year":
                date = date.add(amount, "months");
                break;

            case "year":
            case "years":
            case "yearly":
                date = date.add(amount, "year");
                break;

            case "week":
            case "weeks":
            case "weekly":
                date = date.add(amount, "week");
                break;

            default:
                throw new Error("Invalid plan type. Use daily, monthly, half-yearly, or yearly.");
        }

        return date.format("YYYY-MM-DD h:m:s"); // Format output
    }

    private calculateRemainingAmount = (startDate: Date | string, endDate: Date | string, totalPrice: number): number => {
        const start = new Date(startDate);
        const end = new Date(endDate);
        const current = new Date();

        // Total plan duration
        const totalDays = Math.max(
            Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)),
            1 // avoid division by zero
        );

        // Days left from today
        const daysLeft = Math.max(
            Math.ceil((end.getTime() - current.getTime()) / (1000 * 60 * 60 * 24)),
            0 // avoid negative
        );

        const perDayPrice = totalPrice / totalDays;
        const remainingAmount = perDayPrice * daysLeft;

        return Number(remainingAmount.toFixed(2)); // return only remaining amount
    };

    // // In app purchase payment verification
    // private verifyIosPayment = async (receiptData: string): Promise<{ isValid: boolean; data?: any }> => {
    //   try {
    //     // Define URLs via Environment Variables (or use default Apple constants)
    //     const APPLE_VERIFY_PROD_URL = process.env.APPLE_VERIFY_PROD_URL || "https://buy.itunes.apple.com/verifyReceipt";
    //     const APPLE_VERIFY_SANDBOX_URL = process.env.APPLE_VERIFY_SANDBOX_URL || "https://sandbox.itunes.apple.com/verifyReceipt";
    //     const APPLE_SHARED_SECRET = process.env.APPLE_SHARED_SECRET;
    //     if (!APPLE_SHARED_SECRET) {
    //       console.error("MISSING ENV: APPLE_SHARED_SECRET is not set.");
    //       return { isValid: false };
    //     }

    //     // 1 Sanitize the receipt data (Fixes Error 21002)
    //     // Sometimes receipts arrive with spaces instead of '+' symbols.
    //     const cleanReceipt = receiptData.replace(/\s/g, '+');

    //     const requestBody = {
    //       "receipt-data": cleanReceipt,
    //       "password": APPLE_SHARED_SECRET,
    //       "exclude-old-transactions": true
    //     };

    //     console.log("Validating Receipt with Apple Production...");

    //     // 2. First, Try Production URL
    //     let response = await Axios.post(APPLE_VERIFY_PROD_URL, requestBody);

    //     // 3. Handle Sandbox Case (Status 21007)
    //     // Status 21007 means: "This receipt is from the Sandbox, but it was sent to Production."
    //     if (response.data.status === 21007) {
    //       console.log("Sandbox Receipt detected (Status 21007). Switching to Sandbox URL...");

    //       response = await Axios.post(APPLE_VERIFY_SANDBOX_URL, requestBody);
    //     }

    //     // 4. Check Final Status
    //     if (response.data.status === 0) {
    //       console.log("iOS Payment Verified Successfully");
    //       // Return the latest receipt info so you can check expiration dates
    //       return { isValid: true, data: response.data };
    //     } else {
    //       console.error(`Validation Failed. Status: ${response.data.status}`);
    //       return { isValid: false, data: response.data };
    //     }

    //   } catch (error) {
    //     console.error("iOS payment verification network error:", error);
    //     return { isValid: false };
    //   }
    // };

    // In app purchase payment verification
    private verifyIosPayment = async (receiptData: string): Promise<{ isValid: boolean; data?: any }> => {
        try {
            const {
                APPLE_VERIFY_PROD_URL = "https://buy.itunes.apple.com/verifyReceipt",
                APPLE_VERIFY_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt",
                APPLE_SHARED_SECRET,
                PAYMENT_MODE,
            } = process.env;

            if (!APPLE_SHARED_SECRET) {
                console.error("Missing APPLE_SHARED_SECRET");
                return { isValid: false };
            }

            if (!receiptData) {
                console.error("Receipt data is empty");
                return { isValid: false };
            }

            if (!PAYMENT_MODE || !["LIVE", "SANDBOX"].includes(PAYMENT_MODE)) {
                console.error("Invalid PAYMENT_MODE. Must be LIVE or SANDBOX.");
                return { isValid: false };
            }

            // Fix 21002 issue (replace spaces with '+')
            const cleanReceipt = receiptData.replace(/\s/g, "+");

            const requestBody = {
                "receipt-data": cleanReceipt,
                password: APPLE_SHARED_SECRET,
                "exclude-old-transactions": true,
            };

            const verifyUrl =
                PAYMENT_MODE === "LIVE"
                    ? APPLE_VERIFY_PROD_URL
                    : APPLE_VERIFY_SANDBOX_URL;

            console.log(`Validating iOS receipt in ${PAYMENT_MODE} mode...`);

            const response: AxiosResponse<any> = await Axios.post(verifyUrl, requestBody);

            if (response.data?.status === 0) {
                console.log("iOS Payment Verified Successfully");
                return { isValid: true, data: response.data };
            }

            console.error(`Apple validation failed. Status: ${response.data?.status}`);
            return { isValid: false, data: response.data };
        } catch (error: any) {
            console.error("iOS payment verification error:", error?.response?.data || error.message);
            return { isValid: false };
        }
    };

    attachPaymentMethod = async (data: PaymentAttachPaymentMethodServiceInput) => {
        try {
            const { paymentMethodId, email } = data;
            let response;
            let customerId = await this.paymentDao.getCustomerByUserId(this.userId!);
            if (!customerId) {
                const stripeCustomer = await this.stripeService.createCustomer(email);
                await this.paymentDao.createCustomer(this.userId!, stripeCustomer);
                customerId = stripeCustomer;
            }

            const attachedMethod = await this.stripeService.attachPaymentMethodToCustomer(paymentMethodId, customerId);
            if (attachedMethod) {
                response = await this.stripeService.listPaymentMethod(customerId);
            }

            return response;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    removePaymentMethod = async (data: PaymentMethodIdentifierServiceInput) => {
        try {
            const { paymentMethodId } = data;
            let response;
            let customerId = await this.paymentDao.getCustomerByUserId(this.userId!);
            if (!customerId) {
                throw new AppError(400, 'INVALID_STRIPR_CUSTOMER', {});
            }

            const removeMethod = await this.stripeService.removePaymentMethodToCustomer(paymentMethodId);
            if (removeMethod) {
                response = await this.stripeService.listPaymentMethod(customerId);
            }

            return response;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    changeDefaultPaymentMethod = async (data: PaymentMethodIdentifierServiceInput) => {
        try {
            const { paymentMethodId } = data;
            let response;
            let customerId = await this.paymentDao.getCustomerByUserId(this.userId!);
            if (!customerId) {
                throw new AppError(400, 'INVALID_STRIPR_CUSTOMER', {});
            }

            const defaultMethod = await this.stripeService.setDefaultPaymentMethod(paymentMethodId, customerId);
            if (defaultMethod) {
                response = await this.stripeService.listPaymentMethod(customerId);
            }

            return response;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    listPaymentMethods = async (_data: object = {}) => {
        try {
            let customerId = await this.paymentDao.getCustomerByUserId(this.userId!);
            if (!customerId) {
                throw new AppError(400, 'INVALID_STRIPR_CUSTOMER', {});
            }

            let response = await this.stripeService.listPaymentMethod(customerId);

            return response;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    buySubscription = async (data: PaymentBuySubscriptionServiceInput) => {
        try {
            const { planId, priceId } = data;

            // --- Step 1: Check for a fully active subscription ---
            const activeSubscription = await this.paymentDao.getActiveSubscriptionByUserId(this.userId!);
            if (activeSubscription?.id) {
                throw new AppError(400, 'ACTIVE_SUBSCRIPTION', {});
            }

            // --- Step 2: Check for a "stuck" PENDING/AUTHENTICATED subscription that already
            //     has a Razorpay subscription ID.  This happens when the webhook hasn't fired
            //     yet (or the DB status was never promoted from AUTHENTICATED → ACTIVE).
            //     We reconcile it by fetching the live Razorpay status and updating the DB
            //     rather than blindly creating a duplicate subscription (which Razorpay rejects
            //     with a 400 if the plan is already active for that customer). ---
            const stuckSubscription = await this.paymentDao.getLatestNonActiveSubscriptionByUserId(this.userId!);
            if (stuckSubscription?.id && stuckSubscription.externalSubscriptionId) {
                const liveState = await this.fetchRazorpaySubscriptionState(stuckSubscription.externalSubscriptionId);
                if (liveState) {
                    const isLiveActive = [
                        PAYMENT.SUBSCRIPTION_STATUS.ACTIVE,
                        PAYMENT.SUBSCRIPTION_STATUS.AUTHENTICATED
                    ].includes(liveState.status) || liveState.razorpayStatus === 'active';

                    if (isLiveActive || liveState.razorpayStatus === 'active') {
                        // Promote the DB subscription to ACTIVE with real dates
                        const updatePayload: any = { status: PAYMENT.SUBSCRIPTION_STATUS.ACTIVE };
                        if (liveState.currentPeriodStart) updatePayload.currentPeriodStart = liveState.currentPeriodStart;
                        if (liveState.currentPeriodEnd) updatePayload.currentPeriodEnd = liveState.currentPeriodEnd;
                        await this.paymentDao.updateSubscriptionById(stuckSubscription.id, updatePayload);
                        await this.syncUserPremiumStatus(this.userId!);
                        // Return the existing subscription link so the client can complete payment
                        throw new AppError(400, 'ACTIVE_SUBSCRIPTION', {});
                    }

                    // Razorpay says cancelled/expired — clean up the DB record and allow a fresh subscription
                    const terminalStatuses = ['cancelled', 'completed', 'expired'];
                    if (terminalStatuses.includes(liveState.razorpayStatus)) {
                        await this.paymentDao.updateSubscriptionById(stuckSubscription.id, {
                            status: PAYMENT.SUBSCRIPTION_STATUS.CANCELLED
                        });
                    }
                }
            }

            const planInfo = await this.planDao.getPlanById(planId);
            if (!planInfo?.id) {
                throw new AppError(400, 'INVALID_PLAN_PROVIDED', { planId: 'INVALID_PLAN_ID' });
            }

            const priceInfo = await this.planDao.getPriceByIdFullObject(priceId);
            if (!priceInfo) {
                throw new AppError(400, 'INVALID_PRICE_PROVIDED', { priceId: 'INVALID_PRICE_ID' });
            }

            // Guard: externalPriceId must exist (it's the Razorpay plan_id).
            // If it is null the price was never synced to Razorpay and we must
            // surface a meaningful error rather than letting Razorpay return 400.
            if (!priceInfo.externalPriceId) {
                throw new AppError(400, 'PRICE_NOT_SYNCED_TO_GATEWAY', {
                    priceId,
                    message: 'This price has no Razorpay plan ID. Please sync the plan to Razorpay first.'
                });
            }

            const planObject = this.buildPlanData(planInfo, priceInfo, planId, priceId);

            const subscriptionObject: SubscriptionInterface = {
                userId: this.userId!,
                planId: planId,
                priceId: priceId,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay",
                status: PAYMENT.SUBSCRIPTION_STATUS.PENDING,
                planData: planObject
            }

            const createdSubscription = await this.paymentDao.buySubscription(subscriptionObject);
            if (!createdSubscription) {
                throw new AppError(400, 'ERROR_CREATING_SUBSCRIPTION', {});
            }

            const transactionObject = {
                userId: this.userId!,
                amount: priceInfo.amount,
                currency: priceInfo.currency,
                planData: planObject,
                status: PAYMENT.STATUS.INACTIVE,
                subscriptionId: createdSubscription.id,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay"
            };
            const transactionId = await this.paymentDao.createTransaction(transactionObject);

            // Razorpay: calculate a sensible total_count for 10 years of billing
            // (Razorpay requires all notes values to be strings)
            const frequency = String(priceInfo.frequency || 'months').toLowerCase();
            const duration = Number(priceInfo.duration ?? 1);
            let totalCount: number;
            if (frequency.startsWith('year')) {
                totalCount = Math.max(1, Math.round(10 / duration));       // e.g. 10 for yearly
            } else if (frequency.startsWith('month')) {
                totalCount = Math.max(1, Math.round(120 / duration));      // e.g. 120 for monthly
            } else if (frequency.startsWith('week')) {
                totalCount = Math.max(1, Math.round(520 / duration));      // e.g. 520 for weekly
            } else {
                totalCount = Math.max(1, Math.round(3650 / duration));     // e.g. 3650 for daily
            }
            totalCount = Math.min(totalCount, 1000); // Razorpay hard-cap

            const razorpaySubPayload = {
                plan_id: priceInfo.externalPriceId,
                total_count: totalCount,
                customer_notify: 1,
                notes: {
                    subscriptionId: String(createdSubscription.id),
                    transactionId: String(transactionId),
                    planId: String(planId),
                    priceId: String(priceId),
                    paymentType: "subscription"
                }
            };

            console.log('[buySubscription] Sending to Razorpay:', JSON.stringify(razorpaySubPayload));

            let subscription: any;
            try {
                subscription = await this.razorpayRequest("post", "/subscriptions", razorpaySubPayload);
                console.log('[buySubscription] Razorpay response:', JSON.stringify(subscription));
            } catch (razorpayErr: any) {
                // Log the Razorpay error body so the exact reason is visible in server logs
                const rzpBody = razorpayErr?.response?.data;
                console.error('[buySubscription] Razorpay API error:', JSON.stringify(rzpBody ?? razorpayErr?.message));
                throw razorpayErr;
            }

            await this.paymentDao.updateTransactionById(transactionId, {
                externalOrderId: subscription.id,
                metaData: subscription
            });

            await this.paymentDao.updateSubscriptionById(createdSubscription.id, {
                externalSubscriptionId: subscription.id
            });

            return {
                paymentIntent: subscription.id,
                paymentUrl: subscription.short_url || null
            };
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    expireSubscription = async (data: PaymentExpireSubscriptionServiceInput) => {
        try {
            const { subscriptionId } = data;
            let subscription: SubscriptionInterface = await this.paymentDao.getSubscriptionObjectById(subscriptionId);
            if (!subscription) {
                throw new AppError(400, 'INVALID_SUBSCRIPTION', {});
            }
            this.paymentDao.cancelSubscription(subscriptionId);
            const userService = new UserService({
                userId: subscription.userId!, accountId: subscription.accountId!, language: "en", scope: [], config: null
            });

            // await userService.updateUserSetting("isPremium", false);
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateSubscription = async (data: PaymentUpdateSubscriptionServiceInput) => {
        try {
            let { planId, priceId, userId = null } = data;
            if (userId === null) { userId = this.userId }
            const activeSubscription = await this.paymentDao.getActiveSubscriptionByUserId(this.userId!);
            if (!activeSubscription?.id) {
                throw new AppError(400, 'NO_ACTIVE_SUBSCRIPTION', {});
            }

            const planInfo = await this.planDao.getPlanById(planId);
            if (!planInfo?.id) {
                throw new AppError(400, 'INVALID_PLAN_PROVIDED', { planId: 'INVALID_PLAN_ID' });
            }

            const priceInfo = await this.planDao.getPriceByIdFullObject(priceId);
            if (!priceInfo) {
                throw new AppError(400, 'INVALID_PRICE_PROVIDED', { priceId: 'INVALID_PRICE_ID' });
            }

            const planObject = this.buildPlanData(planInfo, priceInfo, planId, priceId);

            const subscriptionObject: SubscriptionInterface = {
                userId: this.userId!,
                planId: planId,
                priceId: priceId,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay",
                status: PAYMENT.SUBSCRIPTION_STATUS.PENDING,
                planData: planObject
            }

            const updatedSubscription = await this.paymentDao.updateSubscription(subscriptionObject);
            if (!updatedSubscription) {
                throw new AppError(400, 'ERROR_CREATING_SUBSCRIPTION', {});
            }

            let subscriptionObj: SubscriptionInterface = await this.paymentDao.getSubscriptionById(activeSubscription.id);

            return subscriptionObj;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    cancelSubscription = async (data: PaymentCancelSubscriptionServiceInput) => {
        try {
            const { id } = data;
            const subscription = await this.paymentDao.getSubscriptionObjectById(id);
            if (!subscription?.id) {
                throw new AppError(400, 'INVALID_SUBSCRIPTION_ID', { id: 'INVALID_SUBSCRIPTION_ID' });
            }

            // Cancel in Razorpay if it's a Razorpay subscription
            if (subscription.externalSubscriptionId && subscription.externalSubscriptionId.startsWith('sub_')) {
                try {
                    await this.razorpayRequest("post", `/subscriptions/${subscription.externalSubscriptionId}/cancel`, { cancel_at_cycle_end: 0 });
                } catch (err) {
                    console.error(`Failed to cancel subscription ${subscription.externalSubscriptionId} in Razorpay:`, err);
                }
            }

            const cancelledSubscription = await this.paymentDao.cancelSubscription(id);
            return cancelledSubscription;

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list category types
    getSubscriptionById = async (data: PaymentGetSubscriptionByIdServiceInput): Promise<SubscriptionInterface> => {
        try {
            const { id } = data;
            let subscription: SubscriptionInterface = await this.paymentDao.getSubscriptionById(id);

            return subscription

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getSubscriptions = async (data: PaymentGetSubscriptionsServiceInput): Promise<SubscriptionPaginatedList> => {
        try {
            const { listRequest, forUser } = data;
            const { page, perPage } = listRequest;
            let subscriptions: SubscriptionPaginatedData = await this.paymentDao.getSubscriptionList(listRequest, forUser);
            let totalPages = Common.getTotalPages(subscriptions.count, perPage);
            return {
                data: subscriptions.rows,
                page: page,
                perPage: perPage,
                totalRecords: subscriptions.count,
                totalPages: totalPages
            } as unknown as SubscriptionPaginatedList;

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list category types
    getTransactions = async (data: PaymentGetTransactionsServiceInput): Promise<TranasctionPaginatedList> => {
        try {
            const { listRequest, forUser } = data;
            const { page, perPage } = listRequest;
            let transactions: TransactionPaginatedData = await this.paymentDao.getTransactionList(listRequest, forUser);
            let totalPages = Common.getTotalPages(transactions.count, perPage);
            return {
                data: transactions.rows,
                page: page,
                perPage: perPage,
                totalRecords: transactions.count,
                totalPages: totalPages
            } as unknown as TranasctionPaginatedList;

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }



    addTrialSubscription = async (data: PaymentAddTrialSubscriptionServiceInput) => {
        try {
            const { userId } = data;

            const getSettingByCodeInput: SettingGetByCodeServiceInput = { code: "trial-period" };
            const trialPeriodData = await this.settingService.getByCode(getSettingByCodeInput);
            let days;
            if (trialPeriodData?.value) {
                days = +trialPeriodData.value!;
            } else {
                days = +process.env.PLAN_TRIAL_DAYS!;
            }
            const currentPeriodStart = new Date();
            const currentPeriodEnd = new Date(this.getEndDateWithMoment(currentPeriodStart, "trial", days));

            const planObject = {
                planId: null, priceId: null, name: "Free Trial", description: "Free Trial",
                descriptionText: "Free Trial", amount: 0, currency: "INR",
                interval: 'trial'
            }

            const subscriptionObject: SubscriptionInterface = {
                userId: userId,
                planId: null,
                priceId: null,
                status: PAYMENT.SUBSCRIPTION_STATUS.ACTIVE,
                planData: planObject,
                currentPeriodStart: currentPeriodStart,
                currentPeriodEnd: currentPeriodEnd
            }

            const createdSubscription = await this.paymentDao.buySubscription(subscriptionObject);
            if (!createdSubscription) {
                throw new AppError(400, 'ERROR_CREATING_SUBSCRIPTION', {});
            }

            return true
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    initiatePayment = async (data: PaymentInitiatePaymentServiceInput) => {
        try {
            const { planId, priceId } = data;
            const planInfo = await this.planDao.getPlanById(planId);
            if (!planInfo?.id) {
                throw new AppError(400, 'INVALID_PLAN_PROVIDED', { planId: 'INVALID_PLAN_ID' });
            }

            const priceInfo = await this.planDao.getPriceByIdFullObject(priceId);
            if (!priceInfo) {
                throw new AppError(400, 'INVALID_PRICE_PROVIDED', { priceId: 'INVALID_PRICE_ID' });
            }
            const planObject = this.buildPlanData(planInfo, priceInfo, planId, priceId);
            const userService = new UserService({
                userId: this.userId!, accountId: this.accountId!, language: "en", scope: [], config: null
            });

            let calculateDiscount = await userService.fetchRedeemedPoints();
            if (calculateDiscount > priceInfo.amount) {
                calculateDiscount = priceInfo.amount - 1;
            }

            const transactionObject = {
                userId: this.userId!,
                amount: priceInfo.amount - calculateDiscount,
                currency: priceInfo.currency,
                planData: planObject,
                status: PAYMENT.STATUS.INACTIVE,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay"
            };
            const transactionId = await this.paymentDao.createTransaction(transactionObject);
            const order = await this.razorpayRequest("post", "/orders", {
                amount: this.getRazorpayOrderAmount(priceInfo.amount - calculateDiscount),
                currency: priceInfo.currency.toUpperCase(),
                receipt: `payment_${transactionId}`,
                notes: {
                    transactionId,
                    planId,
                    priceId,
                    paymentType: "one_time"
                }
            });

            await this.paymentDao.updateTransactionById(transactionId, {
                externalOrderId: order.id,
                metaData: order
            });

            return {
                paymentIntent: order.id,
                paymentUrl: order.short_url || null,
                transactionId,
                orderId: order.id,
                amount: priceInfo.amount - calculateDiscount,
                currency: priceInfo.currency,
                keyId: process.env.RAZORPAY_KEY_ID || null
            };
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updatePayment = async (data: PaymentUpdatePaymentServiceInput) => {
        try {
            const { planId, priceId } = data;
            const planInfo = await this.planDao.getPlanById(planId);
            if (!planInfo?.id) {
                throw new AppError(400, 'INVALID_PLAN_PROVIDED', { planId: 'INVALID_PLAN_ID' });
            }

            const priceInfo = await this.planDao.getPriceByIdFullObject(priceId);
            if (!priceInfo) {
                throw new AppError(400, 'INVALID_PRICE_PROVIDED', { priceId: 'INVALID_PRICE_ID' });
            }

            const activeSubscription = await this.paymentDao.getActiveSubscriptionByUserId(this.userId!);
            if (!activeSubscription?.id) {
                throw new AppError(400, 'NO_ACTIVE_SUBSCRIPTION', {});
            }

            // Guard: externalPriceId must exist before calling Razorpay
            if (!priceInfo.externalPriceId) {
                throw new AppError(400, 'PRICE_NOT_SYNCED_TO_GATEWAY', {
                    priceId,
                    message: 'This price has no Razorpay plan ID. Please sync the plan to Razorpay first.'
                });
            }

            if ((activeSubscription.planData as any)?.amount >= priceInfo.amount) {
                throw new AppError(400, 'SELECT_HIGHER_PLAN', {});
            }

            const activePlanData = activeSubscription.planData as RequiredPlanDataInterface;
            const remainingAmount = this.calculateRemainingAmount(activeSubscription.currentPeriodStart, activeSubscription.currentPeriodEnd, activePlanData.amount)

            let amountToBePaid = priceInfo.amount - remainingAmount;

            const planObject = this.buildPlanData(planInfo, priceInfo, planId, priceId);

            const subscriptionObject: SubscriptionInterface = {
                userId: this.userId!,
                planId: planId,
                priceId: priceId,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay",
                status: PAYMENT.SUBSCRIPTION_STATUS.PENDING,
                planData: planObject
            }

            const createdSubscription = await this.paymentDao.buySubscription(subscriptionObject);
            if (!createdSubscription) {
                throw new AppError(400, 'ERROR_CREATING_SUBSCRIPTION', {});
            }

            const transactionObject = {
                userId: this.userId!,
                amount: amountToBePaid,
                currency: priceInfo.currency,
                planData: planObject,
                status: PAYMENT.STATUS.INACTIVE,
                subscriptionId: createdSubscription.id,
                gateway: process.env.PAYMENT_GATEWAY || "razorpay"
            };
            const createdTransaction = await this.paymentDao.createTransaction(transactionObject);

            // Same total_count logic as buySubscription — scale to ~10 years of billing cycles
            const upFrequency = String(priceInfo.frequency || 'months').toLowerCase();
            const upDuration = Number(priceInfo.duration ?? 1);
            let upTotalCount: number;
            if (upFrequency.startsWith('year')) {
                upTotalCount = Math.max(1, Math.round(10 / upDuration));
            } else if (upFrequency.startsWith('month')) {
                upTotalCount = Math.max(1, Math.round(120 / upDuration));
            } else if (upFrequency.startsWith('week')) {
                upTotalCount = Math.max(1, Math.round(520 / upDuration));
            } else {
                upTotalCount = Math.max(1, Math.round(3650 / upDuration));
            }
            upTotalCount = Math.min(upTotalCount, 1000);

            const razorpayUpdatePayload = {
                plan_id: priceInfo.externalPriceId,
                total_count: upTotalCount,
                customer_notify: 1,
                notes: {
                    transactionId: String(createdTransaction),
                    subscriptionId: String(createdSubscription.id),
                    oldSubscriptionId: String(activeSubscription.id),
                    planId: String(planId),
                    priceId: String(priceId),
                    paymentType: "subscription"
                }
            };

            console.log('[updatePayment] Sending to Razorpay:', JSON.stringify(razorpayUpdatePayload));

            let subscription: any;
            try {
                subscription = await this.razorpayRequest("post", "/subscriptions", razorpayUpdatePayload);
                console.log('[updatePayment] Razorpay response:', JSON.stringify(subscription));
            } catch (razorpayErr: any) {
                const rzpBody = razorpayErr?.response?.data;
                console.error('[updatePayment] Razorpay API error:', JSON.stringify(rzpBody ?? razorpayErr?.message));
                throw razorpayErr;
            }

            await this.paymentDao.updateTransactionById(createdTransaction, {
                externalOrderId: subscription.id,
                metaData: subscription
            });

            await this.paymentDao.updateSubscriptionById(createdSubscription.id, {
                externalSubscriptionId: subscription.id
            });

            return {
                paymentIntent: subscription.id,
                paymentUrl: subscription.short_url || null
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    initiateRefund = async (data: PaymentInitiateRefundServiceInput) => {
        try {
            const { transactionId, amount } = data;
            const transactionInfo = await this.paymentDao.getTransactionById(transactionId);
            if (!transactionInfo) {
                throw new AppError(400, 'INVALID_TRANSACTION_PROVIDED', { transactionId: 'INVALID_TRANSACTION_ID' });
            }

            if (amount > transactionInfo.amount) {
                throw new AppError(400, 'INVALID_AMOUNT_PROVIDED', { transactionId: 'INVALID_AMOUNT_PROVIDED' });
            }

            switch (process.env.PAYMENT_GATEWAY) {
                case "stripe":
                    {

                    }
                    break;
                default:
                    break;
            }


            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    fetchStatus = async (data: PaymentFetchStatusServiceInput) => {
        try {
            const { merchantOrderId } = data;
            const order = await this.razorpayRequest("get", `/orders/${merchantOrderId}`);
            return order

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    private processRazorpayWebhook = async (data: RazorpayEventPayload, expectedPaymentType: RazorpayWebhookKind) => {
        const eventName = this.getRazorpayEventName(data);
        const entity = this.getRazorpayWebhookEntity(data);
        const notes = this.getRazorpayWebhookNotes(data);
        const webhookKind = this.resolveWebhookKind(eventName, notes, expectedPaymentType);

        const transactionId = notes.transactionId ? Number(notes.transactionId) : null;
        const subscriptionId = notes.subscriptionId ? Number(notes.subscriptionId) : null;
        const oldSubscriptionId = notes.oldSubscriptionId ? Number(notes.oldSubscriptionId) : null;
        let transactionInfo = transactionId
            ? await this.paymentDao.getTransactionById(transactionId)
            : notes.orderId
                ? await this.paymentDao.getTransactionByExternalOrderId(String(notes.orderId))
                : entity?.order_id
                    ? await this.paymentDao.getTransactionByExternalOrderId(String(entity.order_id))
                    : entity?.id && eventName.includes("payment")
                        ? await this.paymentDao.getTransactionByExternalPaymentId(String(entity.id))
                        : null;

        const externalSubscriptionId = entity?.subscription_id || notes.externalSubscriptionId || data.payload?.subscription?.entity?.id || null;

        if (!transactionInfo && externalSubscriptionId) {
            const subTx = await this.paymentDao.getTransactionByExternalOrderId(String(externalSubscriptionId));
            if (subTx && (subTx.status === 0 || !subTx.externalPaymentId || subTx.externalPaymentId === String(entity?.id))) {
                transactionInfo = subTx;
            }
        }

        const subscriptionInfo = subscriptionId
            ? await this.paymentDao.getSubscriptionObjectById(subscriptionId)
            : notes.externalSubscriptionId
                ? await this.paymentDao.getSubscriptionByExternalSubscriptionId(String(notes.externalSubscriptionId))
                : transactionInfo?.subscriptionId
                    ? await this.paymentDao.getSubscriptionObjectById(Number(transactionInfo.subscriptionId))
                    : entity?.subscription_id
                        ? await this.paymentDao.getSubscriptionByExternalSubscriptionId(String(entity.subscription_id))
                        : null;

        const successEvent = ["payment.captured", "order.paid", "subscription.activated", "subscription.resumed", "subscription.updated", "subscription.charged", "invoice.paid"].includes(eventName);
        const failedEvent = ["payment.failed", "order.failed", "invoice.expired"].includes(eventName);
        const pendingEvent = ["payment.authorized", "subscription.pending", "subscription.authenticated"].includes(eventName);
        const pausedEvent = ["subscription.paused", "subscription.halted"].includes(eventName);
        const cancelCompletedEvent = ["subscription.cancelled", "subscription.completed"].includes(eventName);
        const disputeEvent = eventName.startsWith("payment.dispute.");
        const refundEvent = eventName.startsWith("refund.");
        const downtimeEvent = eventName.startsWith("payment.downtime.");

        let activeSubscription = subscriptionInfo;
        let resolvedTransactionId: number | null = transactionInfo?.id ?? null;

        // --- Fetch live subscription state from Razorpay API ---
        // Prefer entity.subscription_id from the webhook payload, then fall back to DB-resolved subscription's external ID.
        const resolvedExternalSubId: string | null =
            externalSubscriptionId ||
            subscriptionInfo?.externalSubscriptionId ||
            null;
        const liveRazorpayState = await this.fetchRazorpaySubscriptionState(resolvedExternalSubId);

        // --- Transaction & Subscription Upsert Logic for actionable events ---
        if (successEvent || failedEvent || pendingEvent || pausedEvent) {
            if (successEvent) {
                activeSubscription = await this.ensureWebhookSubscription(transactionInfo || ({ userId: notes.userId ? Number(notes.userId) : this.userId!, planData: notes.planData || entity?.notes?.planData || null } as TransactionInterface), subscriptionInfo, notes, eventName);
            }

            if (!transactionInfo?.id && (successEvent || pendingEvent) && activeSubscription?.id) {
                resolvedTransactionId = await this.upsertWebhookTransaction({
                    transactionInfo: null,
                    subscriptionInfo: activeSubscription,
                    entity,
                    notes,
                    eventName,
                    kind: webhookKind
                });
            } else if (transactionInfo?.id) {
                resolvedTransactionId = await this.upsertWebhookTransaction({
                    transactionInfo,
                    subscriptionInfo: activeSubscription,
                    entity,
                    notes,
                    eventName,
                    kind: webhookKind
                });
            }
        }

        // --- Handle Success Events ---
        if (successEvent && activeSubscription?.id) {
            const planData = (activeSubscription.planData as RequiredPlanDataInterface) || (notes.planData as RequiredPlanDataInterface) || {} as RequiredPlanDataInterface;
            const period = this.getSubscriptionPeriodDates(planData, new Date());

            // Priority 1: Use live Razorpay API dates (most accurate)
            if (liveRazorpayState?.currentPeriodStart) {
                period.currentPeriodStart = liveRazorpayState.currentPeriodStart;
            } else if (entity?.current_start) {
                // Priority 2: Fallback to webhook payload dates
                period.currentPeriodStart = new Date(entity.current_start * 1000);
            }

            if (liveRazorpayState?.currentPeriodEnd) {
                period.currentPeriodEnd = liveRazorpayState.currentPeriodEnd;
            } else if (entity?.current_end) {
                period.currentPeriodEnd = new Date(entity.current_end * 1000);
            }

            // Determine the final subscription status:
            // - If the event is a cancellation, force CANCELLED.
            // - Otherwise prefer the live Razorpay status (so AUTHENTICATED -> ACTIVE when Razorpay says active).
            // - Fall back to ACTIVE as a safe default for all other success events.
            let subscriptionStatus: number;
            if (eventName.startsWith("subscription.") && eventName.includes("cancel")) {
                subscriptionStatus = PAYMENT.SUBSCRIPTION_STATUS.CANCELLED;
            } else if (liveRazorpayState) {
                // Trust what Razorpay reports; if it says 'active' we map to ACTIVE even
                // if the webhook event itself was 'subscription.authenticated'.
                subscriptionStatus = liveRazorpayState.status === PAYMENT.SUBSCRIPTION_STATUS.AUTHENTICATED
                    ? PAYMENT.SUBSCRIPTION_STATUS.ACTIVE  // authenticated + success event => treat as active
                    : liveRazorpayState.status;
            } else {
                subscriptionStatus = PAYMENT.SUBSCRIPTION_STATUS.ACTIVE;
            }

            await this.paymentDao.activateSubscription(
                {
                    subscriptionId: activeSubscription.id,
                    status: subscriptionStatus,
                    currentPeriodStart: period.currentPeriodStart,
                    currentPeriodEnd: period.currentPeriodEnd
                },
                {
                    transactionId: resolvedTransactionId ?? undefined,
                    status: PAYMENT.TRANSACTION_STATUS.ACTIVE,
                    metaData: data,
                    amount: transactionInfo?.amount ?? this.normalizeRazorpayAmount(entity?.amount ?? notes.amount) ?? 0
                },
                oldSubscriptionId
            );

            if (subscriptionStatus === PAYMENT.SUBSCRIPTION_STATUS.CANCELLED) {
                await this.paymentDao.updateSubscriptionById(activeSubscription.id, {
                    status: PAYMENT.SUBSCRIPTION_STATUS.CANCELLED
                });
            }

            const subscription: SubscriptionInterface = await this.paymentDao.getSubscriptionById(activeSubscription.id);
            const userId = subscription.userId || transactionInfo?.userId || Number(notes.userId);
            const accountId = subscription.accountId ?? this.accountId;
            const userService = new UserService({
                userId: userId!,
                accountId: accountId ?? null,
                language: this.language,
                scope: this.scope,
                config: this.config
            });

            const room = `user_${accountId}_${userId}`;
            await Common.EmitEvent(room, "subscriptionPurchased", subscription);
            await userService.updateUserSetting("isPremium", true);
            await userService.updateReferralPoints(userId!);
            if (webhookKind === "subscription") {
                await userService.updateRedeemedPoints(userId!, Math.min(Number(planData.amount || 0), +process.env.REDEEM_POINTS!));
            }
            if (subscriptionStatus === PAYMENT.SUBSCRIPTION_STATUS.CANCELLED) {
                //await userService.updateUserSetting("isPremium", false);
            }
        }

        // --- Handle Failed Events ---
        if (failedEvent) {
            if (transactionInfo?.id) {
                await this.paymentDao.updateTransactionById(transactionInfo.id, {
                    status: PAYMENT.TRANSACTION_STATUS.FAILED,
                    externalPaymentId: entity?.id ? String(entity.id) : transactionInfo.externalPaymentId || null,
                    metaData: data
                });
            }

            if (activeSubscription?.id) {
                await this.paymentDao.updateSubscriptionById(activeSubscription.id, {
                    status: PAYMENT.SUBSCRIPTION_STATUS.FAILED
                });
            }

            const failedUserId = activeSubscription?.userId || transactionInfo?.userId || Number(notes.userId);
            if (failedUserId) {
                await this.syncUserPremiumStatus(failedUserId);
            }
        }

        // --- Handle Pending Events ---
        if (pendingEvent) {
            if (transactionInfo?.id) {
                await this.paymentDao.updateTransactionById(transactionInfo.id, {
                    status: PAYMENT.TRANSACTION_STATUS.INACTIVE,
                    metaData: data
                });
            }
            if (activeSubscription?.id) {
                // For pending events prefer the live Razorpay status so we don't
                // accidentally downgrade an already-active subscription back to pending.
                const subStatus = liveRazorpayState
                    ? liveRazorpayState.status
                    : (eventName === "subscription.authenticated"
                        ? PAYMENT.SUBSCRIPTION_STATUS.AUTHENTICATED
                        : PAYMENT.SUBSCRIPTION_STATUS.PENDING);
                await this.paymentDao.updateSubscriptionById(activeSubscription.id, {
                    status: subStatus
                });
            }
        }

        // --- Handle Paused Events ---
        if (pausedEvent && activeSubscription?.id) {
            const subStatus = eventName === "subscription.halted"
                ? PAYMENT.SUBSCRIPTION_STATUS.HALTED
                : PAYMENT.SUBSCRIPTION_STATUS.PAUSED;
            await this.paymentDao.updateSubscriptionById(activeSubscription.id, {
                status: subStatus
            });
            await this.syncUserPremiumStatus(activeSubscription.userId);
        }

        // --- Handle Cancel/Completed Events ---
        if (cancelCompletedEvent && activeSubscription?.id) {
            await this.paymentDao.updateSubscriptionById(activeSubscription.id, {
                status: PAYMENT.SUBSCRIPTION_STATUS.CANCELLED
            });
        }

        // --- Handle Dispute Events ---
        if (disputeEvent && transactionInfo?.id) {
            await this.paymentDao.updateTransactionById(transactionInfo.id, {
                status: PAYMENT.TRANSACTION_STATUS.DISPUTED,
                metaData: data
            });
        }

        // --- Handle Refund Events ---
        if (refundEvent && transactionInfo?.id) {
            const txStatus = eventName === "refund.processed"
                ? PAYMENT.TRANSACTION_STATUS.REFUNDED
                : transactionInfo.status;

            await this.paymentDao.updateTransactionById(transactionInfo.id, {
                status: txStatus,
                metaData: data
            });

            if (eventName === "refund.processed" && activeSubscription?.userId) {
                await this.syncUserPremiumStatus(activeSubscription.userId);
            }
        }

        if (successEvent && activeSubscription?.userId) {
            await this.syncUserPremiumStatus(activeSubscription.userId);
        }

        return true;
    }



    subscriptionWebhook = async (input: PaymentWebhookServiceInput): Promise<any> => {
        try {
            const { data } = input;
            await this.paymentDao.stripeWebhook(data, "subscription");
            await this.processRazorpayWebhook(data as RazorpayEventPayload, "subscription");
            return true;
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    paymentWebhook = async (input: PaymentWebhookServiceInput): Promise<any> => {
        try {
            const { data } = input;
            await this.paymentDao.stripeWebhook(data, "payment");
            await this.processRazorpayWebhook(data as RazorpayEventPayload, "one_time");
            return true;
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };
    // paymentWebhook = async (data: any): Promise<any> => {
    //     try {
    //         const webhook = await this.paymentDao.stripeWebhook(data, "subscription");
    //         const event = data.event;
    //         const payload = data.payload;
    //         const subscriptionId = payload.metaInfo.subscriptionId;
    //         const transactionId = payload.metaInfo.transactionId;
    //         const oldSubscriptionId = payload.metaInfo.oldSubscriptionId;
    //         const metaData = payload;
    //         const externalStatus = payload.state;
    //         const transactionAmount = Number(payload.amount / 100) ?? 0;
    //         const currentDate = new Date();

    //         const subscriptionInfo: GiftCardInterface =
    //             await this.paymentDao.getSubscriptionObjectById(subscriptionId);

    //         if (!subscriptionInfo?.id) return webhook;

    //         switch (event) {
    //             case 'checkout.order.completed':
    //                 {

    //                     const interval = (subscriptionInfo.planData as any).interval;

    //                     const subscriptionStatus = PAYMENT.SUBSCRIPTION_STATUS.ACTIVE;
    //                     const transactionStatus = PAYMENT.STATUS.ACTIVE;
    //                     const currentPeriodStart = currentDate;
    //                     const currentPeriodEnd = new Date(this.getEndDateWithMoment(currentDate, interval))

    //                     const subscriptionObject = {
    //                         status: subscriptionStatus,
    //                         currentPeriodStart,
    //                         currentPeriodEnd,
    //                         subscriptionId
    //                     }

    //                     const transactionObject = {
    //                         status: transactionStatus, 
    //                         metaData: metaData, 
    //                         externalStatus, 
    //                         transactionAmount,
    //                         transactionId
    //                     }

    //                     await this.paymentDao.activateSubscription(subscriptionObject, transactionObject, oldSubscriptionId);

    //                     {
    //                         const subscription: SubscriptionInterface = await this.paymentDao.getSubscriptionById(subscriptionId);

    //                         const userService = new UserService({ 
    //                             userId: subscriptionInfo.userId!, accountId: subscriptionInfo.accountId!, language: "en", scope: [], config: null 
    //                         });

    //                         // socketEmit(
    //                         //     "subscriptionPurchased", subscriptionInfo.accountId!,
    //                         //     [subscriptionInfo.userId!], subscription
    //                         // );
    //                         const room = `user_${subscriptionInfo.accountId}_${subscriptionInfo.userId}`;
    //                         await Common.EmitEvent(room,'subscriptionPurchased',subscription)

    //                         await userService.updateReferralPoints(subscriptionInfo.userId!);
    //                         await userService.updateUserSetting("isPremium", true);
    //                     }
    //                 }
    //                 break;

    //             case 'checkout.order.failed':
    //                 {
    //                     const subscriptionStatus = PAYMENT.SUBSCRIPTION_STATUS.FAILED;
    //                     const transactionStatus = PAYMENT.TRANSACTION_STATUS.FAILED;

    //                     const subscriptionObject = {
    //                         status: subscriptionStatus,
    //                         subscriptionId
    //                     }

    //                     const transactionObject = {
    //                         status: transactionStatus, 
    //                         metaData: metaData, 
    //                         externalStatus, 
    //                         transactionId
    //                     }

    //                     await this.paymentDao.activateSubscription(subscriptionObject, transactionObject);
    //                 }
    //                 break;

    //             default:
    //                 break;
    //         }
    //         return webhook;
    //     } catch (err) {
    //         if (err instanceof AppError) throw err;
    //         throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
    //     }
    // };

    // Create PaymentIntent
    createWalletIntent = async (data: PaymentCreateWalletIntentServiceInput): Promise<ClientSecret> => {
        try {
            const { amount, currency } = data;
            return await this.stripeService.createWalletIntent(amount, currency);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    revenueStats = async (data: PaymentRevenueStatsServiceInput) => {
        try {
            const { startDate, endDate } = data;
            const today = Moment().endOf("day");
            let start, end;

            if (!startDate && !endDate) {
                // Case 1: nothing passed
                end = today;
                start = Moment(end).subtract(30, "days").startOf("day");
            } else if (startDate && !endDate) {
                // Case 2: only startDate given
                start = Moment(startDate).startOf("day");
                const potentialEnd = Moment(start).add(30, "days").endOf("day");
                end = potentialEnd.isAfter(today) ? today : potentialEnd;
            } else if (!startDate && endDate) {
                // Case 3: only endDate given
                end = Moment(endDate).endOf("day");
                start = Moment(end).subtract(30, "days").startOf("day");
            } else {
                // Case 4: both passed
                start = Moment(startDate).startOf("day");
                end = Moment(endDate).endOf("day");
                const diffDays = end.diff(start, "days");
                if (diffDays > 30) {
                    throw new AppError(400, "DATE_RANGE_TOO_LARGE", {
                        message: "Date range cannot exceed 30 days"
                    });
                }
            }

            start = start.format("YYYY-MM-DD");
            end = end.format("YYYY-MM-DD");

            return await this.paymentDao.revenueStats(start, end);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    subscriptionStats = async (data: PaymentSubscriptionStatsServiceInput) => {
        try {
            const { startDate, endDate } = data;
            const today = Moment().endOf("day");
            let start, end;

            if (!startDate && !endDate) {
                // Case 1: nothing passed
                end = today;
                start = Moment(end).subtract(30, "days").startOf("day");
            } else if (startDate && !endDate) {
                // Case 2: only startDate given
                start = Moment(startDate).startOf("day");
                const potentialEnd = Moment(start).add(30, "days").endOf("day");
                end = potentialEnd.isAfter(today) ? today : potentialEnd;
            } else if (!startDate && endDate) {
                // Case 3: only endDate given
                end = Moment(endDate).endOf("day");
                start = Moment(end).subtract(30, "days").startOf("day");
            } else {
                // Case 4: both passed
                start = Moment(startDate).startOf("day");
                end = Moment(endDate).endOf("day");
                // const diffDays = end.diff(start, "days");
                // if (diffDays > 30) {
                // throw new AppError(400, "DATE_RANGE_TOO_LARGE", {
                //     message: "Date range cannot exceed 30 days"
                // });
                // }
            }

            const diffInDays = end.diff(start, "days");
            let interval;
            if (diffInDays >= 0 && diffInDays <= 30) {
                interval = 'day';
            } else if (diffInDays >= 31 && diffInDays <= 60) {
                interval = 'week';
            } else if (diffInDays >= 61 && diffInDays <= 365) {
                interval = 'month';
            } else {
                interval = 'year';
            }

            start = start.format("YYYY-MM-DD");
            end = end.format("YYYY-MM-DD");

            return await this.paymentDao.subscriptionStats(start, end, interval);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    subscriptionUserStats = async (data: PaymentSubscriptionUserStatsServiceInput) => {
        try {
            const { startDate, endDate } = data;
            const today = Moment().endOf("day");
            let start, end;

            if (!startDate && !endDate) {
                // Case 1: nothing passed
                end = today;
                start = Moment(end).subtract(30, "days").startOf("day");
            } else if (startDate && !endDate) {
                // Case 2: only startDate given
                start = Moment(startDate).startOf("day");
                const potentialEnd = Moment(start).add(30, "days").endOf("day");
                end = potentialEnd.isAfter(today) ? today : potentialEnd;
            } else if (!startDate && endDate) {
                // Case 3: only endDate given
                end = Moment(endDate).endOf("day");
                start = Moment(end).subtract(30, "days").startOf("day");
            } else {
                // Case 4: both passed
                start = Moment(startDate).startOf("day");
                end = Moment(endDate).endOf("day");
                // const diffDays = end.diff(start, "days");
                // if (diffDays > 30) {
                // throw new AppError(400, "DATE_RANGE_TOO_LARGE", {
                //     message: "Date range cannot exceed 30 days"
                // });
                // }
            }

            const diffInDays = end.diff(start, "days");
            let interval;
            if (diffInDays >= 0 && diffInDays <= 30) {
                interval = 'day';
            } else if (diffInDays >= 31 && diffInDays <= 60) {
                interval = 'week';
            } else if (diffInDays >= 61 && diffInDays <= 365) {
                interval = 'month';
            } else {
                interval = 'year';
            }

            start = start.format("YYYY-MM-DD");
            end = end.format("YYYY-MM-DD");

            return await this.paymentDao.subscriptionUserStats(start, end, interval);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    updateEndedSubscriptions = async (_data: PaymentUpdateEndedSubscriptionsServiceInput = {}) => {
        return this.paymentDao.updateEndedSubscriptions();
    }

    appPurchase = async (data: PaymentAppPurchaseServiceInput) => {
        try {
            const { planId, priceId, purchaseData } = data;
            const planInfo = await this.planDao.getPlanById(planId);
            if (!planInfo?.id) {
                throw new AppError(400, 'INVALID_PLAN_PROVIDED', { planId: 'INVALID_PLAN_ID' });
            }

            const priceInfo = await this.planDao.getPriceByIdFullObject(priceId);
            if (!priceInfo) {
                throw new AppError(400, 'INVALID_PRICE_PROVIDED', { priceId: 'INVALID_PRICE_ID' });
            }

            const activeSubscription = await this.paymentDao.getActiveSubscriptionByUserId(this.userId!);
            if (activeSubscription?.id) {
                // if(activeSubscription?.priceId) {
                //     throw new AppError(400, 'ACTIVE_SUBSCRIPTION', {});
                // }
                await this.paymentDao.cancelSubscription(activeSubscription?.id)
            }
            const planObject = {
                planId: planId, priceId: priceId, name: planInfo.name, description: planInfo.description,
                descriptionText: planInfo.descriptionText, amount: priceInfo.amount, currency: priceInfo.currency,
                duration: priceInfo.duration, frequency: priceInfo.frequency, autoRenew: priceInfo.autoRenew ?? 1
            }

            // Start and End date for subscription
            const currentDate = new Date();
            const frequency = priceInfo.frequency as string;
            const duration = priceInfo.duration as number;
            if (!frequency || !duration) {
                throw new AppError(400, 'PLAN_DURATION_FREQUENCY_REQUIRED', {});
            }
            const currentPeriodStart = currentDate;
            const currentPeriodEnd = new Date(this.getEndDateWithMoment(currentDate, frequency, duration))

            // const now = Moment();
            // const periodEnd = Moment(activeSubscription.currentPeriodEnd);
            // const remainingDays = Math.max(periodEnd.startOf("day").diff(now.startOf("day"), "days"),0);
            // console.log("Days left:", remainingDays);
            // const updatedCurrentPeriodEnd = Moment(currentPeriodEnd).add(remainingDays, "days").toDate();

            let remainingDays = 0;
            if (activeSubscription?.currentPeriodEnd) {
                const now = Moment().startOf("day");
                const periodEnd = Moment(activeSubscription.currentPeriodEnd).startOf("day");
                const effectivePeriodEnd = periodEnd.isBefore(now) ? now : periodEnd;
                remainingDays = Math.max(effectivePeriodEnd.diff(now, "days"), 0);
            }
            console.log("Days left:", remainingDays);
            const updatedCurrentPeriodEnd = Moment(currentPeriodEnd).add(remainingDays, "days").toDate();


            const subscriptionObject: SubscriptionInterface = {
                userId: this.userId!,
                planId: planId,
                priceId: priceId,
                gateway: PLAN.PAYMENT_GATEWAY.APPLE_IN_APP,
                status: PAYMENT.SUBSCRIPTION_STATUS.ACTIVE,
                planData: planObject,
                currentPeriodStart: currentPeriodStart,
                currentPeriodEnd: updatedCurrentPeriodEnd
            }

            const userService = new UserService({
                userId: this.userId!, accountId: this.accountId!, language: "en", scope: [], config: null
            });

            let calculateDiscount = await userService.fetchRedeemedPoints();
            if (calculateDiscount > priceInfo?.amount!) {
                calculateDiscount = priceInfo?.amount! - 1;
            }

            const createdSubscription = await this.paymentDao.buySubscription(subscriptionObject);
            if (!createdSubscription) {
                throw new AppError(400, 'ERROR_CREATING_SUBSCRIPTION', {});
            }

            const transactionObject = {
                userId: this.userId!,
                amount: priceInfo?.amount! - calculateDiscount,
                transactionAmount: priceInfo?.amount! - calculateDiscount,
                metaData: purchaseData,
                externalStatus: purchaseData.purchaseState,
                gateway: PLAN.PAYMENT_GATEWAY.APPLE_IN_APP,
                currency: priceInfo.currency,
                planData: planObject,
                status: PAYMENT.TRANSACTION_STATUS.ACTIVE,
                externalTransactionId: purchaseData.id || purchaseData.transactionId,
                subscriptionId: createdSubscription.id,
                discount: calculateDiscount
            };
            const createdTransaction = await this.paymentDao.createTransaction(transactionObject);

            const metaData = {
                transactionId: createdTransaction,
                subscriptionId: createdSubscription.id
            };
            // await this.paymentDao.subscriptionStatus({
            //     subscriptionId: createdSubscription?.id!,
            //     currentPeriodStart: currentPeriodStart,
            //     currentPeriodEnd: currentPeriodEnd, transactionAmount: transactionObject?.amount, subscriptionStatus: PAYMENT.SUBSCRIPTION_STATUS.ACTIVE, transactionStatus: PAYMENT.STATUS.ACTIVE,
            //     oldSubscriptionId: activeSubscription?.id
            // });

            // const newActiveSubscription = await this.paymentDao.getActiveSubscriptionByUserId(this.userId!);
            const subscription: SubscriptionInterface = await this.paymentDao.getSubscriptionById(createdSubscription.id);
            const room = `user_${subscription.accountId}_${subscription.userId}`;
            await Common.EmitEvent(room, 'subscriptionPurchased', subscription)
            await userService.updateUserSetting("isPremium", true);
            await userService.updateReferralPoints(this.userId!);
            const points = await userService.updateRedeemedPoints(
                this.userId!, Math.min(planObject?.amount!, +process.env.REDEEM_POINTS!)
            );
            // const externalSubscriptionId = subscription.externalSubscriptionId;
            // if (points > 0 && externalSubscriptionId) {
            //     await this.stripeService.updateSubscription(externalSubscriptionId, {points: points });
            //     // await this.stripeService.updateSubscriptionWithCoupon( externalSubscriptionId, points );
            // }
            return metaData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    };

    verifyAppPurchase = async (data: PaymentVerifyAppPurchaseServiceInput) => {
        try {
            const { platform, planId, priceId, receipt, purchaseData } = data;
            const normalizedPlatform = platform.toUpperCase();
            switch (normalizedPlatform) {
                case "ANDROID": {
                    if (!receipt || !planId || !priceId) {
                        throw new Error("Missing request fields for Android");
                    }
                    return { success: false, platform: "ANDROID", data: {} };
                }

                case "IOS": {
                    if (!receipt || !planId || !priceId || !purchaseData?.transactionId) {
                        throw new Error("Missing required fields: receipt, planId, priceId, or transactionId");
                    }
                    const iosResult = await this.verifyIosPayment(receipt);
                    if (!iosResult.isValid || !iosResult.data) {
                        throw new Error("iOS payment verification failed with Apple");
                    }
                    const allTransactions = iosResult.data.latest_receipt_info || iosResult.data.receipt?.in_app || [];
                    const matchedTransaction = allTransactions.find((t: any) =>
                        t.transaction_id === purchaseData.transactionId
                    );
                    if (!matchedTransaction) {
                        console.error(`Transaction ID ${purchaseData.transactionId} not found in receipt transactions.`);
                        throw new Error("Payment verified, but specific transaction not found in receipt");
                    }
                    if (matchedTransaction.product_id !== purchaseData.productId) {
                        throw new Error(`Product ID mismatch. Expected ${purchaseData.productId}, got ${matchedTransaction.product_id}`);
                    }
                    const appPurchaseInput: PaymentAppPurchaseServiceInput = { planId, priceId, purchaseData };
                    await this.appPurchase(appPurchaseInput);
                    return { success: true, platform: "IOS", data: iosResult.data };
                }
                default:
                    throw new Error(`Unsupported platform: ${platform}`);
            }
        } catch (err) {
            console.error("verifyAppPurchase Error:", err);
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err instanceof Error ? err.message : err);
        }
    }

    verifyPayment = async (data: { razorpayPaymentId: string, razorpayOrderId?: string, razorpaySubscriptionId?: string, razorpaySignature: string }) => {
        try {
            const { razorpayPaymentId, razorpayOrderId, razorpaySubscriptionId, razorpaySignature } = data;

            // Signature format varies based on whether it's a one-time order or a subscription
            if (!razorpayOrderId && !razorpaySubscriptionId) {
                throw new AppError(400, 'INVALID_VERIFICATION_DATA', { message: 'Either razorpayOrderId or razorpaySubscriptionId must be provided' });
            }

            const payloadToSign = razorpaySubscriptionId
                ? razorpayPaymentId + "|" + razorpaySubscriptionId
                : razorpayOrderId + "|" + razorpayPaymentId;

            const expectedSignature = crypto.createHmac('sha256', process.env.RAZORPAY_KEY_SECRET!)
                .update(payloadToSign)
                .digest('hex');

            if (expectedSignature !== razorpaySignature) {
                throw new AppError(400, 'PAYMENT_VERIFICATION_FAILED', { message: 'Signature mismatch' });
            }

            let subId: number | null = null;
            let userIdForSub: number | null = null;
            // Track the Razorpay-side external subscription ID for live API calls
            let resolvedExternalSubId: string | null = razorpaySubscriptionId || null;

            if (razorpaySubscriptionId) {
                const subscription = await this.paymentDao.getSubscriptionByExternalSubscriptionId(razorpaySubscriptionId);
                if (subscription) {
                    subId = subscription.id;
                    userIdForSub = subscription.userId;
                }
            }

            if (!subId && razorpayOrderId && razorpayOrderId.startsWith('sub_')) {
                const subscription = await this.paymentDao.getSubscriptionByExternalSubscriptionId(razorpayOrderId);
                if (subscription) {
                    subId = subscription.id;
                    userIdForSub = subscription.userId;
                    if (!resolvedExternalSubId) resolvedExternalSubId = razorpayOrderId;
                }
            }

            if (!subId && razorpayPaymentId) {
                const transaction = await this.paymentDao.getTransactionByExternalPaymentId(razorpayPaymentId);
                if (transaction && transaction.subscriptionId) {
                    subId = transaction.subscriptionId;
                    userIdForSub = transaction.userId;
                    // Try to get the external subscription ID from the DB record
                    if (!resolvedExternalSubId) {
                        const sub = await this.paymentDao.getSubscriptionByExternalSubscriptionId(
                            transaction.externalOrderId || ''
                        );
                        if (sub?.externalSubscriptionId) resolvedExternalSubId = sub.externalSubscriptionId;
                    }
                }
            }

            if (!subId && razorpayOrderId) {
                const transaction = await this.paymentDao.getTransactionByExternalOrderId(razorpayOrderId);
                if (transaction && transaction.subscriptionId) {
                    subId = transaction.subscriptionId;
                    userIdForSub = transaction.userId;
                }
            }

            if (subId && userIdForSub) {
                // --- Fetch live subscription state from Razorpay ---
                // This ensures currentPeriodStart/End and status are always up-to-date
                // immediately after the user completes payment on the client, without
                // waiting for the webhook to arrive.
                const liveState = await this.fetchRazorpaySubscriptionState(resolvedExternalSubId);

                const updateData: any = {
                    // Default to ACTIVE; live state overrides if Razorpay says something different
                    status: liveState ? liveState.status : PAYMENT.SUBSCRIPTION_STATUS.ACTIVE
                };

                if (liveState?.currentPeriodStart) {
                    updateData.currentPeriodStart = liveState.currentPeriodStart;
                }
                if (liveState?.currentPeriodEnd) {
                    updateData.currentPeriodEnd = liveState.currentPeriodEnd;
                }

                await this.paymentDao.updateSubscriptionById(subId, updateData);

                // Update associated transaction status to active
                let transaction: any = null;
                if (razorpayPaymentId) {
                    transaction = await this.paymentDao.getTransactionByExternalPaymentId(razorpayPaymentId);
                }
                if (!transaction && razorpayOrderId) {
                    transaction = await this.paymentDao.getTransactionByExternalOrderId(razorpayOrderId);
                }
                if (!transaction && subId) {
                    transaction = await Models.Transaction.findOne({
                        where: { subscriptionId: subId },
                        order: [['id', 'DESC']]
                    });
                }
                if (transaction) {
                    await this.paymentDao.updateTransactionById(transaction.id, {
                        status: PAYMENT.TRANSACTION_STATUS.ACTIVE,
                        externalPaymentId: razorpayPaymentId ? String(razorpayPaymentId) : transaction.externalPaymentId,
                        externalOrderId: razorpayOrderId ? String(razorpayOrderId) : transaction.externalOrderId,
                        paidAt: new Date(),
                        metaData: { ...(transaction.metaData || {}), ...data }
                    });
                }

                // Immediately sync the user's premium flag so they get access
                // right after payment (before the webhook arrives).
                await this.syncUserPremiumStatus(userIdForSub);
            }

            return { isValid: true };
        } catch (err) {
            console.error("verifyPayment Error:", err);
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err instanceof Error ? err.message : String(err));
        }
    }
}
