/**
 * Invoice Service
 * Generates role-based invoices for bookings, settlements, and withdrawals
 */

const Invoice = require('../models/invoice.model');
const Booking = require('../models/booking.model');
const Transaction = require('../models/transaction.model');
const WithdrawalRequest = require('../models/withdrawal-request.model');
const Vendor = require('../models/vendor.model');
const ZoneAdmin = require('../models/zone-admin.model');
const User = require('../models/user.model');
const Driver = require('../models/driver.model');
const Zone = require('../models/zone.model');
const Setting = require('../models/setting.model');

/**
 * Generate customer booking invoice
 * Shows: Trip details, fare breakdown, payment status
 * Hides: Commission details, vendor earnings
 */
exports.generateCustomerInvoice = async (bookingId) => {
    const booking = await Booking.findById(bookingId)
        .populate('customer', 'fullName email phone')
        .populate('driver', 'fullName phone')
        .populate('vendor', 'vendorName')
        .populate('vehicle')
        .populate({
            path: 'vehicle',
            populate: { path: 'model', select: 'modelName' }
        });

    if (!booking) {
        throw { message: 'Booking not found', statusCode: 404 };
    }

    const settings = await Setting.getSettings();
    const taxRate = settings.tax?.percentage || 0;

    // Check if invoice already exists
    let invoice = await Invoice.findOne({
        booking: bookingId,
        recipientType: 'User',
        invoiceType: 'booking'
    });

    if (invoice) {
        return invoice;
    }

    // Create customer invoice
    invoice = new Invoice({
        invoiceType: 'booking',
        booking: booking._id,
        recipient: booking.customer._id,
        recipientType: 'User',
        recipientDetails: {
            name: booking.customer.fullName,
            email: booking.customer.email,
            phone: booking.customer.phone
        },
        amounts: {
            subtotal: Math.round((booking.fare.total || 0) - (booking.fare.taxes || 0)),
            tax: Math.round(booking.fare.taxes || 0),
            taxRate: taxRate,
            discount: Math.round(booking.fare.discount || 0),
            total: Math.round(booking.fare.total)
        },
        paymentBreakdown: {
            onlinePaid: Math.round(booking.payment.onlinePaid || 0),
            offlinePaid: Math.round(booking.payment.offlinePaid || 0),
            walletPaid: Math.round(booking.payment.walletPaid || 0),
            totalPaid: Math.round(
                (booking.payment.onlinePaid || 0) +
                (booking.payment.offlinePaid || 0) +
                (booking.payment.walletPaid || 0)
            ),
            remainingAmount: Math.round(Math.max(0,
                booking.fare.total -
                (booking.payment.onlinePaid || 0) -
                (booking.payment.offlinePaid || 0) -
                (booking.payment.walletPaid || 0)
            ))
        },
        fareBreakdown: {
            baseFare: Math.round(booking.fare.base || 0),
            distanceFare: Math.round(booking.fare.distance || 0),
            stopsFare: Math.round(booking.fare.stops || 0),
            nightCharge: Math.round(booking.fare.nightCharge || 0),
            zoneExtraCharge: Math.round(booking.fare.zoneExtraCharge || 0),
            taxes: Math.round(booking.fare.taxes || 0),
            discount: Math.round(booking.fare.discount || 0)
        },
        tripDetails: {
            orderId: booking.orderId,
            tripType: booking.tripType,
            pickupAddress: booking.pickupLocation?.address,
            dropAddress: booking.dropLocation?.address,
            distance: booking.distance,
            duration: booking.duration,
            pickupDate: booking.pickupDate,
            completedDate: booking.status === 'completed' ? booking.updatedAt : null,
            vehicleDetails: booking.vehicle?.model?.modelName || 'N/A',
            driverName: booking.driver?.fullName || 'To be assigned',
            driverPhone: booking.driver?.phone || 'N/A'
        },
        lineItems: [
            { description: 'Base Fare', amount: Math.round(booking.fare.base || 0) },
            { description: `Distance Charge (${booking.distance} km)`, amount: Math.round(booking.fare.distance || 0) },
            ...(booking.fare.stops > 0 ? [{ description: 'Stops Charge', amount: Math.round(booking.fare.stops) }] : []),
            ...(booking.fare.nightCharge > 0 ? [{ description: 'Night Charge', amount: Math.round(booking.fare.nightCharge) }] : []),
            ...(booking.fare.zoneExtraCharge > 0 ? [{ description: 'Zone Surcharge', amount: Math.round(booking.fare.zoneExtraCharge) }] : []),
            ...(booking.fare.taxes > 0 ? [{ description: `Tax (${taxRate}%)`, amount: Math.round(booking.fare.taxes) }] : []),
            ...(booking.fare.discount > 0 ? [{ description: 'Discount', amount: -Math.round(booking.fare.discount) }] : [])
        ],
        status: booking.payment.status === 'paid' ? 'paid' : 'generated',
        paidAt: booking.payment.status === 'paid' ? booking.updatedAt : null
    });

    await invoice.save();
    return invoice;
};

