package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"net/http"
	"strconv"
	"time"

	"github.com/redis/go-redis/v9"
)

// Anomaly represents a detected anomaly event from ai_anomaly_detection
type Anomaly struct {
	AnomalyID      string    `json:"anomalyId"`
	AssetID        string    `json:"assetId"`
	TagID          string    `json:"tagId"`
	ModelID        string    `json:"modelId"`
	DMAID          string    `json:"dmaId"`
	AnomalyType    string    `json:"anomalyType"`
	AnomalyScore   float64   `json:"anomalyScore"`
	ZScore         float64   `json:"zScore"`
	ObservedValue  float64   `json:"observedValue"`
	ExpectedValue  float64   `json:"expectedValue"`
	DeviationPct   float64   `json:"deviationPct"`
	DetectionTime  time.Time `json:"detectionTime"`
	ActionTaken    string    `json:"actionTaken"`
	CreatedAt      time.Time `json:"createdAt"`
}

// EventAlarm represents an alarm event from ic3_event_alarm
type EventAlarm struct {
	EventID      string    `json:"eventId"`
	EventCode    string    `json:"eventCode"`
	EventType    string    `json:"eventType"`
	Severity     string    `json:"severity"`
	AssetID      string    `json:"assetId"`
	TagID        string    `json:"tagId"`
	DMAID        string    `json:"dmaId"`
	EventMessage string    `json:"eventMessage"`
	EventTime    time.Time `json:"eventTime"`
	Status       string    `json:"status"`
}

// AnomalyMetrics represents KPI metrics
type AnomalyMetrics struct {
	ActiveAnomalies   int     `json:"activeAnomalies"`
	CriticalCount     int     `json:"criticalCount"`
	HighCount         int     `json:"highCount"`
	MediumCount       int     `json:"mediumCount"`
	AvgAnomalyScore   float64 `json:"avgAnomalyScore"`
	OpenEventsCount   int     `json:"openEventsCount"`
	ResolvedToday     int     `json:"resolvedToday"`
}

// ============================================================================
// Anomaly Detection Worker
// ============================================================================

// RunAnomalyDetectionWorker periodically detects anomalies from telemetry data
func RunAnomalyDetectionWorker(ctx context.Context, db *DB, rdb *redis.Client) {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	log.Println("Anomaly detection worker started (5 min interval)")

	// Run once immediately, then on ticker
	processAnomalies(ctx, db, rdb)

	for {
		select {
		case <-ctx.Done():
			log.Println("Anomaly detection worker stopped")
			return
		case <-ticker.C:
			processAnomalies(ctx, db, rdb)
		}
	}
}

