package platform

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

func (s *ProductionStore) ensureAlertSchema(ctx context.Context) error {
	s.alertSchemaOnce.Do(func() {
		var count int
		s.alertSchemaErr = s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_alert_rule`).Scan(&count)
		if s.alertSchemaErr != nil {
			s.alertSchemaErr = fmt.Errorf("alert center schema unavailable; apply deploy/migrations/002_alert_center.sql: %w", s.alertSchemaErr)
		}
	})
	return s.alertSchemaErr
}

func (s *ProductionStore) AlertSummary(ctx context.Context, query AlertQuery) (AlertSummary, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return AlertSummary{}, err
	}
	where, args := buildAlertWhere(query)
	var result AlertSummary
	err := s.db.QueryRowContext(ctx, `SELECT
	COALESCE(SUM(CASE WHEN status IN ('unprocessed','processing') THEN 1 ELSE 0 END),0),
	COALESCE(SUM(CASE WHEN status='unprocessed' THEN 1 ELSE 0 END),0),
	COALESCE(SUM(CASE WHEN status='processing' THEN 1 ELSE 0 END),0),
	COALESCE(SUM(CASE WHEN status='recovered' THEN 1 ELSE 0 END),0),
	COALESCE(SUM(CASE WHEN status='closed' THEN 1 ELSE 0 END),0),
	COALESCE(SUM(CASE WHEN status='ignored' THEN 1 ELSE 0 END),0)
FROM vehicle_alert_event e WHERE `+where, args...).Scan(&result.Active, &result.Unprocessed, &result.Processing, &result.Recovered, &result.Closed, &result.Ignored)
	if err != nil {
		return AlertSummary{}, err
	}
	if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_alert_notification WHERE channel='in_app' AND is_read=0`).Scan(&result.UnreadNotifications); err != nil {
		return AlertSummary{}, err
	}
	result.AsOf = time.Now().Format(time.RFC3339)
	return result, nil
}

func (s *ProductionStore) ActiveAlertVINs(ctx context.Context, protocol string) ([]string, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return nil, err
	}
	query := `SELECT DISTINCT vin FROM vehicle_alert_event WHERE status IN ('unprocessed','processing') AND vin<>''`
	args := []any{}
	if protocol = strings.TrimSpace(protocol); protocol != "" {
		query += ` AND protocol=?`
		args = append(args, protocol)
	}
	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	vins := make([]string, 0)
	for rows.Next() {
		var vin string
		if err := rows.Scan(&vin); err != nil {
			return nil, err
		}
		vins = append(vins, vin)
	}
	return vins, rows.Err()
}

func buildAlertWhere(query AlertQuery) (string, []any) {
	where := []string{"1=1"}
	args := []any{}
	if query.Keyword != "" {
		like := "%" + query.Keyword + "%"
		where = append(where, "(e.vin LIKE ? OR e.plate LIKE ? OR e.rule_name LIKE ?)")
		args = append(args, like, like, like)
	}
	if query.Severity != "" && query.Severity != "all" {
		where = append(where, "e.severity=?")
		args = append(args, query.Severity)
	}
	if query.Status == "active" {
		where = append(where, "e.status IN ('unprocessed','processing')")
	} else if query.Status != "" && query.Status != "all" {
		where = append(where, "e.status=?")
		args = append(args, query.Status)
	}
	if query.RuleID != "" {
		where = append(where, "e.rule_id=?")
		args = append(args, query.RuleID)
	}
	if query.Protocol != "" {
		where = append(where, "e.protocol=?")
		args = append(args, query.Protocol)
	}
	if query.DateFrom != "" {
		where = append(where, "e.triggered_at>=?")
		args = append(args, query.DateFrom)
	}
	if query.DateTo != "" {
		where = append(where, "e.triggered_at<=?")
		args = append(args, query.DateTo)
	}
	return strings.Join(where, " AND "), args
}

const alertEventSelect = `SELECT e.id,e.rule_id,e.rule_name,e.rule_version,e.severity,e.status,e.vin,e.plate,e.protocol,
e.metric,e.operator,e.trigger_value,e.threshold_value,e.threshold_high,e.unit,e.duration_sec,e.location_text,e.longitude,e.latitude,
e.source_event_id,COALESCE(DATE_FORMAT(e.event_at,'%Y-%m-%dT%H:%i:%s.%fZ'),''),COALESCE(DATE_FORMAT(e.received_at,'%Y-%m-%dT%H:%i:%s.%fZ'),''),
DATE_FORMAT(e.triggered_at,'%Y-%m-%dT%H:%i:%s.%fZ'),COALESCE(DATE_FORMAT(e.recovered_at,'%Y-%m-%dT%H:%i:%s.%fZ'),''),e.handler,e.version
FROM vehicle_alert_event e `

