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

const prisma = new PrismaClient();

/**
 * AI Insights Service
 * Note: This provides the framework for ML predictions.
 * In production, integrate with actual ML models (TensorFlow, PyTorch, etc.)
 */

// Simulated prediction based on historical data patterns
function predictYield(params: {
    strain: string;
    plantCount: number;
    stage: string;
    daysInStage: number;
    avgTemp?: number;
    avgHumidity?: number;
    co2Level?: number;
}): { prediction: number; confidence: number; factors: any } {
    // Base yield per plant (grams) - varies by strain
    const strainYields: Record<string, number> = {
        'default': 85,
        'indica': 90,
        'sativa': 80,
        'hybrid': 85,
    };

    const baseYield = strainYields[params.strain.toLowerCase()] || strainYields.default;
    let multiplier = 1.0;
    let confidence = 0.7; // Base confidence

    const factors: Record<string, { impact: number; value: any }> = {};

    // Temperature impact
    if (params.avgTemp) {
        if (params.avgTemp >= 70 && params.avgTemp <= 80) {
            multiplier *= 1.05;
            factors.temperature = { impact: 0.05, value: params.avgTemp };
        } else if (params.avgTemp < 65 || params.avgTemp > 85) {
            multiplier *= 0.85;
            factors.temperature = { impact: -0.15, value: params.avgTemp };
        }
        confidence += 0.05;
    }

    // Humidity impact
    if (params.avgHumidity) {
        if (params.avgHumidity >= 40 && params.avgHumidity <= 60) {
            multiplier *= 1.03;
            factors.humidity = { impact: 0.03, value: params.avgHumidity };
        } else if (params.avgHumidity > 70) {
            multiplier *= 0.90; // Higher risk of mold
            factors.humidity = { impact: -0.10, value: params.avgHumidity };
        }
        confidence += 0.05;
    }

    // CO2 impact
    if (params.co2Level) {
        if (params.co2Level >= 1000 && params.co2Level <= 1500) {
            multiplier *= 1.15;
            factors.co2 = { impact: 0.15, value: params.co2Level };
        }
        confidence += 0.05;
    }

    // Stage factor
    if (params.stage === 'FLOWER' && params.daysInStage > 21) {
        confidence += 0.1; // More accurate later in flower
    }

    const prediction = Math.round(baseYield * params.plantCount * multiplier);

    return {
        prediction,
        confidence: Math.min(confidence, 0.95),
        factors
    };
}

