import mongoose from 'mongoose';
import Stripe from 'stripe';
import Payment from '../models/Payment';
import Plan from '../models/Plan';
import Subscription from '../models/Subscription';
import User from '../models/User';
import EmailService from './EmailService';

class PaymentService {
    private _stripe: Stripe | null = null;

    private get stripe(): Stripe {
        if (!this._stripe) {
            const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
            if (!stripeSecretKey) {
                throw new Error('STRIPE_SECRET_KEY is missing in environment variables');
            }
            this._stripe = new Stripe(stripeSecretKey, {
                apiVersion: '2023-10-16' as any,
            });
        }
        return this._stripe;
    }

    constructor() {
        // Stripe is now lazy-loaded to prevent boot-time crashes
    }

    /**
     * Create a Stripe Checkout Session with plan validation (wrapper)
     */
    async createCheckoutWithPlan(params: {
        userId: string;
        planId: string;
        currency?: string;
    }) {
        const { userId, planId, currency = 'usd' } = params;

        try {
            // Validate plan exists
            const plan = await Plan.findById(planId);
            if (!plan) {
                throw new Error('Plan not found');
            }

            // Create the checkout session
            const result = await this.createCheckoutSession({
                userId,
                productName: plan.title,
                amount: plan.price,
                currency,
                metadata: { planId }
            });

            return {
                url: result.url,
                planId,
                planTitle: plan.title,
                amount: plan.price
            };
        } catch (error: any) {
            console.error('Error creating checkout session:', error);
            throw new Error(error.message || 'Failed to create checkout session');
        }
    }