func scanAlertEvent(scanner interface{ Scan(...any) error }) (AlertEvent, error) {
	var event AlertEvent
	var longitude, latitude sql.NullFloat64
	err := scanner.Scan(&event.ID, &event.RuleID, &event.RuleName, &event.RuleVersion, &event.Severity, &event.Status, &event.VIN, &event.Plate, &event.Protocol,
		&event.Metric, &event.Operator, &event.TriggerValue, &event.Threshold, &event.ThresholdHigh, &event.Unit, &event.DurationSec, &event.Location, &longitude, &latitude,
		&event.SourceEventID, &event.EventAt, &event.ReceivedAt, &event.TriggeredAt, &event.RecoveredAt, &event.Handler, &event.Version)
	if longitude.Valid {
		event.Longitude = &longitude.Float64
	}
	if latitude.Valid {
		event.Latitude = &latitude.Float64
	}
	return event, err
}

func (s *ProductionStore) AlertEvents(ctx context.Context, query AlertQuery) (Page[AlertEvent], error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return Page[AlertEvent]{}, err
	}
	where, args := buildAlertWhere(query)
	var total int
	if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_alert_event e WHERE `+where, args...).Scan(&total); err != nil {
		return Page[AlertEvent]{}, err
	}
	listArgs := append(append([]any(nil), args...), query.Limit, query.Offset)
	rows, err := s.db.QueryContext(ctx, alertEventSelect+`WHERE `+where+` ORDER BY e.triggered_at DESC,e.id DESC LIMIT ? OFFSET ?`, listArgs...)
	if err != nil {
		return Page[AlertEvent]{}, err
	}
	defer rows.Close()
	items := make([]AlertEvent, 0, query.Limit)
	for rows.Next() {
		event, err := scanAlertEvent(rows)
		if err != nil {
			return Page[AlertEvent]{}, err
		}
		items = append(items, event)
	}
	return Page[AlertEvent]{Items: items, Total: total, Limit: query.Limit, Offset: query.Offset}, rows.Err()
}

func (s *ProductionStore) AlertEvent(ctx context.Context, id string) (AlertEvent, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return AlertEvent{}, err
	}
	event, err := scanAlertEvent(s.db.QueryRowContext(ctx, alertEventSelect+`WHERE e.id=?`, id))
	if err == sql.ErrNoRows {
		return AlertEvent{}, clientError{Code: "ALERT_EVENT_NOT_FOUND", Message: "告警事件不存在"}
	}
	if err != nil {
		return AlertEvent{}, err
	}
	rows, err := s.db.QueryContext(ctx, `SELECT id,action,from_status,to_status,actor,note,DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%s.%fZ') FROM vehicle_alert_event_action WHERE event_id=? ORDER BY created_at,id`, id)
	if err != nil {
		return AlertEvent{}, err
	}
	defer rows.Close()
	for rows.Next() {
		var item AlertAction
		if err := rows.Scan(&item.ID, &item.Action, &item.FromStatus, &item.ToStatus, &item.Actor, &item.Note, &item.CreatedAt); err != nil {
			return AlertEvent{}, err
		}
		event.Actions = append(event.Actions, item)
	}
	return event, rows.Err()
}

const alertRuleSelect = `SELECT id,name,description,severity,value_type,metric,operator,threshold_value,threshold_high,boolean_threshold,duration_sec,
recovery_operator,recovery_threshold,repeat_interval_sec,scope_protocols_json,scope_vins_json,COALESCE(scope_oems_json,'[]'),COALESCE(scope_models_json,'[]'),COALESCE(scope_companies_json,'[]'),notification_channels_json,enabled,version,
created_by,updated_by,DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%s.%fZ'),DATE_FORMAT(updated_at,'%Y-%m-%dT%H:%i:%s.%fZ') FROM vehicle_alert_rule `