// Anomaly detection based on statistical analysis
function detectAnomalies(data: {
    type: string;
    values: number[];
    timestamps: Date[];
    thresholds?: { min: number; max: number };
}): { anomalies: any[]; stats: any } {
    const { values, timestamps, thresholds } = data;

    if (values.length < 5) {
        return { anomalies: [], stats: { insufficient: true } };
    }

    // Calculate statistics
    const mean = values.reduce((a, b) => a + b, 0) / values.length;
    const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length;
    const stdDev = Math.sqrt(variance);

    const anomalies: any[] = [];

    values.forEach((value, index) => {
        const zScore = (value - mean) / stdDev;

        // Flag if > 2 standard deviations or outside thresholds
        if (Math.abs(zScore) > 2 ||
            (thresholds && (value < thresholds.min || value > thresholds.max))) {
            anomalies.push({
                value,
                timestamp: timestamps[index],
                zScore,
                deviation: value - mean,
                severity: Math.abs(zScore) > 3 ? 'CRITICAL' : 'WARNING'
            });
        }
    });

    return {
        anomalies,
        stats: {
            mean,
            stdDev,
            min: Math.min(...values),
            max: Math.max(...values),
            count: values.length
        }
    };
}

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

    // ==================== YIELD PREDICTIONS ====================

    /**
     * POST /predict/yield
     * Generate yield prediction for a batch
     */
    fastify.post('/predict/yield', {
        handler: async (request, reply) => {
            try {
                const { batchId } = request.body as any;

                // Get batch info
                const batch = await prisma.batch.findUnique({
                    where: { id: batchId }
                });

                if (!batch) {
                    return reply.status(404).send({ error: 'Batch not found' });
                }

                // Get actual plant count from facility plants
                const actualPlantCount = await prisma.facilityPlant.count({
                    where: { batchId }
                });

                const effectivePlantCount = actualPlantCount > 0 ? actualPlantCount : (batch.plantCount || 0);

                // Get environment data if available
                let envData: any = {};

                const roomSensors = await prisma.sensor.findMany({
                    where: { roomId: batch.roomId || undefined, isActive: true },
                    include: {
                        readings: {
                            orderBy: { timestamp: 'desc' },
                            take: 24 // Last 24 readings
                        }
                    }
                });

                roomSensors.forEach(sensor => {
                    if (sensor.readings.length > 0) {
                        const avg = sensor.readings.reduce((sum, r) => sum + r.value, 0) / sensor.readings.length;
                        if (sensor.type === 'TEMPERATURE') envData.avgTemp = avg;
                        if (sensor.type === 'HUMIDITY') envData.avgHumidity = avg;
                        if (sensor.type === 'CO2') envData.co2Level = avg;
                    }
                });

                // Days in current stage
                const daysInStage = Math.floor(
                    (Date.now() - new Date(batch.updatedAt).getTime()) / (1000 * 60 * 60 * 24)
                );

                const result = predictYield({
                    strain: batch.strain || 'default',
                    plantCount: effectivePlantCount,
                    stage: batch.stage || 'VEG',
                    daysInStage,
                    ...envData
                });

                // Store prediction
                const prediction = await prisma.yieldPrediction.create({
                    data: {
                        batchId,
                        predictedYield: result.prediction,
                        confidence: result.confidence,
                        factors: result.factors
                    }
                });

                return {
                    prediction,
                    batchInfo: {
                        name: batch.name,
                        strain: batch.strain,
                        stage: batch.stage,
                        plantCount: effectivePlantCount
                    },
                    environment: envData
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to generate prediction' });
            }
        }
    });

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

                const predictions = await prisma.yieldPrediction.findMany({
                    where: { batchId },
                    orderBy: { predictedAt: 'desc' }
                });

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

    /**
     * POST /predictions/:id/actual
     * Record actual yield to improve model
     */
    fastify.post('/predictions/:id/actual', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;
                const { actualYield } = request.body as any;

                const prediction = await prisma.yieldPrediction.findUnique({ where: { id } });
                if (!prediction) {
                    return reply.status(404).send({ error: 'Prediction not found' });
                }

                const accuracy = 1 - Math.abs(prediction.predictedYield - actualYield) / actualYield;

                const updated = await prisma.yieldPrediction.update({
                    where: { id },
                    data: {
                        actualYield,
                        accuracy: Math.max(0, Math.min(1, accuracy))
                    }
                });

                return updated;
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to update prediction' });
            }
        }
    });

    // ==================== ANOMALY DETECTION ====================

    /**
     * POST /detect/anomalies
     * Detect anomalies in sensor data
     */
    fastify.post('/detect/anomalies', {
        handler: async (request, reply) => {
            try {
                const { sensorId, hours = 24 } = request.body as any;

                const sensor = await prisma.sensor.findUnique({
                    where: { id: sensorId }
                });

                if (!sensor) {
                    return reply.status(404).send({ error: 'Sensor not found' });
                }

                const since = new Date(Date.now() - hours * 60 * 60 * 1000);

                const readings = await prisma.sensorReading.findMany({
                    where: {
                        sensorId,
                        timestamp: { gte: since }
                    },
                    orderBy: { timestamp: 'asc' }
                });

                const result = detectAnomalies({
                    type: sensor.type,
                    values: readings.map(r => r.value),
                    timestamps: readings.map(r => r.timestamp),
                    thresholds: sensor.minThreshold && sensor.maxThreshold
                        ? { min: sensor.minThreshold, max: sensor.maxThreshold }
                        : undefined
                });

                // Store detected anomalies
                for (const anomaly of result.anomalies) {
                    await prisma.anomalyDetection.create({
                        data: {
                            entityType: 'Sensor',
                            entityId: sensorId,
                            anomalyType: 'ENV_UNSTABLE',
                            severity: anomaly.severity,
                            description: `${sensor.name} reading of ${anomaly.value} detected (z-score: ${anomaly.zScore.toFixed(2)})`,
                            data: anomaly
                        }
                    });
                }

                return {
                    sensor: { id: sensor.id, name: sensor.name, type: sensor.type },
                    period: { hours, since: since.toISOString() },
                    ...result
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to detect anomalies' });
            }
        }
    });

    /**
     * GET /anomalies
     * Get detected anomalies
     */
    fastify.get('/anomalies', {
        handler: async (request, reply) => {
            try {
                const { entityType, entityId, resolved, limit = 50 } = request.query as any;

                const where: any = {};
                if (entityType) where.entityType = entityType;
                if (entityId) where.entityId = entityId;
                if (resolved !== undefined) where.isResolved = resolved === 'true';

                const anomalies = await prisma.anomalyDetection.findMany({
                    where,
                    orderBy: { detectedAt: 'desc' },
                    take: parseInt(limit)
                });

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

    /**
     * POST /anomalies/:id/resolve
     * Mark anomaly as resolved
     */
    fastify.post('/anomalies/:id/resolve', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;

                const anomaly = await prisma.anomalyDetection.update({
                    where: { id },
                    data: { isResolved: true }
                });

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

    // ==================== INSIGHTS DASHBOARD ====================

    /**
     * GET /dashboard
     * Get AI insights overview
     */
    fastify.get('/dashboard', {
        handler: async (request, reply) => {
            try {
                // Get recent predictions accuracy
                const predictions = await prisma.yieldPrediction.findMany({
                    where: { accuracy: { not: null } },
                    orderBy: { predictedAt: 'desc' },
                    take: 20
                });

                const avgAccuracy = predictions.length > 0
                    ? predictions.reduce((sum, p) => sum + (p.accuracy || 0), 0) / predictions.length
                    : null;

                // Get unresolved anomalies
                const anomalies = await prisma.anomalyDetection.findMany({
                    where: { isResolved: false },
                    orderBy: { detectedAt: 'desc' },
                    take: 10
                });

                // Get batch performance insights
                const batchCosts = await prisma.batchCost.findMany({
                    where: { costPerGram: { not: null } },
                    orderBy: { costPerGram: 'asc' },
                    take: 5
                });

                return {
                    predictions: {
                        count: predictions.length,
                        avgAccuracy: avgAccuracy ? (avgAccuracy * 100).toFixed(1) + '%' : 'N/A',
                        recentAccuracy: predictions.slice(0, 5).map(p => ({
                            batchId: p.batchId,
                            accuracy: p.accuracy ? (p.accuracy * 100).toFixed(1) + '%' : 'N/A',
                            predictedAt: p.predictedAt
                        }))
                    },
                    anomalies: {
                        unresolved: anomalies.length,
                        recent: anomalies
                    },
                    performance: {
                        topBatches: batchCosts.map(c => ({
                            batchId: c.batchId,
                            costPerGram: c.costPerGram?.toFixed(2),
                            totalCost: c.totalCost,
                            yieldGrams: c.yieldGrams
                        }))
                    }
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch dashboard' });
            }
        }
    });
}