func processAnomalies(ctx context.Context, db *DB, rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(ctx, 4*time.Minute)
	defer cancel()

	// Query current telemetry with good quality
	rows, err := db.pool.Query(ctx, `
		SELECT tc.tag_id, tc.asset_id, tc.dma_id, tc.value_num
		FROM telemetry_current tc
		WHERE tc.quality_code = 'GOOD'
		AND tc.value_num IS NOT NULL
	`)
	if err != nil {
		log.Printf("anomaly worker query error: %v", err)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var tagID, assetID, dmaID string
		var currentValue float64

		if err := rows.Scan(&tagID, &assetID, &dmaID, &currentValue); err != nil {
			log.Printf("anomaly scan error: %v", err)
			continue
		}

		// Get 1-hour statistics
		var mean, stddev *float64
		err := db.pool.QueryRow(ctx, `
			SELECT AVG(value_num) as mean, STDDEV(value_num) as std
			FROM telemetry_history
			WHERE tag_id = $1
			AND event_time > NOW() - INTERVAL '1 hour'
		`, tagID).Scan(&mean, &stddev)

		if err != nil || mean == nil || stddev == nil || *stddev == 0 {
			continue
		}

		// Calculate z-score
		zScore := (currentValue - *mean) / *stddev

		// Detect anomaly if |z-score| > 2.5
		if math.Abs(zScore) > 2.5 {
			anomalyScore := math.Min(1.0, math.Abs(zScore)/5.0)
			deviationPct := ((currentValue - *mean) / *mean) * 100

			anomalyType := "SPIKE"
			if zScore < 0 {
				anomalyType = "DRIFT"
			}

			// Insert into ai_anomaly_detection
			var anomalyID string
			err := db.pool.QueryRow(ctx, `
				INSERT INTO ai_anomaly_detection (
					asset_id, tag_id, model_id, dma_id, anomaly_type,
					anomaly_score, z_score, observed_value, expected_value, deviation_pct,
					detection_time, action_taken
				) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
				RETURNING anomaly_id
			`,
				assetID, tagID, "model-v5", dmaID, anomalyType,
				anomalyScore, zScore, currentValue, *mean, deviationPct,
				time.Now(), "NONE",
			).Scan(&anomalyID)

			if err != nil {
				log.Printf("insert anomaly error: %v", err)
				continue
			}

			// If high severity, create alarm event
			if anomalyScore > 0.7 {
				severity := "HIGH"
				if anomalyScore > 0.9 {
					severity = "CRITICAL"
				}

				eventMessage := fmt.Sprintf("%s anomaly detected on %s: observed=%.2f, expected=%.2f, z-score=%.2f",
					anomalyType, tagID, currentValue, *mean, zScore)

				_, err := db.pool.Exec(ctx, `
					INSERT INTO ic3_event_alarm (
						event_code, event_type, severity, asset_id, tag_id, dma_id,
						event_message, event_time, status
					) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
				`,
					"ANOMALY_"+anomalyType, "ANOMALY", severity,
					assetID, tagID, dmaID,
					eventMessage, time.Now(), "OPEN",
				)
				if err != nil {
					log.Printf("insert event error: %v", err)
				}
			}

			// Cache to Redis for 5 minutes
			healthData := map[string]interface{}{
				"anomaly_id":      anomalyID,
				"anomaly_type":    anomalyType,
				"anomaly_score":   anomalyScore,
				"z_score":         zScore,
				"observed_value":  currentValue,
				"expected_value":  *mean,
				"detected_at":     time.Now(),
			}
			healthJSON, _ := json.Marshal(healthData)
			rdb.Set(ctx, fmt.Sprintf("health:anomaly:%s", assetID), string(healthJSON), 5*time.Minute)
		}
	}
}

// ============================================================================
// HTTP Handlers
// ============================================================================

// POST /api/ai/anomalies (create new anomaly)
func createAnomalyHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var payload struct {
			AssetID        string  `json:"assetId"`
			TagID          string  `json:"tagId"`
			DMAID          string  `json:"dmaId"`
			AnomalyType    string  `json:"anomalyType"`
			AnomalyScore   float64 `json:"anomalyScore"`
			ZScore         float64 `json:"zScore"`
			ObservedValue  float64 `json:"observedValue"`
			ExpectedValue  float64 `json:"expectedValue"`
			DeviationPct   float64 `json:"deviationPct"`
		}

		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			w.WriteHeader(400)
			json.NewEncoder(w).Encode(map[string]string{"error": "invalid json"})
			return
		}

		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		var anomalyID string
		err := db.pool.QueryRow(ctx, `
			INSERT INTO ai_anomaly_detection (
				asset_id, tag_id, dma_id, anomaly_type,
				anomaly_score, z_score, observed_value, expected_value, deviation_pct,
				detection_time, action_taken
			) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
			RETURNING anomaly_id
		`,
			payload.AssetID, payload.TagID, payload.DMAID, payload.AnomalyType,
			payload.AnomalyScore, payload.ZScore, payload.ObservedValue, payload.ExpectedValue,
			payload.DeviationPct, time.Now(), "NONE",
		).Scan(&anomalyID)

		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(201)
		json.NewEncoder(w).Encode(map[string]string{"id": anomalyID, "created": "true"})
	}
}