    /**
     * Create a Stripe Checkout Session
     */
    async createCheckoutSession(params: {
        userId: string;
        productName: string;
        amount: number;
        currency: string;
        metadata?: Record<string, any>;
    }) {
        const { userId, productName, amount, currency, metadata } = params;

        // Create the session in Stripe
        const session = await this.stripe.checkout.sessions.create({
            payment_method_types: ['card'],
            line_items: [
                {
                    price_data: {
                        currency: currency.toLowerCase(),
                        product_data: {
                            name: productName,
                        },
                        unit_amount: Math.round(amount * 100), // Stripe expects cents
                    },
                    quantity: 1,
                },
            ],
            mode: 'payment',
            success_url: `${process.env.FRONTEND_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
            cancel_url: `${process.env.FRONTEND_URL}/payment/cancel?session_id={CHECKOUT_SESSION_ID}`,
            customer_email: metadata?.email, // Optional: auto-fill email if available
            client_reference_id: userId,
            metadata: {
                userId,
                ...metadata
            }
        });

        // Store initial payment record in DB
        await Payment.create({
            userId,
            sessionId: session.id,
            amount: amount,
            currency: currency.toUpperCase(),
            status: 'created',
            provider: 'stripe',
            metadata: metadata
        });

        return { url: session.url };
    }

    /**
     * Handle Stripe Webhook Events
     */
    async handleWebhook(payload: string, signature: string) {
        let event: Stripe.Event;

        try {
            event = this.stripe.webhooks.constructEvent(
                payload,
                signature,
                process.env.STRIPE_WEBHOOK_SECRET || ''
            );
        } catch (err: any) {
            console.error(`Webhook signature verification failed: ${err.message}`);
            throw new Error(`Webhook Error: ${err.message}`);
        }


        switch (event.type) {
            case 'checkout.session.completed': {
                const session = event.data.object as Stripe.Checkout.Session;
                await this.fulfillOrder(session);
                break;
            }

            case 'payment_intent.succeeded': {
                const paymentIntent = event.data.object as Stripe.PaymentIntent;
                await this.updatePaymentStatusByIntent(paymentIntent.id, 'succeeded');
                break;
            }

            case 'payment_intent.payment_failed':
            case 'invoice.payment_failed': {
                const object = event.data.object as any;
                const sessionId = object.metadata?.sessionId;
                if (sessionId) {
                    await this.updatePaymentStatusBySession(sessionId, 'failed');
                } else if (object.id) {
                    await this.updatePaymentStatusByIntent(object.id, 'failed');
                }
                break;
            }

            default:
                console.log(`Unhandled event type ${event.type}`);
        }

        return { received: true };
    }

    async verifyAndFulfillSession(stripeSessionId: string) {
        const existingPayment = await Payment.findOne({ sessionId: stripeSessionId });

        // If payment is succeeded, check for subscription
        if (existingPayment && existingPayment.status === 'succeeded') {
            const subscription = await Subscription.findOne({ paymentId: existingPayment._id })
                .populate('userId', 'firstName lastName email userType')
                .populate('planId'); // Populate plan details
            if (subscription) {
                return { status: 'succeeded', subscription };
            }
        }

        // Fetch latest status from Stripe
        const session = await this.stripe.checkout.sessions.retrieve(stripeSessionId);

        if (session.payment_status === 'paid') {
            const subscription = await this.fulfillOrder(session);
            if (subscription) {
                await subscription.populate('userId', 'firstName lastName email userType');
                await subscription.populate('planId');
            }
            return { status: 'succeeded', subscription };
        }

        return { status: existingPayment?.status || 'pending', subscription: null };
    }

    private async fulfillOrder(session: Stripe.Checkout.Session) {
        const stripeSessionId = session.id;
        const userId = session.client_reference_id;
        const planId = session.metadata?.planId;

        if (!userId || !planId) {
            console.error('[Fulfillment] Critical Error: Missing userId or planId in session metadata');
            return null;
        }

        const existingPayment = await Payment.findOne({ sessionId: stripeSessionId });

        // 1. Check if Subscription already exists
        const existingSubscription = await Subscription.findOne({ paymentId: existingPayment?._id });
        if (existingSubscription && existingPayment?.status === 'succeeded') {
            return existingSubscription;
        }

        // 2. Fetch Plan to calculate end date
        const plan = await Plan.findById(planId);
        if (!plan) {
            console.error(`[Fulfillment] Error: Plan not found for ID ${planId}. Cannot create subscription.`);
            return null;
        }

        // 3. Calculate Start & End Date
        let startDate = new Date();

        // Check if user has an active subscription that hasn't expired yet
        const activeSubscription = await Subscription.findOne({
            userId: new mongoose.Types.ObjectId(userId),
            status: 'Active',
            endDate: { $gt: new Date() }
        }).sort({ endDate: -1 });

        if (activeSubscription) {
            // Extension / Upgrade: Start from when current one ends
            startDate = new Date(activeSubscription.endDate);
        }

        const endDate = new Date(startDate);

        switch (plan.period) {
            case 'monthly':
                endDate.setMonth(endDate.getMonth() + 1);
                break;
            case '6-month':
                endDate.setMonth(endDate.getMonth() + 6);
                break;
            case 'annual':
                endDate.setFullYear(endDate.getFullYear() + 1);
                break;
            case 'freely':
                // Free trial probably shouldn't stack, but if logic allows:
                endDate.setDate(endDate.getDate() + 2);
                break;
            default:
                endDate.setMonth(endDate.getMonth() + 1);
        }

        // 4. Create New Subscription
        const subscription = new Subscription({
            userId: new mongoose.Types.ObjectId(userId),
            planId: new mongoose.Types.ObjectId(planId),
            paymentId: existingPayment?._id, // If multiple subscriptions use same paymentId, ensure schema allows it or update paymentId logic
            startDate,
            endDate,
            status: 'Active'
        });

        await subscription.save();

        if (activeSubscription && activeSubscription._id.toString() !== subscription._id.toString()) {
            // Optional: Mark old as 'Upgraded' or leave it to expire naturally?
            // Leaving it to expire naturally is safer if dates don't overlap.
            // If dates overlap (immediate upgrade), adjust old endDate to now?

            // The user asked "start date should end date of previous subscription", so no overlap.
            // We just let the old one run its course.
        }

        // 5. Update Payment Status to succeeded
        const updatedPayment = await Payment.findOneAndUpdate(
            { sessionId: stripeSessionId },
            {
                status: 'succeeded',
                paymentIntentId: session.payment_intent as string,
                updatedAt: new Date()
            },
            { new: true, upsert: true }
        );

        // Update subscription with paymentId ref if needed (though we set it above if available)
        if (updatedPayment) {
            subscription.paymentId = updatedPayment._id as any;
            await subscription.save();
        }

        const user = await User.findById(userId);
        if (user && user.email && user.firstName && plan?.title) {
            await EmailService.sendSubscriptionConfirmationEmail(user.email, user.firstName, plan.title, user.language || 'en');
        }
        return subscription;
    }

    private async updatePaymentStatusBySession(sessionId: string, status: 'succeeded' | 'failed', extraData: any = {}) {
        await Payment.findOneAndUpdate(
            { sessionId },
            {
                status,
                ...extraData,
                updatedAt: new Date()
            }
        );
    }

    private async updatePaymentStatusByIntent(paymentIntentId: string, status: 'succeeded' | 'failed') {
        await Payment.findOneAndUpdate(
            { paymentIntentId },
            {
                status,
                updatedAt: new Date()
            }
        );
    }
}

export default new PaymentService();
