const { Subscription, SubscriptionItem } = require('../models/subscription');
const { Invoice, InvoiceItem } = require('../models/billing');
const { Client } = require('../models/client');
const { logAction } = require('../utils/logger');
const { sequelize } = require('../config/db');
const { Op } = require('sequelize');
const { sendBillingEmail } = require('./emailService');

exports.createSubscriptionFromInvoice = async (invoiceId, data, userId) => {
    const transaction = await sequelize.transaction();
    try {
        const invoice = await Invoice.findByPk(invoiceId, { include: ['items'] });
        if (!invoice) throw new Error('Invoice not found');

        const subscription = await Subscription.create({
            name: data.name || `Sub for ${invoice.invoiceNumber}`,
            clientId: invoice.clientId,
            billingCycle: data.billingCycle || 'Monthly',
            startDate: data.startDate || new Date().toISOString().split('T')[0],
            nextBillingDate: data.nextBillingDate || data.startDate || new Date().toISOString().split('T')[0],
            amount: invoice.subtotal,
            tax: invoice.tax,
            total: invoice.total,
            status: 'Active',
            autoSendInvoice: data.autoSendInvoice !== undefined ? data.autoSendInvoice : true,
            createdBy: userId
        }, { transaction });

        if (invoice.items && invoice.items.length > 0) {
            const items = invoice.items.map(item => ({
                subscriptionId: subscription.id,
                description: item.description,
                quantity: item.quantity,
                unitPrice: item.unitPrice,
                lineTotal: item.lineTotal
            }));
            await SubscriptionItem.bulkCreate(items, { transaction });
        }

        await transaction.commit();
        await logAction(userId, 'CREATE', 'SUBSCRIPTION', { id: subscription.id, fromInvoice: invoiceId }, null);
        return subscription;
    } catch (err) {
        await transaction.rollback();
        throw err;
    }
};

const calculateProratedAmount = (startDateStr, fullAmount) => {
    const start = new Date(startDateStr);
    const year = start.getFullYear();
    const month = start.getMonth();
    const day = start.getDate();

    const lastDayOfMonth = new Date(year, month + 1, 0).getDate();
    const remainingDays = lastDayOfMonth - day + 1;

    const proratedAmount = (remainingDays / lastDayOfMonth) * fullAmount;
    return Math.round(proratedAmount * 100) / 100;
};

const getFirstOfNextMonth = (dateStr) => {
    const date = new Date(dateStr);
    const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1);
    return nextMonth.toISOString().split('T')[0];
};

