import { FastifyInstance } from 'fastify';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import { broadcastAlert } from '../plugins/websocket';

const prisma = new PrismaClient();

const createSensorSchema = z.object({
    name: z.string().min(2),
    type: z.enum(['TEMPERATURE', 'HUMIDITY', 'CO2', 'LIGHT_PAR', 'LIGHT_LUX', 'PH', 'EC', 'VPD', 'SOIL_MOISTURE', 'AIR_FLOW']),
    roomId: z.string().uuid().optional(),
    location: z.string().optional(),
    deviceId: z.string().optional(),
    manufacturer: z.string().optional(),
    model: z.string().optional(),
    minThreshold: z.number().optional(),
    maxThreshold: z.number().optional()
});

const readingSchema = z.object({
    value: z.number(),
    unit: z.string(),
    timestamp: z.string().datetime().optional()
});

const profileSchema = z.object({
    name: z.string().min(2),
    stage: z.string().optional(),
    roomId: z.string().uuid().optional(),
    tempMinF: z.number().optional(),
    tempMaxF: z.number().optional(),
    humidityMin: z.number().optional(),
    humidityMax: z.number().optional(),
    co2Min: z.number().optional(),
    co2Max: z.number().optional(),
    lightHours: z.number().optional(),
    vpdMin: z.number().optional(),
    vpdMax: z.number().optional(),
    isDefault: z.boolean().optional()
});