// GET /api/ai/anomalies/active
func getActiveAnomaliesHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		rows, err := db.pool.Query(ctx, `
			SELECT anomaly_id, asset_id, tag_id, model_id, dma_id,
				anomaly_type, anomaly_score, z_score, observed_value, expected_value,
				deviation_pct, detection_time, action_taken, created_at
			FROM ai_anomaly_detection
			WHERE action_taken = 'NONE'
			ORDER BY anomaly_score DESC, detection_time DESC
			LIMIT 100
		`)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}
		defer rows.Close()

		var anomalies []Anomaly
		for rows.Next() {
			var a Anomaly
			err := rows.Scan(
				&a.AnomalyID, &a.AssetID, &a.TagID, &a.ModelID, &a.DMAID,
				&a.AnomalyType, &a.AnomalyScore, &a.ZScore, &a.ObservedValue, &a.ExpectedValue,
				&a.DeviationPct, &a.DetectionTime, &a.ActionTaken, &a.CreatedAt,
			)
			if err != nil {
				continue
			}
			anomalies = append(anomalies, a)
		}

		if anomalies == nil {
			anomalies = []Anomaly{}
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(anomalies)
	}
}

// GET /api/ai/anomalies/:id
func getAnomalyDetailHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		anomalyID := r.PathValue("id")
		if anomalyID == "" {
			w.WriteHeader(400)
			json.NewEncoder(w).Encode(map[string]string{"error": "anomaly_id required"})
			return
		}

		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		var a Anomaly
		err := db.pool.QueryRow(ctx, `
			SELECT anomaly_id, asset_id, tag_id, model_id, dma_id,
				anomaly_type, anomaly_score, z_score, observed_value, expected_value,
				deviation_pct, detection_time, action_taken, created_at
			FROM ai_anomaly_detection
			WHERE anomaly_id = $1
		`, anomalyID).Scan(
			&a.AnomalyID, &a.AssetID, &a.TagID, &a.ModelID, &a.DMAID,
			&a.AnomalyType, &a.AnomalyScore, &a.ZScore, &a.ObservedValue, &a.ExpectedValue,
			&a.DeviationPct, &a.DetectionTime, &a.ActionTaken, &a.CreatedAt,
		)

		if err != nil {
			w.WriteHeader(404)
			json.NewEncoder(w).Encode(map[string]string{"error": "not found"})
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(a)
	}
}

// GET /api/ai/anomalies/history?days=30
func getAnomalyHistoryHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		days := 30
		if d := r.URL.Query().Get("days"); d != "" {
			if parsed, err := strconv.Atoi(d); err == nil {
				days = parsed
			}
		}

		cutoff := time.Now().AddDate(0, 0, -days)

		rows, err := db.pool.Query(ctx, `
			SELECT anomaly_id, asset_id, tag_id, model_id, dma_id,
				anomaly_type, anomaly_score, z_score, observed_value, expected_value,
				deviation_pct, detection_time, action_taken, created_at
			FROM ai_anomaly_detection
			WHERE detection_time >= $1
			ORDER BY detection_time DESC
			LIMIT 500
		`, cutoff)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}
		defer rows.Close()

		var anomalies []Anomaly
		for rows.Next() {
			var a Anomaly
			err := rows.Scan(
				&a.AnomalyID, &a.AssetID, &a.TagID, &a.ModelID, &a.DMAID,
				&a.AnomalyType, &a.AnomalyScore, &a.ZScore, &a.ObservedValue, &a.ExpectedValue,
				&a.DeviationPct, &a.DetectionTime, &a.ActionTaken, &a.CreatedAt,
			)
			if err != nil {
				continue
			}
			anomalies = append(anomalies, a)
		}

		if anomalies == nil {
			anomalies = []Anomaly{}
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(anomalies)
	}
}