/**
 * Generate vendor settlement invoice
 * Shows: Full commission breakdown, earnings, offline/online split
 */
exports.generateVendorSettlementInvoice = async (bookingId) => {
    const booking = await Booking.findById(bookingId)
        .populate('vendor', 'vendorName email phone registeredAddress')
        .populate('driver', 'fullName phone')
        .populate('customer', 'fullName phone')
        .populate({
            path: 'zone',
            populate: { path: 'zoneAdmin', select: 'fullName' }
        });

    if (!booking) {
        throw { message: 'Booking not found', statusCode: 404 };
    }

    if (!booking.payment.isSettled) {
        throw { message: 'Booking not yet settled', statusCode: 400 };
    }

    // Check if invoice already exists
    let invoice = await Invoice.findOne({
        booking: bookingId,
        recipientType: 'Vendor',
        invoiceType: 'vendor_settlement'
    });

    if (invoice) {
        return invoice;
    }

    // Get settlement details from transactions
    const vendorTransaction = await Transaction.findOne({
        booking: bookingId,
        entityType: 'Vendor'
    });

    const settings = await Setting.getSettings();
    const systemCommissionRate = settings.financials?.defaultSystemCommission || 20;
    const zoneAdminShareRate = booking.zone?.zoneAdminCommissionShare || 5;

    // Calculate commission breakdown
    const baseFare = Math.round(
        (booking.fare.base || 0) +
        (booking.fare.distance || 0) +
        (booking.fare.stops || 0) +
        (booking.fare.nightCharge || 0) +
        (booking.fare.zoneExtraCharge || 0)
    );
    const taxes = Math.round(booking.fare.taxes || 0);
    const systemCommission = Math.round((baseFare * systemCommissionRate) / 100);
    const zoneAdminShare = Math.round((systemCommission * zoneAdminShareRate) / 100);
    const superAdminShare = Math.round(systemCommission - zoneAdminShare);
    const vendorGrossEarning = Math.round((baseFare - systemCommission) + taxes);

    // Net earning after offline adjustment
    const offlinePaid = Math.round(booking.payment.offlinePaid || 0);
    const vendorNetEarning = Math.round(vendorGrossEarning - offlinePaid);

    invoice = new Invoice({
        invoiceType: 'vendor_settlement',
        booking: booking._id,
        transaction: vendorTransaction?._id,
        recipient: booking.vendor._id,
        recipientType: 'Vendor',
        recipientDetails: {
            name: booking.vendor.vendorName,
            email: booking.vendor.email,
            phone: booking.vendor.phone,
            address: booking.vendor.registeredAddress
        },
        amounts: {
            subtotal: vendorGrossEarning,
            total: vendorNetEarning
        },
        paymentBreakdown: {
            onlinePaid: Math.round(booking.payment.onlinePaid || 0),
            offlinePaid: offlinePaid,
            totalPaid: Math.round(booking.fare.total),
            remainingAmount: 0
        },
        commissionBreakdown: {
            baseFare,
            systemCommissionRate,
            systemCommission,
            zoneAdminShareRate,
            zoneAdminShare,
            superAdminShare,
            vendorGrossEarning,
            vendorNetEarning
        },
        tripDetails: {
            orderId: booking.orderId,
            tripType: booking.tripType,
            pickupAddress: booking.pickupLocation?.address,
            dropAddress: booking.dropLocation?.address,
            distance: booking.distance,
            duration: booking.duration,
            pickupDate: booking.pickupDate,
            completedDate: booking.updatedAt,
            driverName: booking.driver?.fullName,
            driverPhone: booking.driver?.phone
        },
        lineItems: [
            { description: 'Total Fare', amount: Math.round(booking.fare.total) },
            { description: `System Commission (${systemCommissionRate}%)`, amount: -systemCommission },
            { description: 'Taxes Collected', amount: taxes },
            { description: 'Gross Earning', amount: vendorGrossEarning },
            { description: 'Less: Cash Collected by Driver', amount: -offlinePaid },
            { description: 'Net Wallet Credit', amount: vendorNetEarning }
        ],
        notes: vendorNetEarning < 0
            ? `Due Amount: ₹${Math.abs(vendorNetEarning)} has been debited from your wallet for system commission.`
            : `Amount of ₹${vendorNetEarning} has been credited to your wallet.`,
        status: 'generated'
    });

    await invoice.save();
    return invoice;
};