func scanAlertRule(scanner interface{ Scan(...any) error }) (AlertRule, error) {
	var rule AlertRule
	var boolean sql.NullBool
	var protocols, vins, oems, models, companies, channels string
	err := scanner.Scan(&rule.ID, &rule.Name, &rule.Description, &rule.Severity, &rule.ValueType, &rule.Metric, &rule.Operator, &rule.Threshold, &rule.ThresholdHigh, &boolean, &rule.DurationSec, &rule.RecoveryOperator, &rule.RecoveryThreshold, &rule.RepeatIntervalSec, &protocols, &vins, &oems, &models, &companies, &channels, &rule.Enabled, &rule.Version, &rule.CreatedBy, &rule.UpdatedBy, &rule.CreatedAt, &rule.UpdatedAt)
	if boolean.Valid {
		rule.BooleanThreshold = &boolean.Bool
	}
	if err == nil {
		if e := json.Unmarshal([]byte(protocols), &rule.ScopeProtocols); e != nil {
			return AlertRule{}, e
		}
		if e := json.Unmarshal([]byte(vins), &rule.ScopeVINs); e != nil {
			return AlertRule{}, e
		}
		if e := json.Unmarshal([]byte(oems), &rule.ScopeOEMs); e != nil {
			return AlertRule{}, e
		}
		if e := json.Unmarshal([]byte(models), &rule.ScopeModels); e != nil {
			return AlertRule{}, e
		}
		if e := json.Unmarshal([]byte(companies), &rule.ScopeCompanies); e != nil {
			return AlertRule{}, e
		}
		if e := json.Unmarshal([]byte(channels), &rule.NotificationChannels); e != nil {
			return AlertRule{}, e
		}
	}
	return rule, err
}