// GET /api/ai/anomalies/metrics
func getAnomalyMetricsHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		var metrics AnomalyMetrics

		// Active anomalies (action_taken = 'NONE')
		err := db.pool.QueryRow(ctx, `
			SELECT COUNT(*) FROM ai_anomaly_detection WHERE action_taken = 'NONE'
		`).Scan(&metrics.ActiveAnomalies)
		if err != nil {
			metrics.ActiveAnomalies = 0
		}

		// Average anomaly score
		err = db.pool.QueryRow(ctx, `
			SELECT COALESCE(AVG(anomaly_score), 0) FROM ai_anomaly_detection WHERE action_taken = 'NONE'
		`).Scan(&metrics.AvgAnomalyScore)
		if err != nil {
			metrics.AvgAnomalyScore = 0
		}

		// Severity distribution from events
		db.pool.QueryRow(ctx, `
			SELECT COUNT(*) FROM ic3_event_alarm WHERE severity = 'CRITICAL' AND status = 'OPEN'
		`).Scan(&metrics.CriticalCount)
		db.pool.QueryRow(ctx, `
			SELECT COUNT(*) FROM ic3_event_alarm WHERE severity = 'HIGH' AND status = 'OPEN'
		`).Scan(&metrics.HighCount)
		db.pool.QueryRow(ctx, `
			SELECT COUNT(*) FROM ic3_event_alarm WHERE severity = 'MEDIUM' AND status = 'OPEN'
		`).Scan(&metrics.MediumCount)

		// Open events
		db.pool.QueryRow(ctx, `
			SELECT COUNT(*) FROM ic3_event_alarm WHERE status = 'OPEN'
		`).Scan(&metrics.OpenEventsCount)

		// Resolved today
		today := time.Now().Truncate(24 * time.Hour)
		db.pool.QueryRow(ctx, `
			SELECT COUNT(*) FROM ic3_event_alarm WHERE status = 'CLOSED' AND event_time >= $1
		`, today).Scan(&metrics.ResolvedToday)

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(metrics)
	}
}

// POST /api/ai/anomalies/:id/dismiss
func dismissAnomalyHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		anomalyID := r.PathValue("id")
		if anomalyID == "" {
			w.WriteHeader(400)
			json.NewEncoder(w).Encode(map[string]string{"error": "anomaly_id required"})
			return
		}

		var payload struct {
			Reason string `json:"reason"`
		}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			w.WriteHeader(400)
			json.NewEncoder(w).Encode(map[string]string{"error": "invalid json"})
			return
		}

		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		// Update anomaly action_taken
		_, err := db.pool.Exec(ctx, `
			UPDATE ai_anomaly_detection
			SET action_taken = $1
			WHERE anomaly_id = $2
		`, payload.Reason, anomalyID)

		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{"success": "true"})
	}
}

// GET /api/ai/alert-rules (mapped to ic3_parameter_master)
func getAlertRulesHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		rows, err := db.pool.Query(ctx, `
			SELECT parameter_id, normal_min, normal_max, alarm_min, alarm_max
			FROM ic3_parameter_master
			ORDER BY parameter_id
		`)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}
		defer rows.Close()

		var rules []map[string]interface{}
		for rows.Next() {
			var paramID string
			var normalMin, normalMax, alarmMin, alarmMax *float64

			err := rows.Scan(&paramID, &normalMin, &normalMax, &alarmMin, &alarmMax)
			if err != nil {
				continue
			}

			rule := map[string]interface{}{
				"parameterId": paramID,
				"thresholds": map[string]interface{}{
					"normal": map[string]interface{}{
						"min": normalMin,
						"max": normalMax,
					},
					"alarm": map[string]interface{}{
						"min": alarmMin,
						"max": alarmMax,
					},
				},
			}
			rules = append(rules, rule)
		}

		if rules == nil {
			rules = []map[string]interface{}{}
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(rules)
	}
}