/**
 * Generate zone admin commission invoice
 */
exports.generateZoneAdminCommissionInvoice = async (bookingId) => {
    const booking = await Booking.findById(bookingId)
        .populate({
            path: 'zone',
            populate: { path: 'zoneAdmin', select: 'fullName email phone' }
        })
        .populate('vendor', 'vendorName');

    if (!booking || !booking.zone?.zoneAdmin) {
        throw { message: 'Booking or Zone Admin not found', statusCode: 404 };
    }

    if (!booking.payment.isSettled) {
        throw { message: 'Booking not yet settled', statusCode: 400 };
    }

    // Check if invoice already exists
    let invoice = await Invoice.findOne({
        booking: bookingId,
        recipientType: 'ZoneAdmin',
        invoiceType: 'commission'
    });

    if (invoice) {
        return invoice;
    }

    const zoneAdminTransaction = await Transaction.findOne({
        booking: bookingId,
        entityType: 'ZoneAdmin'
    });

    const settings = await Setting.getSettings();
    const systemCommissionRate = settings.financials?.defaultSystemCommission || 20;
    const zoneAdminShareRate = booking.zone?.zoneAdminCommissionShare || 5;

    const baseFare = Math.round(
        (booking.fare.base || 0) +
        (booking.fare.distance || 0) +
        (booking.fare.stops || 0) +
        (booking.fare.nightCharge || 0) +
        (booking.fare.zoneExtraCharge || 0)
    );
    const systemCommission = Math.round((baseFare * systemCommissionRate) / 100);
    const zoneAdminShare = Math.round((systemCommission * zoneAdminShareRate) / 100);

    invoice = new Invoice({
        invoiceType: 'commission',
        booking: booking._id,
        transaction: zoneAdminTransaction?._id,
        recipient: booking.zone.zoneAdmin._id,
        recipientType: 'ZoneAdmin',
        recipientDetails: {
            name: booking.zone.zoneAdmin.fullName,
            email: booking.zone.zoneAdmin.email,
            phone: booking.zone.zoneAdmin.phone
        },
        amounts: {
            total: zoneAdminShare
        },
        commissionBreakdown: {
            baseFare,
            systemCommissionRate,
            systemCommission,
            zoneAdminShareRate,
            zoneAdminShare
        },
        tripDetails: {
            orderId: booking.orderId,
            completedDate: booking.updatedAt
        },
        lineItems: [
            { description: 'Booking Base Fare', amount: baseFare },
            { description: `System Commission (${systemCommissionRate}%)`, amount: systemCommission },
            { description: `Your Share (${zoneAdminShareRate}% of Commission)`, amount: zoneAdminShare }
        ],
        notes: `Commission of ₹${zoneAdminShare} credited to your wallet for booking ${booking.orderId}`,
        status: 'generated'
    });

    await invoice.save();
    return invoice;
};

