import { FastifyInstance } from 'fastify';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';

const prisma = new PrismaClient();

const transactionSchema = z.object({
    type: z.enum(['EXPENSE', 'REVENUE', 'ADJUSTMENT']),
    category: z.enum(['LABOR', 'NUTRIENTS', 'SUPPLIES', 'EQUIPMENT', 'UTILITIES', 'RENT', 'LICENSES', 'TESTING', 'PACKAGING', 'MARKETING', 'OTHER']).optional(),
    amount: z.number(),
    description: z.string(),
    batchId: z.string().uuid().optional(),
    roomId: z.string().uuid().optional(),
    vendorId: z.string().optional(),
    invoiceNumber: z.string().optional(),
    date: z.string().datetime().optional(),
    tags: z.array(z.string()).optional()
});

const saleSchema = z.object({
    batchId: z.string().uuid().optional(),
    product: z.string(),
    quantity: z.number(),
    unit: z.string(),
    pricePerUnit: z.number(),
    customerId: z.string().optional(),
    invoiceId: z.string().optional(),
    date: z.string().datetime().optional(),
    notes: z.string().optional()
});

export async function financialRoutes(fastify: FastifyInstance) {
    // Auth middleware
    fastify.addHook('onRequest', async (request) => {
        try {
            await request.jwtVerify();
        } catch (err) {
            throw err;
        }
    });

    // ==================== TRANSACTIONS ====================

    /**
     * GET /transactions
     * List financial transactions
     */
    fastify.get('/transactions', {
        handler: async (request, reply) => {
            try {
                const { type, category, batchId, startDate, endDate, page = 1, limit = 50 } = request.query as any;

                const where: any = {};
                if (type) where.type = type;
                if (category) where.category = category;
                if (batchId) where.batchId = batchId;
                if (startDate || endDate) {
                    where.date = {};
                    if (startDate) where.date.gte = new Date(startDate);
                    if (endDate) where.date.lte = new Date(endDate);
                }

                const [transactions, total] = await Promise.all([
                    prisma.financialTransaction.findMany({
                        where,
                        orderBy: { date: 'desc' },
                        skip: (page - 1) * limit,
                        take: parseInt(limit)
                    }),
                    prisma.financialTransaction.count({ where })
                ]);

                // Calculate totals
                const totals = await prisma.financialTransaction.groupBy({
                    by: ['type'],
                    where,
                    _sum: { amount: true }
                });

                const totalsByType = totals.reduce((acc: any, t) => {
                    acc[t.type] = t._sum.amount || 0;
                    return acc;
                }, {});

                return {
                    transactions,
                    totals: totalsByType,
                    netAmount: (totalsByType.REVENUE || 0) - (totalsByType.EXPENSE || 0),
                    pagination: {
                        page: parseInt(page),
                        limit: parseInt(limit),
                        total,
                        pages: Math.ceil(total / limit)
                    }
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch transactions' });
            }
        }
    });

    /**
     * POST /transactions
     * Create transaction
     */
    fastify.post('/transactions', {
        handler: async (request, reply) => {
            try {
                const user = request.user as any;
                const data = transactionSchema.parse(request.body);

                const transaction = await prisma.financialTransaction.create({
                    data: {
                        ...data,
                        date: data.date ? new Date(data.date) : new Date(),
                        tags: data.tags || [],
                        attachments: [],
                        createdBy: user.id
                    }
                });

                // Update batch costs if expense is linked to a batch
                if (data.batchId && data.type === 'EXPENSE') {
                    await updateBatchCosts(data.batchId);
                }

                return transaction;
            } catch (error: any) {
                if (error.name === 'ZodError') {
                    return reply.status(400).send({ error: 'Validation failed', details: error.errors });
                }
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to create transaction' });
            }
        }
    });

    // ==================== SALES ====================

    /**
     * GET /sales
     * List sales
     */
    fastify.get('/sales', {
        handler: async (request, reply) => {
            try {
                const { batchId, startDate, endDate, limit = 50 } = request.query as any;

                const where: any = {};
                if (batchId) where.batchId = batchId;
                if (startDate || endDate) {
                    where.date = {};
                    if (startDate) where.date.gte = new Date(startDate);
                    if (endDate) where.date.lte = new Date(endDate);
                }

                const sales = await prisma.sale.findMany({
                    where,
                    orderBy: { date: 'desc' },
                    take: parseInt(limit)
                });

                const totalRevenue = sales.reduce((sum, s) => sum + s.totalPrice, 0);
                const totalQuantity = sales.reduce((sum, s) => sum + s.quantity, 0);

                return {
                    sales,
                    summary: {
                        count: sales.length,
                        totalRevenue,
                        totalQuantity,
                        avgPrice: sales.length > 0 ? totalRevenue / totalQuantity : 0
                    }
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch sales' });
            }
        }
    });

    /**
     * POST /sales
     * Record a sale
     */
    fastify.post('/sales', {
        handler: async (request, reply) => {
            try {
                const user = request.user as any;
                const data = saleSchema.parse(request.body);

                const sale = await prisma.sale.create({
                    data: {
                        ...data,
                        totalPrice: data.quantity * data.pricePerUnit,
                        date: data.date ? new Date(data.date) : new Date(),
                        createdBy: user.id
                    }
                });

                // Create revenue transaction
                await prisma.financialTransaction.create({
                    data: {
                        type: 'REVENUE',
                        amount: sale.totalPrice,
                        description: `Sale: ${data.product} (${data.quantity} ${data.unit})`,
                        batchId: data.batchId,
                        createdBy: user.id
                    }
                });

                return sale;
            } catch (error: any) {
                if (error.name === 'ZodError') {
                    return reply.status(400).send({ error: 'Validation failed', details: error.errors });
                }
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to record sale' });
            }
        }
    });

    // ==================== BATCH COSTS ====================

    /**
     * GET /batches/:batchId/costs
     * Get cost breakdown for a batch
     */
    fastify.get('/batches/:batchId/costs', {
        handler: async (request, reply) => {
            try {
                const { batchId } = request.params as any;

                let costs = await prisma.batchCost.findUnique({
                    where: { batchId }
                });

                if (!costs) {
                    costs = await updateBatchCosts(batchId);
                }

                // Get transaction breakdown
                const transactions = await prisma.financialTransaction.findMany({
                    where: { batchId, type: 'EXPENSE' },
                    orderBy: { date: 'desc' }
                });

                const byCategory = transactions.reduce((acc: any, t) => {
                    const cat = t.category || 'OTHER';
                    acc[cat] = (acc[cat] || 0) + t.amount;
                    return acc;
                }, {});

                return {
                    costs,
                    transactions,
                    byCategory
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch batch costs' });
            }
        }
    });

    /**
     * POST /batches/:batchId/costs/recalculate
     * Recalculate batch costs
     */
    fastify.post('/batches/:batchId/costs/recalculate', {
        handler: async (request, reply) => {
            try {
                const { batchId } = request.params as any;

                const costs = await updateBatchCosts(batchId);

                return costs;
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to recalculate costs' });
            }
        }
    });

    // ==================== REPORTS ====================

    /**
     * GET /reports/profit-loss
     * Get profit/loss report
     */
    fastify.get('/reports/profit-loss', {
        handler: async (request, reply) => {
            try {
                const { startDate, endDate, groupBy = 'month' } = request.query as any;

                const where: any = {};
                if (startDate || endDate) {
                    where.date = {};
                    if (startDate) where.date.gte = new Date(startDate);
                    if (endDate) where.date.lte = new Date(endDate);
                }

                // Get all transactions
                const transactions = await prisma.financialTransaction.findMany({
                    where,
                    orderBy: { date: 'asc' }
                });

                // Group by period
                const grouped: Record<string, { revenue: number; expenses: number; net: number }> = {};

                transactions.forEach(t => {
                    let key: string;
                    const date = new Date(t.date);

                    if (groupBy === 'day') {
                        key = date.toISOString().split('T')[0];
                    } else if (groupBy === 'week') {
                        const weekStart = new Date(date);
                        weekStart.setDate(date.getDate() - date.getDay());
                        key = weekStart.toISOString().split('T')[0];
                    } else {
                        key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
                    }

                    if (!grouped[key]) {
                        grouped[key] = { revenue: 0, expenses: 0, net: 0 };
                    }

                    if (t.type === 'REVENUE') {
                        grouped[key].revenue += t.amount;
                    } else if (t.type === 'EXPENSE') {
                        grouped[key].expenses += t.amount;
                    }
                    grouped[key].net = grouped[key].revenue - grouped[key].expenses;
                });

                const periods = Object.entries(grouped).map(([period, data]) => ({
                    period,
                    ...data
                }));

                const totals = periods.reduce(
                    (acc, p) => ({
                        revenue: acc.revenue + p.revenue,
                        expenses: acc.expenses + p.expenses,
                        net: acc.net + p.net
                    }),
                    { revenue: 0, expenses: 0, net: 0 }
                );

                return {
                    groupBy,
                    periods,
                    totals
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to generate report' });
            }
        }
    });

    /**
     * GET /reports/category-breakdown
     * Get expense breakdown by category
     */
    fastify.get('/reports/category-breakdown', {
        handler: async (request, reply) => {
            try {
                const { startDate, endDate } = request.query as any;

                const where: any = { type: 'EXPENSE' };
                if (startDate || endDate) {
                    where.date = {};
                    if (startDate) where.date.gte = new Date(startDate);
                    if (endDate) where.date.lte = new Date(endDate);
                }

                const breakdown = await prisma.financialTransaction.groupBy({
                    by: ['category'],
                    where,
                    _sum: { amount: true },
                    _count: true,
                    orderBy: { _sum: { amount: 'desc' } }
                });

                const total = breakdown.reduce((sum, b) => sum + (b._sum.amount || 0), 0);

                return {
                    breakdown: breakdown.map(b => ({
                        category: b.category || 'OTHER',
                        amount: b._sum.amount || 0,
                        count: b._count,
                        percentage: total > 0 ? ((b._sum.amount || 0) / total) * 100 : 0
                    })),
                    total
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to generate report' });
            }
        }
    });

    /**
     * GET /reports/cost-per-gram
     * Get cost per gram across batches
     */
    fastify.get('/reports/cost-per-gram', {
        handler: async (request, reply) => {
            try {
                const costs = await prisma.batchCost.findMany({
                    where: {
                        costPerGram: { not: null }
                    },
                    orderBy: { costPerGram: 'asc' }
                });

                const withYield = costs.filter(c => c.yieldGrams && c.yieldGrams > 0);
                const avgCostPerGram = withYield.length > 0
                    ? withYield.reduce((sum, c) => sum + (c.costPerGram || 0), 0) / withYield.length
                    : 0;

                return {
                    batches: costs,
                    summary: {
                        count: withYield.length,
                        avgCostPerGram,
                        minCostPerGram: withYield[0]?.costPerGram || 0,
                        maxCostPerGram: withYield[withYield.length - 1]?.costPerGram || 0
                    }
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to generate report' });
            }
        }
    });
}

// Helper function to update batch costs
async function updateBatchCosts(batchId: string) {
    const transactions = await prisma.financialTransaction.findMany({
        where: { batchId, type: 'EXPENSE' }
    });

    const costs = {
        laborCost: 0,
        nutrientCost: 0,
        utilityCost: 0,
        supplyCost: 0,
        otherCost: 0,
        totalCost: 0
    };

    transactions.forEach(t => {
        switch (t.category) {
            case 'LABOR':
                costs.laborCost += t.amount;
                break;
            case 'NUTRIENTS':
                costs.nutrientCost += t.amount;
                break;
            case 'UTILITIES':
                costs.utilityCost += t.amount;
                break;
            case 'SUPPLIES':
                costs.supplyCost += t.amount;
                break;
            default:
                costs.otherCost += t.amount;
        }
        costs.totalCost += t.amount;
    });

    return prisma.batchCost.upsert({
        where: { batchId },
        create: { batchId, ...costs },
        update: costs
    });
}