exports.createSubscription = async (data, userId) => {
    const transaction = await sequelize.transaction();
    try {
        let { prorateFirstMonth, ...subData } = data;

        const subscription = await Subscription.create({
            ...subData,
            createdBy: userId
        }, { transaction });

        if (subData.items && subData.items.length > 0) {
            const items = subData.items.map(item => ({ ...item, subscriptionId: subscription.id }));
            await SubscriptionItem.bulkCreate(items, { transaction });
        }

        // Handle Proration
        if (prorateFirstMonth) {
            const proratedSubtotal = calculateProratedAmount(subscription.startDate, subscription.amount);
            const proratedTotal = calculateProratedAmount(subscription.startDate, subscription.total);
            const proratedTax = proratedTotal - proratedSubtotal;

            // 1. Generate Prorated Invoice Immediately
            const count = await Invoice.count();
            const invoiceNumber = `PRR-${(count + 1).toString().padStart(4, '0')}`;

            const invoice = await Invoice.create({
                clientId: subscription.clientId,
                invoiceNumber,
                status: 'Sent',
                issueDate: new Date().toISOString().split('T')[0],
                dueDate: new Date(Date.now() + (subscription.dueDays || 7) * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
                subtotal: proratedSubtotal,
                tax: proratedTax,
                total: proratedTotal,
                balanceDue: proratedTotal,
                source: 'Manual',
                createdBy: userId,
                notes: `Prorated invoice for period ${subscription.startDate} to End of Month.`
            }, { transaction });

            // Copy items (prorated)
            const remainingFactor = (new Date(subscription.startDate).getDate() === 1) ? 1 : ((new Date(new Date(subscription.startDate).getFullYear(), new Date(subscription.startDate).getMonth() + 1, 0).getDate() - new Date(subscription.startDate).getDate() + 1) / new Date(new Date(subscription.startDate).getFullYear(), new Date(subscription.startDate).getMonth() + 1, 0).getDate());

            const subItems = await SubscriptionItem.findAll({ where: { subscriptionId: subscription.id }, transaction });
            const invItems = subItems.map(item => ({
                invoiceId: invoice.id,
                description: `${item.description} (Prorated)`,
                quantity: item.quantity,
                unitPrice: Math.round((item.unitPrice * remainingFactor) * 100) / 100,
                lineTotal: Math.round((item.lineTotal * remainingFactor) * 100) / 100
            }));
            await InvoiceItem.bulkCreate(invItems, { transaction });

            // 2. Set next billing date to 1st of next month
            subscription.nextBillingDate = getFirstOfNextMonth(subscription.startDate);
            await subscription.save({ transaction });
        }

        await transaction.commit();
        await logAction(userId, 'CREATE', 'SUBSCRIPTION', { id: subscription.id, name: subscription.name, prorated: !!prorateFirstMonth }, null);
        return subscription;
    } catch (err) {
        await transaction.rollback();
        throw err;
    }
};

exports.updateSubscription = async (id, data, userId) => {
    const transaction = await sequelize.transaction();
    try {
        const sub = await Subscription.findByPk(id);
        if (!sub) throw new Error('Subscription not found');

        await sub.update(data, { transaction });

        if (data.items) {
            await SubscriptionItem.destroy({ where: { subscriptionId: id }, transaction });
            const items = data.items.map(item => ({ ...item, subscriptionId: id }));
            await SubscriptionItem.bulkCreate(items, { transaction });
        }

        await transaction.commit();
        await logAction(userId, 'UPDATE', 'SUBSCRIPTION', { id, name: sub.name }, null);
        return sub;
    } catch (err) {
        await transaction.rollback();
        throw err;
    }
};

exports.deleteSubscription = async (id, userId) => {
    const sub = await Subscription.findByPk(id);
    if (!sub) throw new Error('Subscription not found');
    await sub.destroy();
    await logAction(userId, 'DELETE', 'SUBSCRIPTION', { id }, null);
    return true;
};

exports.getAllSubscriptions = async () => {
    return await Subscription.findAll({
        include: [Client, 'items'],
        order: [['createdAt', 'DESC']]
    });
};

exports.getSubscriptionById = async (id) => {
    return await Subscription.findByPk(id, { include: [Client, 'items'] });
};

// Scheduler logic: process recurring invoices
exports.processRecurringInvoices = async () => {
    const today = new Date().toISOString().split('T')[0];

    const dueSubscriptions = await Subscription.findAll({
        where: {
            status: 'Active',
            nextBillingDate: { [Op.lte]: today }
        },
        include: [Client, 'items']
    });

    const processed = [];

    for (const sub of dueSubscriptions) {
        const transaction = await sequelize.transaction();
        try {
            // 1. Generate Invoice
            const count = await Invoice.count();
            const invoiceNumber = `REC-${(count + 1).toString().padStart(4, '0')}`;

            const invoice = await Invoice.create({
                clientId: sub.clientId,
                invoiceNumber,
                status: 'Sent', // Default to sent for recurring
                issueDate: today,
                dueDate: new Date(Date.now() + (sub.dueDays || 7) * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
                subtotal: sub.subtotal || sub.amount,
                tax: sub.tax,
                total: sub.total || (Number(sub.amount) + Number(sub.tax)),
                balanceDue: sub.total || (Number(sub.amount) + Number(sub.tax)),
                source: 'Manual', // Or add 'Subscription' to enum
                createdBy: sub.createdBy
            }, { transaction });

            // Copy items
            const items = sub.items.map(item => ({
                invoiceId: invoice.id,
                description: item.description,
                quantity: item.quantity,
                unitPrice: item.unitPrice,
                lineTotal: item.lineTotal
            }));
            await InvoiceItem.bulkCreate(items, { transaction });

            // 2. Update Next Billing Date
            let nextDate = new Date(sub.nextBillingDate);
            if (sub.billingCycle === 'Monthly') nextDate.setMonth(nextDate.getMonth() + 1);
            if (sub.billingCycle === 'Quarterly') nextDate.setMonth(nextDate.getMonth() + 3);
            if (sub.billingCycle === 'Yearly') nextDate.setFullYear(nextDate.getFullYear() + 1);

            sub.nextBillingDate = nextDate.toISOString().split('T')[0];
            await sub.save({ transaction });

            await transaction.commit();

            // 3. Optional Email
            if (sub.autoSendInvoice) {
                await sendBillingEmail('invoice', invoice, sub.Client);
            }

            await logAction(sub.createdBy, 'AUTO_GENERATE', 'INVOICE', { subscriptionId: sub.id, invoiceId: invoice.id }, 'SYSTEM');
            processed.push({ subId: sub.id, invNo: invoiceNumber });

        } catch (err) {
            await transaction.rollback();
            console.error(`Failed to process subscription ${sub.id}:`, err);
        }
    }

    return processed;
};