/**
 * Generate withdrawal receipt
 */
exports.generateWithdrawalInvoice = async (withdrawalRequestId) => {
    const withdrawal = await WithdrawalRequest.findById(withdrawalRequestId)
        .populate('requester', 'vendorName fullName email phone');

    if (!withdrawal) {
        throw { message: 'Withdrawal request not found', statusCode: 404 };
    }

    if (withdrawal.status !== 'processed') {
        throw { message: 'Withdrawal not yet processed', statusCode: 400 };
    }

    // Check if invoice already exists
    let invoice = await Invoice.findOne({
        withdrawalRequest: withdrawalRequestId,
        invoiceType: 'withdrawal'
    });

    if (invoice) {
        return invoice;
    }

    invoice = new Invoice({
        invoiceType: 'withdrawal',
        withdrawalRequest: withdrawal._id,
        recipient: withdrawal.requester._id,
        recipientType: withdrawal.requesterType,
        recipientDetails: {
            name: withdrawal.requester.vendorName || withdrawal.requester.fullName,
            email: withdrawal.requester.email,
            phone: withdrawal.requester.phone
        },
        amounts: {
            total: withdrawal.amount
        },
        lineItems: [
            { description: 'Withdrawal Amount', amount: withdrawal.amount },
            { description: `Payment Method: ${withdrawal.paymentMethod}`, amount: 0 },
            { description: `Transaction ID: ${withdrawal.paymentTransactionId}`, amount: 0 }
        ],
        notes: `Withdrawal of ₹${withdrawal.amount} processed successfully via ${withdrawal.paymentMethod}`,
        status: 'paid',
        paidAt: withdrawal.processedAt
    });

    await invoice.save();
    return invoice;
};

/**
 * Get invoices for a user/vendor/admin with role-based filtering
 */
exports.getInvoices = async (entityId, entityType, filters = {}, pagination = {}) => {
    const { invoiceType, startDate, endDate } = filters;
    const { page = 1, limit = 20 } = pagination;
    const skip = (page - 1) * limit;

    const query = {
        recipient: entityId,
        recipientType: entityType
    };

    if (invoiceType) query.invoiceType = invoiceType;

    if (startDate || endDate) {
        query.createdAt = {};
        if (startDate) query.createdAt.$gte = new Date(startDate);
        if (endDate) query.createdAt.$lte = new Date(endDate);
    }

    const [invoices, total] = await Promise.all([
        Invoice.find(query)
            .populate('booking', 'orderId status')
            .sort({ createdAt: -1 })
            .skip(skip)
            .limit(parseInt(limit)),
        Invoice.countDocuments(query)
    ]);

    return {
        invoices,
        pagination: {
            total,
            page: parseInt(page),
            limit: parseInt(limit),
            pages: Math.ceil(total / limit)
        }
    };
};

/**
 * Get invoice by ID with role-based access control
 */
exports.getInvoiceById = async (invoiceId, requesterId, requesterType, requesterRole) => {
    const invoice = await Invoice.findById(invoiceId)
        .populate('booking')
        .populate('transaction')
        .populate('withdrawalRequest');

    if (!invoice) {
        throw { message: 'Invoice not found', statusCode: 404 };
    }

    // Access control
    if (requesterRole !== 'super-admin') {
        // Check if requester is the recipient
        if (invoice.recipient.toString() !== requesterId.toString()) {
            // For vendors, also check if they own the booking
            if (requesterType === 'Vendor' && invoice.booking) {
                const booking = await Booking.findById(invoice.booking);
                if (!booking || booking.vendor.toString() !== requesterId.toString()) {
                    throw { message: 'Access denied', statusCode: 403 };
                }
            } else {
                throw { message: 'Access denied', statusCode: 403 };
            }
        }
    }

    return invoice;
};

/**
 * Generate all invoices for a completed booking
 * Called after settlement is processed
 */