func (s *ProductionStore) AlertRules(ctx context.Context) ([]AlertRule, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return nil, err
	}
	rows, err := s.db.QueryContext(ctx, alertRuleSelect+`ORDER BY enabled DESC,severity,name`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []AlertRule{}
	for rows.Next() {
		item, err := scanAlertRule(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func marshalAlertRuleLists(input AlertRuleInput) (string, string, string, string, string, string, error) {
	p, e := json.Marshal(input.ScopeProtocols)
	if e != nil {
		return "", "", "", "", "", "", e
	}
	v, e := json.Marshal(input.ScopeVINs)
	if e != nil {
		return "", "", "", "", "", "", e
	}
	o, e := json.Marshal(input.ScopeOEMs)
	if e != nil {
		return "", "", "", "", "", "", e
	}
	m, e := json.Marshal(input.ScopeModels)
	if e != nil {
		return "", "", "", "", "", "", e
	}
	co, e := json.Marshal(input.ScopeCompanies)
	if e != nil {
		return "", "", "", "", "", "", e
	}
	c, e := json.Marshal(input.NotificationChannels)
	return string(p), string(v), string(o), string(m), string(co), string(c), e
}

const alertRuleInsertSQL = `INSERT INTO vehicle_alert_rule(id,name,description,severity,value_type,metric,operator,threshold_value,threshold_high,boolean_threshold,duration_sec,recovery_operator,recovery_threshold,repeat_interval_sec,scope_protocols_json,scope_vins_json,scope_oems_json,scope_models_json,scope_companies_json,notification_channels_json,enabled,version,created_by,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?)`
const alertRuleUpdateSQL = `UPDATE vehicle_alert_rule SET name=?,description=?,severity=?,value_type=?,metric=?,operator=?,threshold_value=?,threshold_high=?,boolean_threshold=?,duration_sec=?,recovery_operator=?,recovery_threshold=?,repeat_interval_sec=?,scope_protocols_json=?,scope_vins_json=?,scope_oems_json=?,scope_models_json=?,scope_companies_json=?,notification_channels_json=?,enabled=?,version=version+1,updated_by=? WHERE id=? AND version=?`

func (s *ProductionStore) SaveAlertRule(ctx context.Context, input AlertRuleInput) (AlertRule, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return AlertRule{}, err
	}
	p, v, o, m, co, c, err := marshalAlertRuleLists(input)
	if err != nil {
		return AlertRule{}, err
	}
	tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return AlertRule{}, err
	}
	defer tx.Rollback()
	var current int
	err = tx.QueryRowContext(ctx, `SELECT version FROM vehicle_alert_rule WHERE id=? FOR UPDATE`, input.ID).Scan(&current)
	if err == sql.ErrNoRows {
		if input.Version != 0 {
			return AlertRule{}, clientError{Code: "ALERT_RULE_NOT_FOUND", Message: "待更新规则不存在"}
		}
		_, err = tx.ExecContext(ctx, alertRuleInsertSQL, input.ID, input.Name, input.Description, strings.ToLower(input.Severity), strings.ToLower(input.ValueType), input.Metric, strings.ToLower(input.Operator), input.Threshold, input.ThresholdHigh, input.BooleanThreshold, input.DurationSec, strings.ToLower(input.RecoveryOperator), input.RecoveryThreshold, input.RepeatIntervalSec, p, v, o, m, co, c, input.Enabled, input.Actor, input.Actor)
		current = 0
	} else if err != nil {
		return AlertRule{}, err
	} else {
		if current != input.Version {
			return AlertRule{}, clientError{Code: "ALERT_RULE_VERSION_CONFLICT", Message: "规则已被其他用户更新，请刷新后重试"}
		}
		res, e := tx.ExecContext(ctx, alertRuleUpdateSQL, input.Name, input.Description, strings.ToLower(input.Severity), strings.ToLower(input.ValueType), input.Metric, strings.ToLower(input.Operator), input.Threshold, input.ThresholdHigh, input.BooleanThreshold, input.DurationSec, strings.ToLower(input.RecoveryOperator), input.RecoveryThreshold, input.RepeatIntervalSec, p, v, o, m, co, c, input.Enabled, input.Actor, input.ID, current)
		err = e
		if err == nil {
			n, _ := res.RowsAffected()
			if n != 1 {
				err = clientError{Code: "ALERT_RULE_VERSION_CONFLICT", Message: "规则更新冲突，请刷新后重试"}
			}
		}
	}
	if err != nil {
		return AlertRule{}, err
	}
	snapshot, _ := json.Marshal(input)
	if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_alert_rule_audit(rule_id,rule_version,actor,action,snapshot_json) VALUES(?,?,?,?,?)`, input.ID, current+1, input.Actor, firstNonEmpty(map[bool]string{true: "create", false: "update"}[current == 0], "update"), string(snapshot)); err != nil {
		return AlertRule{}, err
	}
	if err = tx.Commit(); err != nil {
		return AlertRule{}, err
	}
	return scanAlertRule(s.db.QueryRowContext(ctx, alertRuleSelect+`WHERE id=?`, input.ID))
}

func (s *ProductionStore) SetAlertRuleEnabled(ctx context.Context, id string, update AlertRuleEnabledUpdate) (AlertRule, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return AlertRule{}, err
	}
	tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return AlertRule{}, err
	}
	defer tx.Rollback()
	res, err := tx.ExecContext(ctx, `UPDATE vehicle_alert_rule SET enabled=?,version=version+1,updated_by=? WHERE id=? AND version=?`, update.Enabled, update.Actor, id, update.Version)
	if err != nil {
		return AlertRule{}, err
	}
	n, _ := res.RowsAffected()
	if n != 1 {
		return AlertRule{}, clientError{Code: "ALERT_RULE_VERSION_CONFLICT", Message: "规则不存在或已被其他用户更新"}
	}
	rule, err := scanAlertRule(tx.QueryRowContext(ctx, alertRuleSelect+`WHERE id=?`, id))
	if err != nil {
		return AlertRule{}, err
	}
	if !update.Enabled {
		if _, err = tx.ExecContext(ctx, `DELETE FROM vehicle_alert_candidate WHERE rule_id=?`, id); err != nil {
			return AlertRule{}, err
		}
		if _, err = tx.ExecContext(ctx, `DELETE FROM vehicle_alert_rule_state WHERE rule_id=?`, id); err != nil {
			return AlertRule{}, err
		}
	}
	snapshot, _ := json.Marshal(rule)
	if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_alert_rule_audit(rule_id,rule_version,actor,action,snapshot_json) VALUES(?,?,?,?,?)`, id, rule.Version, update.Actor, map[bool]string{true: "enable", false: "disable"}[update.Enabled], string(snapshot)); err != nil {
		return AlertRule{}, err
	}
	if err = tx.Commit(); err != nil {
		return AlertRule{}, err
	}
	return rule, nil
}

func alertActionTarget(status, action string) (string, error) {
	switch action {
	case "acknowledge":
		if status == "unprocessed" {
			return "processing", nil
		}
	case "close":
		if status == "unprocessed" || status == "processing" || status == "recovered" {
			return "closed", nil
		}
	case "ignore":
		if status == "unprocessed" || status == "processing" {
			return "ignored", nil
		}
	}
	return "", clientError{Code: "ALERT_ACTION_NOT_ALLOWED", Message: "当前状态不允许该处置动作"}
}