// GET /api/ai/sensors/health (mapped to telemetry_current + events)
func getSensorHealthHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		rows, err := db.pool.Query(ctx, `
			SELECT tc.tag_id, tc.asset_id, tc.status_code,
				COALESCE(COUNT(CASE WHEN ae.severity = 'CRITICAL' THEN 1 END), 0) as critical_count
			FROM telemetry_current tc
			LEFT JOIN ic3_event_alarm ae ON tc.asset_id = ae.asset_id AND ae.status = 'OPEN'
			GROUP BY tc.tag_id, tc.asset_id, tc.status_code
			ORDER BY tc.asset_id
		`)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}
		defer rows.Close()

		var sensors []map[string]interface{}
		for rows.Next() {
			var tagID, assetID, statusCode string
			var criticalCount int

			err := rows.Scan(&tagID, &assetID, &statusCode, &criticalCount)
			if err != nil {
				continue
			}

			trustScore := 100.0
			if criticalCount > 0 {
				trustScore = 100.0 - (float64(criticalCount) * 5)
			}
			if trustScore < 0 {
				trustScore = 0
			}

			sensor := map[string]interface{}{
				"tagId":      tagID,
				"assetId":    assetID,
				"trustScore": trustScore,
				"status":     statusCode,
			}
			sensors = append(sensors, sensor)
		}

		if sensors == nil {
			sensors = []map[string]interface{}{}
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(sensors)
	}
}

// POST /api/ai/alert-rules (update ic3_parameter_master)
func createAlertRuleHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var payload struct {
			ParameterID string  `json:"parameterId"`
			NormalMin   float64 `json:"normalMin"`
			NormalMax   float64 `json:"normalMax"`
			AlarmMin    float64 `json:"alarmMin"`
			AlarmMax    float64 `json:"alarmMax"`
		}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			w.WriteHeader(400)
			json.NewEncoder(w).Encode(map[string]string{"error": "invalid json"})
			return
		}

		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		_, err := db.pool.Exec(ctx, `
			INSERT INTO ic3_parameter_master (parameter_id, normal_min, normal_max, alarm_min, alarm_max)
			VALUES ($1, $2, $3, $4, $5)
			ON CONFLICT (parameter_id) DO UPDATE SET
				normal_min = EXCLUDED.normal_min,
				normal_max = EXCLUDED.normal_max,
				alarm_min = EXCLUDED.alarm_min,
				alarm_max = EXCLUDED.alarm_max
		`, payload.ParameterID, payload.NormalMin, payload.NormalMax, payload.AlarmMin, payload.AlarmMax)

		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(201)
		json.NewEncoder(w).Encode(map[string]string{"parameterId": payload.ParameterID, "created": "true"})
	}
}

// POST /api/work-orders (create from anomaly - creates event alarm)
func createWorkOrderHandler(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var payload struct {
			AnomalyID string `json:"anomalyId"`
			Priority  string `json:"priority"`
			Type      string `json:"type"`
		}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			w.WriteHeader(400)
			json.NewEncoder(w).Encode(map[string]string{"error": "invalid json"})
			return
		}

		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		// Get anomaly details
		var assetID, tagID, dmaID string
		err := db.pool.QueryRow(ctx, `
			SELECT asset_id, tag_id, dma_id FROM ai_anomaly_detection WHERE anomaly_id = $1
		`, payload.AnomalyID).Scan(&assetID, &tagID, &dmaID)

		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}

		// Create event alarm (work order)
		var eventID string
		err = db.pool.QueryRow(ctx, `
			INSERT INTO ic3_event_alarm (
				event_code, event_type, severity, asset_id, tag_id, dma_id,
				event_message, event_time, status
			) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
			RETURNING event_id
		`,
			"WO_"+payload.Type, "WORK_ORDER", payload.Priority,
			assetID, tagID, dmaID,
			fmt.Sprintf("Work order: %s (Type: %s)", payload.Type, payload.Type),
			time.Now(), "OPEN",
		).Scan(&eventID)

		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(201)
		json.NewEncoder(w).Encode(map[string]string{"id": eventID, "created": "true"})
	}
}