exports.generateAllBookingInvoices = async (bookingId) => {
    const results = {
        customerInvoice: null,
        vendorInvoice: null,
        zoneAdminInvoice: null,
        errors: []
    };

    try {
        results.customerInvoice = await exports.generateCustomerInvoice(bookingId);
    } catch (error) {
        results.errors.push({ type: 'customer', error: error.message });
    }

    try {
        results.vendorInvoice = await exports.generateVendorSettlementInvoice(bookingId);
    } catch (error) {
        results.errors.push({ type: 'vendor', error: error.message });
    }

    try {
        results.zoneAdminInvoice = await exports.generateZoneAdminCommissionInvoice(bookingId);
    } catch (error) {
        results.errors.push({ type: 'zoneAdmin', error: error.message });
    }

    return results;
};

/**
 * Get booking invoice summary (for dashboard/quick view)
 */
exports.getBookingInvoiceSummary = async (bookingId, viewerRole) => {
    const booking = await Booking.findById(bookingId)
        .populate('customer', 'fullName')
        .populate('vendor', 'vendorName')
        .populate('driver', 'fullName');

    if (!booking) {
        throw { message: 'Booking not found', statusCode: 404 };
    }

    const settings = await Setting.getSettings();
    const systemCommissionRate = settings.financials?.defaultSystemCommission || 20;

    const baseFare = Math.round(
        (booking.fare.base || 0) +
        (booking.fare.distance || 0) +
        (booking.fare.stops || 0) +
        (booking.fare.nightCharge || 0) +
        (booking.fare.zoneExtraCharge || 0)
    );
    const taxes = Math.round(booking.fare.taxes || 0);
    const systemCommission = Math.round((baseFare * systemCommissionRate) / 100);

    // Base summary for all roles
    const summary = {
        orderId: booking.orderId,
        status: booking.status,
        totalFare: Math.round(booking.fare.total),
        paymentStatus: booking.payment.status,
        paymentMode: booking.payment.mode,
        onlinePaid: Math.round(booking.payment.onlinePaid || 0),
        offlinePaid: Math.round(booking.payment.offlinePaid || 0),
        isSettled: booking.payment.isSettled
    };

    // Role-based additional info
    if (viewerRole === 'user') {
        // Customer view - basic info only
        return {
            ...summary,
            fareBreakdown: {
                baseFare: Math.round(booking.fare.base),
                distanceFare: Math.round(booking.fare.distance),
                taxes: taxes,
                total: Math.round(booking.fare.total)
            }
        };
    }

    if (viewerRole === 'vendor') {
        // Vendor view - includes earnings
        const vendorGrossEarning = Math.round((baseFare - systemCommission) + taxes);
        const vendorNetEarning = Math.round(vendorGrossEarning - (booking.payment.offlinePaid || 0));

        return {
            ...summary,
            customer: booking.customer?.fullName,
            driver: booking.driver?.fullName,
            commissionDeducted: systemCommission,
            grossEarning: vendorGrossEarning,
            cashCollectedByDriver: Math.round(booking.payment.offlinePaid || 0),
            netWalletCredit: vendorNetEarning,
            walletCreditStatus: vendorNetEarning >= 0 ? 'credit' : 'debit'
        };
    }

    if (viewerRole === 'zone-admin' || viewerRole === 'super-admin') {
        // Admin view - full breakdown
        const zone = await Zone.findById(booking.zone);
        const zoneAdminShareRate = zone?.zoneAdminCommissionShare || 5;
        const zoneAdminShare = Math.round((systemCommission * zoneAdminShareRate) / 100);
        const superAdminShare = Math.round(systemCommission - zoneAdminShare);
        const vendorGrossEarning = Math.round((baseFare - systemCommission) + taxes);

        return {
            ...summary,
            customer: booking.customer?.fullName,
            vendor: booking.vendor?.vendorName,
            driver: booking.driver?.fullName,
            commissionBreakdown: {
                baseFare,
                taxes,
                systemCommissionRate: `${systemCommissionRate}%`,
                systemCommission,
                zoneAdminShareRate: `${zoneAdminShareRate}%`,
                zoneAdminShare,
                superAdminShare,
                vendorGrossEarning,
                vendorNetEarning: Math.round(vendorGrossEarning - (booking.payment.offlinePaid || 0))
            }
        };
    }

    return summary;
};