func (s *ProductionStore) ActOnAlert(ctx context.Context, id string, request AlertActionRequest) (AlertEvent, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return AlertEvent{}, err
	}
	tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return AlertEvent{}, err
	}
	defer tx.Rollback()
	var status string
	var version int
	if err = tx.QueryRowContext(ctx, `SELECT status,version FROM vehicle_alert_event WHERE id=? FOR UPDATE`, id).Scan(&status, &version); err == sql.ErrNoRows {
		return AlertEvent{}, clientError{Code: "ALERT_EVENT_NOT_FOUND", Message: "告警事件不存在"}
	} else if err != nil {
		return AlertEvent{}, err
	}
	if version != request.Version {
		return AlertEvent{}, clientError{Code: "ALERT_EVENT_VERSION_CONFLICT", Message: "事件已被其他用户更新，请刷新后重试"}
	}
	target, err := alertActionTarget(status, request.Action)
	if err != nil {
		return AlertEvent{}, err
	}
	res, err := tx.ExecContext(ctx, `UPDATE vehicle_alert_event SET status=?,handler=?,version=version+1 WHERE id=? AND version=?`, target, request.Actor, id, version)
	if err != nil {
		return AlertEvent{}, err
	}
	n, _ := res.RowsAffected()
	if n != 1 {
		return AlertEvent{}, clientError{Code: "ALERT_EVENT_VERSION_CONFLICT", Message: "事件更新冲突，请刷新后重试"}
	}
	if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_alert_event_action(event_id,action,from_status,to_status,actor,note) VALUES(?,?,?,?,?,?)`, id, request.Action, status, target, request.Actor, request.Note); err != nil {
		return AlertEvent{}, err
	}
	if err = tx.Commit(); err != nil {
		return AlertEvent{}, err
	}
	return s.AlertEvent(ctx, id)
}

func (s *ProductionStore) AlertNotifications(ctx context.Context, query AlertNotificationQuery) (Page[AlertNotification], error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return Page[AlertNotification]{}, err
	}
	where := "channel='in_app'"
	if query.UnreadOnly {
		where += " AND is_read=0"
	}
	var total int
	if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_alert_notification WHERE `+where).Scan(&total); err != nil {
		return Page[AlertNotification]{}, err
	}
	rows, err := s.db.QueryContext(ctx, `SELECT id,event_id,title,content,severity,channel,is_read,DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%s.%fZ'),COALESCE(DATE_FORMAT(read_at,'%Y-%m-%dT%H:%i:%s.%fZ'),'') FROM vehicle_alert_notification WHERE `+where+` ORDER BY created_at DESC,id DESC LIMIT ? OFFSET ?`, query.Limit, query.Offset)
	if err != nil {
		return Page[AlertNotification]{}, err
	}
	defer rows.Close()
	items := []AlertNotification{}
	for rows.Next() {
		var item AlertNotification
		if err := rows.Scan(&item.ID, &item.EventID, &item.Title, &item.Content, &item.Severity, &item.Channel, &item.Read, &item.CreatedAt, &item.ReadAt); err != nil {
			return Page[AlertNotification]{}, err
		}
		items = append(items, item)
	}
	return Page[AlertNotification]{Items: items, Total: total, Limit: query.Limit, Offset: query.Offset}, rows.Err()
}

func (s *ProductionStore) MarkAlertNotificationsRead(ctx context.Context, request AlertNotificationReadRequest) (int, error) {
	if err := s.ensureAlertSchema(ctx); err != nil {
		return 0, err
	}
	placeholders := make([]string, len(request.IDs))
	args := make([]any, 0, len(request.IDs)+1)
	args = append(args, request.Actor)
	for i, id := range request.IDs {
		placeholders[i] = "?"
		args = append(args, id)
	}
	result, err := s.db.ExecContext(ctx, `UPDATE vehicle_alert_notification SET is_read=1,read_by=?,read_at=CURRENT_TIMESTAMP(3) WHERE channel='in_app' AND is_read=0 AND id IN (`+strings.Join(placeholders, ",")+`)`, args...)
	if err != nil {
		return 0, err
	}
	n, err := result.RowsAffected()
	return int(n), err
}