export async function environmentRoutes(fastify: FastifyInstance) {
    // Auth middleware - skip for edge device endpoints (they use API key auth)
    const edgeEndpoints = ['/heartbeat', '/ingest'];
    fastify.addHook('onRequest', async (request, reply) => {
        // Skip JWT for edge device endpoints
        const path = request.url.split('?')[0];
        if (edgeEndpoints.some(ep => path.endsWith(ep))) {
            return; // Edge endpoints handle their own auth
        }

        try {
            await request.jwtVerify();
        } catch (err) {
            throw err;
        }
    });

    // ==================== SENSORS ====================

    /**
     * GET /sensors
     * List all sensors
     */
    fastify.get('/sensors', {
        handler: async (request, reply) => {
            try {
                const { roomId, type, active } = request.query as any;

                const where: any = {};
                if (roomId) where.roomId = roomId;
                if (type) where.type = type;
                if (active !== undefined) where.isActive = active === 'true';

                const sensors = await prisma.sensor.findMany({
                    where,
                    include: {
                        readings: {
                            orderBy: { timestamp: 'desc' },
                            take: 1
                        },
                        _count: { select: { alerts: true } }
                    },
                    orderBy: { name: 'asc' }
                });

                return sensors.map(s => ({
                    ...s,
                    latestReading: s.readings[0] || null,
                    alertCount: s._count.alerts
                }));
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch sensors' });
            }
        }
    });

    /**
     * POST /sensors
     * Create new sensor
     */
    fastify.post('/sensors', {
        handler: async (request, reply) => {
            try {
                const data = createSensorSchema.parse(request.body);

                const sensor = await prisma.sensor.create({ data });

                return sensor;
            } 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 sensor' });
            }
        }
    });

    /**
     * POST /sensors/:id/readings
     * Submit sensor reading
     */
    fastify.post('/sensors/:id/readings', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;
                const data = readingSchema.parse(request.body);

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

                const reading = await prisma.sensorReading.create({
                    data: {
                        sensorId: id,
                        value: data.value,
                        unit: data.unit,
                        timestamp: data.timestamp ? new Date(data.timestamp) : new Date()
                    }
                });

                // Check thresholds and create alert if needed
                if (sensor.minThreshold && data.value < sensor.minThreshold) {
                    await prisma.environmentAlert.create({
                        data: {
                            sensorId: id,
                            roomId: sensor.roomId,
                            type: `${sensor.type}_LOW`,
                            severity: 'WARNING',
                            message: `${sensor.name} reading ${data.value}${data.unit} below minimum threshold ${sensor.minThreshold}${data.unit}`,
                            value: data.value,
                            threshold: sensor.minThreshold
                        }
                    });
                }

                if (sensor.maxThreshold && data.value > sensor.maxThreshold) {
                    await prisma.environmentAlert.create({
                        data: {
                            sensorId: id,
                            roomId: sensor.roomId,
                            type: `${sensor.type}_HIGH`,
                            severity: 'WARNING',
                            message: `${sensor.name} reading ${data.value}${data.unit} above maximum threshold ${sensor.maxThreshold}${data.unit}`,
                            value: data.value,
                            threshold: sensor.maxThreshold
                        }
                    });
                }

                return reading;
            } 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 submit reading' });
            }
        }
    });

    /**
     * GET /sensors/:id/readings
     * Get sensor readings history
     */
    fastify.get('/sensors/:id/readings', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;
                const { hours = 24, limit = 1000 } = request.query as any;

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

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

                return {
                    sensorId: id,
                    period: { hours: parseInt(hours), since: since.toISOString() },
                    count: readings.length,
                    readings
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch readings' });
            }
        }
    });

    // ==================== ALERTS ====================

    /**
     * GET /alerts
     * Get environment alerts
     */
    fastify.get('/alerts', {
        handler: async (request, reply) => {
            try {
                const { roomId, severity, resolved, limit = 50 } = request.query as any;

                const where: any = {};
                if (roomId) where.roomId = roomId;
                if (severity) where.severity = severity;
                if (resolved !== undefined) {
                    where.resolvedAt = resolved === 'true' ? { not: null } : null;
                }

                const alerts = await prisma.environmentAlert.findMany({
                    where,
                    include: {
                        sensor: { select: { id: true, name: true, type: true } }
                    },
                    orderBy: { createdAt: 'desc' },
                    take: parseInt(limit)
                });

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

    /**
     * POST /alerts/:id/acknowledge
     * Acknowledge an alert
     */
    fastify.post('/alerts/:id/acknowledge', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;
                const user = request.user as any;

                const alert = await prisma.environmentAlert.update({
                    where: { id },
                    data: {
                        acknowledgedAt: new Date(),
                        acknowledgedBy: user.id
                    }
                });

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

    /**
     * POST /alerts/:id/resolve
     * Resolve an alert
     */
    fastify.post('/alerts/:id/resolve', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;
                const user = request.user as any;

                const alert = await prisma.environmentAlert.update({
                    where: { id },
                    data: {
                        resolvedAt: new Date(),
                        resolvedBy: user.id
                    }
                });

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

    /**
     * POST /alerts/resolve-all
     * Resolve all unresolved alerts (for demo reset)
     */
    fastify.post('/alerts/resolve-all', {
        handler: async (request, reply) => {
            try {
                const result = await prisma.environmentAlert.updateMany({
                    where: { resolvedAt: null },
                    data: { resolvedAt: new Date() }
                });

                fastify.log.info(`🔄 Resolved ${result.count} pending alerts`);
                return { resolved: result.count, message: `Resolved ${result.count} alerts` };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to resolve alerts' });
            }
        }
    });

    /**
     * GET /alerts/analytics
     * Get alert analytics with response times
     */
    fastify.get('/alerts/analytics', {
        handler: async (request, reply) => {
            try {
                const { hours = 168 } = request.query as { hours?: number }; // Default 7 days
                const since = new Date();
                since.setHours(since.getHours() - Number(hours));

                const alerts = await prisma.environmentAlert.findMany({
                    where: {
                        createdAt: { gte: since }
                    },
                    orderBy: { createdAt: 'desc' }
                });

                // Calculate metrics
                const totalAlerts = alerts.length;
                const resolvedAlerts = alerts.filter(a => a.resolvedAt);
                const acknowledgedAlerts = alerts.filter(a => a.acknowledgedAt);
                const unresolvedAlerts = alerts.filter(a => !a.resolvedAt);

                // Response times (time from creation to acknowledgement)
                const responseTimes = acknowledgedAlerts.map(a => {
                    const created = new Date(a.createdAt).getTime();
                    const acked = new Date(a.acknowledgedAt!).getTime();
                    return (acked - created) / 1000 / 60; // minutes
                });

                // Resolution times (time from creation to resolution)
                const resolutionTimes = resolvedAlerts.map(a => {
                    const created = new Date(a.createdAt).getTime();
                    const resolved = new Date(a.resolvedAt!).getTime();
                    return (resolved - created) / 1000 / 60; // minutes
                });

                // Calculate averages
                const avgResponseTime = responseTimes.length > 0
                    ? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length
                    : null;

                const avgResolutionTime = resolutionTimes.length > 0
                    ? resolutionTimes.reduce((a, b) => a + b, 0) / resolutionTimes.length
                    : null;

                const minResponseTime = responseTimes.length > 0 ? Math.min(...responseTimes) : null;
                const maxResponseTime = responseTimes.length > 0 ? Math.max(...responseTimes) : null;
                const minResolutionTime = resolutionTimes.length > 0 ? Math.min(...resolutionTimes) : null;
                const maxResolutionTime = resolutionTimes.length > 0 ? Math.max(...resolutionTimes) : null;

                // Group by type
                const byType: Record<string, { count: number; resolved: number; avgResolution: number | null }> = {};
                alerts.forEach(a => {
                    if (!byType[a.type]) {
                        byType[a.type] = { count: 0, resolved: 0, avgResolution: null };
                    }
                    byType[a.type].count++;
                    if (a.resolvedAt) byType[a.type].resolved++;
                });

                // Calculate avg resolution per type
                Object.keys(byType).forEach(type => {
                    const typeAlerts = resolvedAlerts.filter(a => a.type === type);
                    if (typeAlerts.length > 0) {
                        const times = typeAlerts.map(a => {
                            const created = new Date(a.createdAt).getTime();
                            const resolved = new Date(a.resolvedAt!).getTime();
                            return (resolved - created) / 1000 / 60;
                        });
                        byType[type].avgResolution = times.reduce((a, b) => a + b, 0) / times.length;
                    }
                });

                // Recent alerts for timeline
                const recentAlerts = alerts.slice(0, 10).map(a => ({
                    id: a.id,
                    type: a.type,
                    severity: a.severity,
                    message: a.message,
                    value: a.value,
                    threshold: a.threshold,
                    createdAt: a.createdAt,
                    acknowledgedAt: a.acknowledgedAt,
                    resolvedAt: a.resolvedAt,
                    responseTimeMin: a.acknowledgedAt
                        ? (new Date(a.acknowledgedAt).getTime() - new Date(a.createdAt).getTime()) / 1000 / 60
                        : null,
                    resolutionTimeMin: a.resolvedAt
                        ? (new Date(a.resolvedAt).getTime() - new Date(a.createdAt).getTime()) / 1000 / 60
                        : null
                }));

                return {
                    period: {
                        hours: Number(hours),
                        from: since.toISOString(),
                        to: new Date().toISOString()
                    },
                    summary: {
                        total: totalAlerts,
                        resolved: resolvedAlerts.length,
                        acknowledged: acknowledgedAlerts.length,
                        unresolved: unresolvedAlerts.length,
                        resolutionRate: totalAlerts > 0 ? (resolvedAlerts.length / totalAlerts * 100).toFixed(1) : 0
                    },
                    responseTimes: {
                        avgMinutes: avgResponseTime?.toFixed(1) || null,
                        minMinutes: minResponseTime?.toFixed(1) || null,
                        maxMinutes: maxResponseTime?.toFixed(1) || null
                    },
                    resolutionTimes: {
                        avgMinutes: avgResolutionTime?.toFixed(1) || null,
                        minMinutes: minResolutionTime?.toFixed(1) || null,
                        maxMinutes: maxResolutionTime?.toFixed(1) || null
                    },
                    byType: Object.entries(byType).map(([type, data]) => ({
                        type,
                        ...data,
                        avgResolutionMin: data.avgResolution?.toFixed(1) || null
                    })),
                    recentAlerts
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch alert analytics' });
            }
        }
    });

    // ==================== ENVIRONMENT PROFILES ====================

    /**
     * GET /profiles
     * List environment profiles
     */
    fastify.get('/profiles', {
        handler: async (request, reply) => {
            try {
                const profiles = await prisma.environmentProfile.findMany({
                    orderBy: [{ isDefault: 'desc' }, { name: 'asc' }]
                });

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

    /**
     * POST /profiles
     * Create environment profile
     */
    fastify.post('/profiles', {
        handler: async (request, reply) => {
            try {
                const data = profileSchema.parse(request.body);

                const profile = await prisma.environmentProfile.create({ data });

                return profile;
            } 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 profile' });
            }
        }
    });

    // ==================== DASHBOARD ====================

    /**
     * GET /dashboard
     * Get environment dashboard data
     */
    fastify.get('/dashboard', {
        handler: async (request, reply) => {
            try {
                const { roomId } = request.query as any;

                const where = roomId ? { roomId } : {};
                const sensorWhere = roomId ? { roomId, isActive: true } : { isActive: true };

                // Get latest readings by sensor type
                const sensors = await prisma.sensor.findMany({
                    where: sensorWhere,
                    include: {
                        readings: {
                            orderBy: { timestamp: 'desc' },
                            take: 1
                        }
                    }
                });

                // Group by type with latest value
                const byType: Record<string, { value: number; unit: string; sensor: string; timestamp: string }[]> = {};
                sensors.forEach(s => {
                    if (s.readings[0]) {
                        if (!byType[s.type]) byType[s.type] = [];
                        byType[s.type].push({
                            value: s.readings[0].value,
                            unit: s.readings[0].unit,
                            sensor: s.name,
                            timestamp: s.readings[0].timestamp.toISOString()
                        });
                    }
                });

                // Calculate averages
                const averages: Record<string, number> = {};
                Object.entries(byType).forEach(([type, readings]) => {
                    averages[type] = readings.reduce((sum, r) => sum + r.value, 0) / readings.length;
                });

                // Get active alerts
                const activeAlerts = await prisma.environmentAlert.findMany({
                    where: {
                        ...where,
                        resolvedAt: null
                    },
                    orderBy: { createdAt: 'desc' },
                    take: 10
                });

                // Get active profile
                const profile = roomId
                    ? await prisma.environmentProfile.findFirst({ where: { roomId } })
                    : await prisma.environmentProfile.findFirst({ where: { isDefault: true } });

                return {
                    sensors: sensors.length,
                    readings: byType,
                    averages,
                    alerts: {
                        active: activeAlerts.length,
                        list: activeAlerts
                    },
                    profile,
                    timestamp: new Date().toISOString()
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch dashboard' });
            }
        }
    });

    // ==================== EDGE DEVICE ENDPOINTS ====================

    /**
     * POST /ingest
     * Batch ingest readings from edge device
     * This endpoint accepts API key auth (not JWT) for edge devices
     */
    fastify.post('/ingest', {
        config: { rawBody: true },
        preHandler: async (request, reply) => {
            // Allow API key auth for edge devices
            const authHeader = request.headers.authorization;
            if (authHeader?.startsWith('Bearer ')) {
                const apiKey = authHeader.substring(7);
                // TODO: Validate API key against facility configuration
                // For now, accept any non-empty key
                if (!apiKey) {
                    return reply.status(401).send({ error: 'Invalid API key' });
                }
            }
        },
        handler: async (request, reply) => {
            try {
                const { facilityId, readings } = request.body as {
                    facilityId?: string;
                    readings: Array<{
                        roomId: string;
                        sensorId?: string;
                        temperature: number;
                        humidity: number;
                        dewpoint?: number;
                        vpd?: number;
                        timestamp: string;
                    }>;
                };

                if (!readings || !Array.isArray(readings) || readings.length === 0) {
                    return reply.status(400).send({ error: 'No readings provided' });
                }

                // Store readings in batch
                const created = await prisma.$transaction(
                    readings.map(r =>
                        prisma.sensorReading.create({
                            data: {
                                sensorId: r.sensorId || 'edge-default',
                                value: r.temperature, // Primary value is temperature
                                unit: '°F',
                                timestamp: new Date(r.timestamp)
                            }
                        })
                    )
                );

                fastify.log.info(`Ingested ${created.length} readings from edge device`);

                return {
                    success: true,
                    count: created.length,
                    timestamp: new Date().toISOString()
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to ingest readings' });
            }
        }
    });

    /**
     * POST /heartbeat
     * Edge device heartbeat for monitoring
     */
    fastify.post('/heartbeat', {
        preHandler: async (request, reply) => {
            // For demo: Accept any Bearer token (no validation)
            const authHeader = request.headers.authorization;
            if (!authHeader?.startsWith('Bearer ')) {
                return reply.status(401).send({ error: 'API key required' });
            }
            // TODO: In production, validate API key against registered edge devices
        },
        handler: async (request, reply) => {
            try {
                const data = request.body as {
                    facilityId: string;
                    edgeId: string;
                    status: 'ok' | 'degraded' | 'error';
                    sensorCount: number;
                    bufferSize: number;
                    lastReading?: string;
                    uptime: number;
                };

                // Log heartbeat (could store in Redis for real-time monitoring)
                fastify.log.info({
                    event: 'edge_heartbeat',
                    ...data,
                    receivedAt: new Date().toISOString()
                });

                // TODO: Store in Redis or create alert if status is not 'ok'
                // For now, just acknowledge
                if (data.status !== 'ok') {
                    fastify.log.warn(`Edge device ${data.edgeId} status: ${data.status}`);
                }

                // Check for unaddressed critical temperature alerts (> 30 seconds for DEMO)
                const thirtySecondsAgo = new Date(Date.now() - 30 * 1000);
                const criticalAlert = await prisma.environmentAlert.findFirst({
                    where: {
                        type: 'TEMPERATURE_HIGH',
                        resolvedAt: null,
                        acknowledgedAt: null,
                        createdAt: { lt: thirtySecondsAgo }
                    }
                });

                const commands: any[] = [];
                if (criticalAlert) {
                    fastify.log.warn(`🚨 Failsafe triggered for alert ${criticalAlert.id}! Sending Kasa ON command to edge.`);
                    commands.push({ type: 'toggle_plug', state: true });
                }

                return {
                    ack: true,
                    serverTime: new Date().toISOString(),
                    commands
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to process heartbeat' });
            }
        }
    });

    /**
     * POST /alert
     * Edge device alert relay (for notification fan-out)
     */
    fastify.post('/alert', {
        preHandler: async (request, reply) => {
            const authHeader = request.headers.authorization;
            if (!authHeader?.startsWith('Bearer ')) {
                return reply.status(401).send({ error: 'API key required' });
            }
        },
        handler: async (request, reply) => {
            try {
                const data = request.body as {
                    facilityId: string;
                    edgeId: string;
                    alertType: string;
                    sensorId: string;
                    sensorName: string;
                    currentValue: number;
                    threshold: number;
                    timestamp: string;
                };

                // Create alert in database
                const alert = await prisma.environmentAlert.create({
                    data: {
                        type: data.alertType,
                        severity: data.alertType.includes('HIGH') ? 'WARNING' : 'INFO',
                        message: `${data.sensorName}: ${data.alertType.replace('_', ' ')} (${data.currentValue} vs threshold ${data.threshold})`,
                        value: data.currentValue,
                        threshold: data.threshold
                    }
                });

                fastify.log.warn({
                    event: 'edge_alert',
                    alertId: alert.id,
                    ...data
                });

                // Broadcast to WebSocket clients for real-time notifications
                broadcastAlert({
                    id: alert.id,
                    type: data.alertType,
                    sensorName: data.sensorName,
                    value: data.currentValue,
                    threshold: data.threshold,
                    message: alert.message,
                    timestamp: new Date().toISOString()
                });

                return {
                    success: true,
                    alertId: alert.id,
                    timestamp: new Date().toISOString()
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to process alert' });
            }
        }
    });
}
