package platform

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"
)

type alertStreamVehicleMetadata struct {
	Plate, OEM, Model, Company string
}

type alertStreamMetricMapping map[string]map[string]string

func evaluateAlertStreamRecordsTx(ctx context.Context, tx *sql.Tx, records []AlertStreamRecord, now time.Time) (AlertEvaluationResult, error) {
	result := AlertEvaluationResult{VehiclesScanned: len(records), AsOf: now.Format(time.RFC3339)}
	rules, err := loadActiveAlertStreamRules(ctx, tx)
	if err != nil {
		return result, err
	}
	if len(rules) == 0 {
		return result, nil
	}
	mappings, err := loadAlertStreamMetricMappings(ctx, tx)
	if err != nil {
		return result, err
	}
	metadata, err := loadAlertStreamVehicleMetadata(ctx, tx, records)
	if err != nil {
		return result, err
	}
	candidates, err := loadAlertStreamCandidates(ctx, tx, rules, records)
	if err != nil {
		return result, err
	}
	activeEvents, err := loadActiveAlertStreamEvents(ctx, tx, rules, records)
	if err != nil {
		return result, err
	}
	ruleStates, err := loadAlertStreamRuleStates(ctx, tx, rules, records)
	if err != nil {
		return result, err
	}
	lastTriggered, err := loadAlertStreamLastTriggered(ctx, tx, rules, records)
	if err != nil {
		return result, err
	}
	candidateUpserts := map[string]alertCandidateUpsert{}
	candidateDeletes := map[string]alertCandidateUpsert{}
	stateUpserts := map[string]alertRuleStateUpsert{}
	for _, rule := range rules {
		result.RulesEvaluated++
		for _, record := range records {
			item := alertStreamEvidence(record, metadata[record.VIN], mappings)
			if !alertRuleInScope(rule, item) {
				continue
			}
			value, supported := alertStreamMetricValue(rule.Metric, record, mappings)
			if !supported {
				continue
			}
			// Delayed device observations remain useful evidence for data-delay
			// rules, but must not open or recover telemetry-value alerts using
			// evidence outside the configured ingestion lateness window.
			if record.Late && !strings.EqualFold(rule.Metric, "data_delay_sec") {
				result.LateObservations++
				continue
			}
			observedAt := record.EventAt
			if strings.EqualFold(rule.Metric, "data_delay_sec") {
				observedAt = record.ReceivedAt
			}
			fingerprint := rule.ID + "|" + record.VIN + "|" + record.Protocol
			matched := false
			if strings.EqualFold(rule.Operator, "changed") {
				normalized := 0.0
				if value != 0 {
					normalized = 1
				}
				previous, exists := ruleStates[fingerprint]
				if exists && !observedAt.After(previous.LastObservedAt) {
					result.LateObservations++
					continue
				}
				matched = exists && previous.LastValue != normalized
				ruleStates[fingerprint] = alertRuleState{LastValue: normalized, LastObservedAt: observedAt}
				stateUpserts[fingerprint] = alertRuleStateUpsert{RuleID: rule.ID, VIN: record.VIN, Protocol: record.Protocol, LastValue: normalized, ObservedAt: observedAt}
			} else {
				matched = compareAlertRuleValue(value, rule)
			}
			if matched {
				if len(activeEvents[fingerprint]) > 0 {
					continue
				}
				candidate, exists := candidates[fingerprint]
				candidate, decision := advanceAlertCandidate(candidate, exists, observedAt, record.SourceEventID, false)
				switch decision {
				case alertObservationDuplicate:
					result.DuplicateObservations++
					continue
				case alertObservationLate:
					result.LateObservations++
					continue
				}
				candidates[fingerprint] = candidate
				result.CandidatesAdvanced++
				candidateUpserts[fingerprint] = alertCandidateUpsert{RuleID: rule.ID, VIN: record.VIN, Protocol: record.Protocol, SourceEventID: candidate.SourceEventID, FirstMatchedAt: candidate.FirstMatchedAt, LastMatchedAt: candidate.LastMatchedAt, LatestValue: value}
				delete(candidateDeletes, fingerprint)
				if int(candidate.LastMatchedAt.Sub(candidate.FirstMatchedAt).Seconds()) < rule.DurationSec || !alertRepeatAllowed(now, lastTriggered[fingerprint], rule.RepeatIntervalSec) {
					continue
				}
				id, idErr := newAlertID("alert")
				if idErr != nil {
					return result, idErr
				}
				insertSQL, insertArgs := buildAlertEventInsert(id, fingerprint, rule, item, value)
				if _, err = tx.ExecContext(ctx, insertSQL, insertArgs...); err != nil {
					return result, err
				}
				if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_alert_event_action(event_id,action,from_status,to_status,actor,note) VALUES(?,'trigger','','unprocessed','alert-stream-evaluator','Kafka 事件时间规则命中并达到持续时间')`, id); err != nil {
					return result, err
				}
				unit := alertMetricUnit(rule.Metric)
				for _, channel := range rule.NotificationChannels {
					delivery := "reserved"
					if channel == "in_app" {
						delivery = "created"
					}
					if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_alert_notification(event_id,title,content,severity,channel,delivery_status) VALUES(?,?,?,?,?,?)`, id, rule.Name, fmt.Sprintf("%s / %s 触发%s：%.2f %s", item.Plate, item.VIN, rule.Name, value, unit), rule.Severity, channel, delivery); err != nil {
						return result, err
					}
				}
				activeEvents[fingerprint] = []alertActiveEvent{{ID: id, Status: "unprocessed"}}
				lastTriggered[fingerprint] = now
				delete(candidates, fingerprint)
				delete(candidateUpserts, fingerprint)
				candidateDeletes[fingerprint] = alertCandidateUpsert{RuleID: rule.ID, VIN: record.VIN, Protocol: record.Protocol}
				result.Opened++
				continue
			}
			if _, exists := candidates[fingerprint]; exists {
				delete(candidates, fingerprint)
				delete(candidateUpserts, fingerprint)
				candidateDeletes[fingerprint] = alertCandidateUpsert{RuleID: rule.ID, VIN: record.VIN, Protocol: record.Protocol}
			}
			recovered := true
			if strings.TrimSpace(rule.RecoveryOperator) != "" {
				recovered = compareAlertValue(value, rule.RecoveryOperator, rule.RecoveryThreshold)
			}
			if !recovered {
				continue
			}
			for _, event := range activeEvents[fingerprint] {
				if _, err = tx.ExecContext(ctx, `UPDATE vehicle_alert_event SET status='recovered',recovered_at=CURRENT_TIMESTAMP(3),version=version+1 WHERE id=? AND status=?`, event.ID, event.Status); err != nil {
					return result, err
				}
				if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_alert_event_action(event_id,action,from_status,to_status,actor,note) VALUES(?,'recover',?,'recovered','alert-stream-evaluator','Kafka 事件时间恢复条件满足')`, event.ID, event.Status); err != nil {
					return result, err
				}
				result.Recovered++
			}
			delete(activeEvents, fingerprint)
		}
	}
	for _, item := range candidateDeletes {
		if _, err = tx.ExecContext(ctx, `DELETE FROM vehicle_alert_candidate WHERE rule_id=? AND vin=? AND protocol=?`, item.RuleID, item.VIN, item.Protocol); err != nil {
			return result, err
		}
	}
	candidateItems := make([]alertCandidateUpsert, 0, len(candidateUpserts))
	for _, item := range candidateUpserts {
		candidateItems = append(candidateItems, item)
	}
	for start := 0; start < len(candidateItems); start += alertCandidateBatchSize {
		end := min(start+alertCandidateBatchSize, len(candidateItems))
		query, args := buildAlertCandidateUpsert(candidateItems[start:end])
		if _, err = tx.ExecContext(ctx, query, args...); err != nil {
			return result, err
		}
	}
	stateItems := make([]alertRuleStateUpsert, 0, len(stateUpserts))
	for _, item := range stateUpserts {
		stateItems = append(stateItems, item)
	}
	for start := 0; start < len(stateItems); start += alertCandidateBatchSize {
		end := min(start+alertCandidateBatchSize, len(stateItems))
		query, args := buildAlertRuleStateUpsert(stateItems[start:end])
		if _, err = tx.ExecContext(ctx, query, args...); err != nil {
			return result, err
		}
	}
	return result, nil
}

func alertStreamScope(rules []AlertRule, records []AlertStreamRecord) ([]string, []string, []string) {
	ruleIDs := make([]string, 0, len(rules))
	vins := make([]string, 0, len(records))
	protocols := make([]string, 0, 3)
	seenRules, seenVINs, seenProtocols := map[string]bool{}, map[string]bool{}, map[string]bool{}
	for _, rule := range rules {
		if !seenRules[rule.ID] {
			seenRules[rule.ID] = true
			ruleIDs = append(ruleIDs, rule.ID)
		}
	}
	for _, record := range records {
		if !seenVINs[record.VIN] {
			seenVINs[record.VIN] = true
			vins = append(vins, record.VIN)
		}
		if !seenProtocols[record.Protocol] {
			seenProtocols[record.Protocol] = true
			protocols = append(protocols, record.Protocol)
		}
	}
	return ruleIDs, vins, protocols
}

func alertStreamScopeWhere(rules []AlertRule, records []AlertStreamRecord) (string, []any) {
	ruleIDs, vins, protocols := alertStreamScope(rules, records)
	placeholders := func(count int) string { return strings.TrimSuffix(strings.Repeat("?,", count), ",") }
	args := make([]any, 0, len(ruleIDs)+len(vins)+len(protocols))
	for _, value := range ruleIDs {
		args = append(args, value)
	}
	for _, value := range vins {
		args = append(args, value)
	}
	for _, value := range protocols {
		args = append(args, value)
	}
	return `rule_id IN (` + placeholders(len(ruleIDs)) + `) AND vin IN (` + placeholders(len(vins)) + `) AND protocol IN (` + placeholders(len(protocols)) + `)`, args
}

func loadAlertStreamCandidates(ctx context.Context, tx *sql.Tx, rules []AlertRule, records []AlertStreamRecord) (map[string]alertCandidateState, error) {
	where, args := alertStreamScopeWhere(rules, records)
	rows, err := tx.QueryContext(ctx, `SELECT rule_id,vin,protocol,first_matched_at,last_matched_at,source_event_id FROM vehicle_alert_candidate WHERE `+where, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := map[string]alertCandidateState{}
	for rows.Next() {
		var ruleID, vin, protocol string
		var state alertCandidateState
		if err := rows.Scan(&ruleID, &vin, &protocol, &state.FirstMatchedAt, &state.LastMatchedAt, &state.SourceEventID); err != nil {
			return nil, err
		}
		items[ruleID+"|"+vin+"|"+protocol] = state
	}
	return items, rows.Err()
}

func loadActiveAlertStreamEvents(ctx context.Context, tx *sql.Tx, rules []AlertRule, records []AlertStreamRecord) (map[string][]alertActiveEvent, error) {
	where, args := alertStreamScopeWhere(rules, records)
	rows, err := tx.QueryContext(ctx, `SELECT fingerprint,id,status FROM vehicle_alert_event WHERE status IN ('unprocessed','processing') AND `+where+` FOR UPDATE`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := map[string][]alertActiveEvent{}
	for rows.Next() {
		var fingerprint string
		var event alertActiveEvent
		if err := rows.Scan(&fingerprint, &event.ID, &event.Status); err != nil {
			return nil, err
		}
		items[fingerprint] = append(items[fingerprint], event)
	}
	return items, rows.Err()
}

func loadAlertStreamRuleStates(ctx context.Context, tx *sql.Tx, rules []AlertRule, records []AlertStreamRecord) (map[string]alertRuleState, error) {
	where, args := alertStreamScopeWhere(rules, records)
	rows, err := tx.QueryContext(ctx, `SELECT rule_id,vin,protocol,observed_value,last_observed_at FROM vehicle_alert_rule_state WHERE `+where, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := map[string]alertRuleState{}
	for rows.Next() {
		var ruleID, vin, protocol string
		var state alertRuleState
		if err := rows.Scan(&ruleID, &vin, &protocol, &state.LastValue, &state.LastObservedAt); err != nil {
			return nil, err
		}
		items[ruleID+"|"+vin+"|"+protocol] = state
	}
	return items, rows.Err()
}

func loadAlertStreamLastTriggered(ctx context.Context, tx *sql.Tx, rules []AlertRule, records []AlertStreamRecord) (map[string]time.Time, error) {
	where, args := alertStreamScopeWhere(rules, records)
	rows, err := tx.QueryContext(ctx, `SELECT fingerprint,MAX(triggered_at) FROM vehicle_alert_event WHERE `+where+` GROUP BY fingerprint`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := map[string]time.Time{}
	for rows.Next() {
		var fingerprint string
		var triggeredAt time.Time
		if err := rows.Scan(&fingerprint, &triggeredAt); err != nil {
			return nil, err
		}
		items[fingerprint] = triggeredAt
	}
	return items, rows.Err()
}

func loadActiveAlertStreamRules(ctx context.Context, tx *sql.Tx) ([]AlertRule, error) {
	rows, err := tx.QueryContext(ctx, alertRuleSelect+`WHERE enabled=1 AND metric<>'freshness_sec' ORDER BY id FOR UPDATE`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	rules := []AlertRule{}
	for rows.Next() {
		rule, scanErr := scanAlertRule(rows)
		if scanErr != nil {
			return nil, scanErr
		}
		rules = append(rules, rule)
	}
	return rules, rows.Err()
}

func loadAlertStreamMetricMappings(ctx context.Context, tx *sql.Tx) (alertStreamMetricMapping, error) {
	rows, err := tx.QueryContext(ctx, `SELECT m.metric_key,m.protocol,m.source_field FROM vehicle_metric_protocol_mapping m JOIN vehicle_metric_definition d ON d.metric_key=m.metric_key WHERE d.enabled=1 AND (d.alertable=1 OR d.metric_key IN ('longitude','latitude'))`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	result := alertStreamMetricMapping{}
	for rows.Next() {
		var metric, protocol, source string
		if err := rows.Scan(&metric, &protocol, &source); err != nil {
			return nil, err
		}
		if result[metric] == nil {
			result[metric] = map[string]string{}
		}
		result[metric][strings.ToUpper(protocol)] = source
	}
	return result, rows.Err()
}

func loadAlertStreamVehicleMetadata(ctx context.Context, tx *sql.Tx, records []AlertStreamRecord) (map[string]alertStreamVehicleMetadata, error) {
	vins := make([]string, 0, len(records))
	seen := map[string]bool{}
	for _, record := range records {
		if !seen[record.VIN] {
			seen[record.VIN] = true
			vins = append(vins, record.VIN)
		}
	}
	if len(vins) == 0 {
		return map[string]alertStreamVehicleMetadata{}, nil
	}
	selects := make([]string, len(vins))
	args := make([]any, len(vins))
	for i, vin := range vins {
		selects[i] = "SELECT ? AS vin"
		args[i] = vin
	}
	query := `SELECT v.vin,COALESCE(MAX(NULLIF(b.plate,'')),''),COALESCE(MAX(b.oem),''),COALESCE(MAX(p.model_name),''),COALESCE(MAX(p.company_name),'') FROM (` + strings.Join(selects, " UNION ALL ") + `) v LEFT JOIN vehicle_identity_binding b ON b.vin=v.vin LEFT JOIN vehicle_profile p ON p.vin=v.vin GROUP BY v.vin`
	rows, err := tx.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	result := map[string]alertStreamVehicleMetadata{}
	for rows.Next() {
		var vin string
		var item alertStreamVehicleMetadata
		if err := rows.Scan(&vin, &item.Plate, &item.OEM, &item.Model, &item.Company); err != nil {
			return nil, err
		}
		result[vin] = item
	}
	return result, rows.Err()
}

func alertStreamEvidence(record AlertStreamRecord, metadata alertStreamVehicleMetadata, mappings alertStreamMetricMapping) alertEvaluationEvidence {
	longitude, _ := alertStreamLocationValue("longitude", record, mappings)
	latitude, _ := alertStreamLocationValue("latitude", record, mappings)
	plate := firstNonEmpty(record.Plate, metadata.Plate)
	return alertEvaluationEvidence{
		VIN: record.VIN, Plate: plate, Protocol: record.Protocol, OEM: metadata.OEM, Model: metadata.Model, Company: metadata.Company,
		SourceEventID: record.SourceEventID,
		EventAt:       record.EventAt.Format("2006-01-02 15:04:05.999"),
		ReceivedAt:    record.ReceivedAt.Format("2006-01-02 15:04:05.999"),
		Longitude:     longitude, Latitude: latitude,
		Location: fmt.Sprintf("%.6f,%.6f", longitude, latitude),
	}
}

func alertStreamLocationValue(metric string, record AlertStreamRecord, mappings alertStreamMetricMapping) (float64, bool) {
	if source := mappings[metric][strings.ToUpper(record.Protocol)]; source != "" && !strings.EqualFold(source, "vehicle_realtime_location."+metric) {
		return alertStreamRawNumber(record.Fields[source])
	}
	aliases := map[string]map[string][]string{
		"longitude": {
			"GB32960": {"gb32960.position.longitude"}, "JT808": {"jt808.location.longitude"}, "YUTONG_MQTT": {"yutong_mqtt.data.longitude", "yutong_mqtt.root.data.longitude"},
		},
		"latitude": {
			"GB32960": {"gb32960.position.latitude"}, "JT808": {"jt808.location.latitude"}, "YUTONG_MQTT": {"yutong_mqtt.data.latitude", "yutong_mqtt.root.data.latitude"},
		},
	}
	for _, source := range aliases[metric][strings.ToUpper(record.Protocol)] {
		if value, ok := alertStreamRawNumber(record.Fields[source]); ok {
			return value, true
		}
	}
	return 0, false
}

func alertStreamMetricValue(metric string, record AlertStreamRecord, mappings alertStreamMetricMapping) (float64, bool) {
	metric = strings.ToLower(strings.TrimSpace(metric))
	if metric == "data_delay_sec" {
		return record.ReceivedAt.Sub(record.EventAt).Seconds(), true
	}
	source := mappings[metric][strings.ToUpper(record.Protocol)]
	if source == "" || strings.EqualFold(source, "received_at-event_time") || strings.HasPrefix(source, "vehicle_realtime_location.") {
		return 0, false
	}
	value, ok := alertStreamRawNumber(record.Fields[source])
	if ok && metric == "alarm_active" {
		if value != 0 {
			return 1, true
		}
		return 0, true
	}
	return value, ok
}

func alertStreamRawNumber(raw json.RawMessage) (float64, bool) {
	if len(raw) == 0 || string(raw) == "null" {
		return 0, false
	}
	decoder := json.NewDecoder(strings.NewReader(string(raw)))
	decoder.UseNumber()
	var value any
	if err := decoder.Decode(&value); err != nil {
		return 0, false
	}
	switch typed := value.(type) {
	case json.Number:
		parsed, err := typed.Float64()
		return parsed, err == nil
	case string:
		text := strings.TrimSpace(typed)
		if strings.HasPrefix(strings.ToLower(text), "0x") {
			parsed, err := strconv.ParseUint(text[2:], 16, 64)
			return float64(parsed), err == nil
		}
		parsed, err := strconv.ParseFloat(text, 64)
		return parsed, err == nil
	case bool:
		if typed {
			return 1, true
		}
		return 0, true
	default:
		return 0, false
	}
}
