package topics

import (
	"fmt"
	"sort"
	"strings"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)

const (
	RawGB32960       = "vehicle.raw.go.gb32960.v1"
	RawJT808         = "vehicle.raw.go.jt808.v1"
	RawYutongMQTT    = "vehicle.raw.go.yutong-mqtt.v1"
	FieldsGB32960    = "vehicle.fields.go.gb32960.v1"
	FieldsJT808      = "vehicle.fields.go.jt808.v1"
	FieldsYutongMQTT = "vehicle.fields.go.yutong-mqtt.v1"
	Unified          = "vehicle.event.go.unified.v1"
)

const (
	RawPrefix    = "vehicle.raw."
	FieldsPrefix = "vehicle.fields."
)

func ProtocolForKnownRawTopic(topic string) (string, bool) {
	switch strings.TrimSpace(topic) {
	case RawGB32960:
		return "GB32960", true
	case RawJT808:
		return "JT808", true
	case RawYutongMQTT:
		return "YUTONG_MQTT", true
	default:
		return "", false
	}
}

func ProtocolForKnownFieldsTopic(topic string) (string, bool) {
	switch strings.TrimSpace(topic) {
	case FieldsGB32960:
		return "GB32960", true
	case FieldsJT808:
		return "JT808", true
	case FieldsYutongMQTT:
		return "YUTONG_MQTT", true
	default:
		return "", false
	}
}

func ValidateKnownRawTopicProtocol(topic string, protocol string) error {
	expectedProtocol, ok := ProtocolForKnownRawTopic(topic)
	if !ok {
		return nil
	}
	protocol = strings.TrimSpace(protocol)
	if protocol == expectedProtocol {
		return nil
	}
	if protocol == "" {
		protocol = "UNKNOWN"
	}
	return fmt.Errorf("raw topic %q expects protocol %s, got %s", strings.TrimSpace(topic), expectedProtocol, protocol)
}

func ValidateKnownFieldsTopicProtocol(topic string, protocol string) error {
	expectedProtocol, ok := ProtocolForKnownFieldsTopic(topic)
	if !ok {
		return nil
	}
	protocol = strings.TrimSpace(protocol)
	if protocol == expectedProtocol {
		return nil
	}
	if protocol == "" {
		protocol = "UNKNOWN"
	}
	return fmt.Errorf("fields topic %q expects protocol %s, got %s", strings.TrimSpace(topic), expectedProtocol, protocol)
}

func ValidateRawEnvelope(topic string, env envelope.FrameEnvelope) (string, error) {
	if err := ValidateKnownRawTopicProtocol(topic, string(env.Protocol)); err != nil {
		return "protocol_topic_mismatch", err
	}
	if strings.HasPrefix(strings.TrimSpace(topic), RawPrefix) {
		switch env.EventKind {
		case "", envelope.EventKindRaw:
			return "", nil
		default:
			return "event_kind_mismatch", fmt.Errorf("raw topic %q expects event kind %s, got %s", strings.TrimSpace(topic), envelope.EventKindRaw, env.EventKind)
		}
	}
	return "", nil
}

func ValidateFieldsEnvelope(topic string, env envelope.FrameEnvelope) (string, error) {
	if err := ValidateKnownFieldsTopicProtocol(topic, string(env.Protocol)); err != nil {
		return "protocol_topic_mismatch", err
	}
	if strings.HasPrefix(strings.TrimSpace(topic), FieldsPrefix) {
		if env.EventKind != envelope.EventKindFields {
			return "event_kind_mismatch", fmt.Errorf("fields topic %q expects event kind %s, got %s", strings.TrimSpace(topic), envelope.EventKindFields, env.EventKind)
		}
		if strings.TrimSpace(env.FieldMapping) == "" {
			return "missing_field_mapping", fmt.Errorf("fields topic %q expects non-empty field mapping", strings.TrimSpace(topic))
		}
		if len(env.Fields) == 0 {
			return "missing_fields", fmt.Errorf("fields topic %q expects non-empty fields payload", strings.TrimSpace(topic))
		}
		if err := ValidateProtocolFieldNames(env.Protocol, env.Fields); err != nil {
			return "invalid_field_name", err
		}
	}
	return "", nil
}

func ValidateProtocolFieldNames(protocol envelope.Protocol, fields map[string]any) error {
	prefix, ok := protocolFieldPrefix(protocol)
	if !ok {
		return nil
	}
	invalid := make([]string, 0)
	for key := range fields {
		trimmed := strings.TrimSpace(key)
		if key == trimmed && len(trimmed) > len(prefix) && strings.HasPrefix(trimmed, prefix) {
			continue
		}
		invalid = append(invalid, key)
	}
	if len(invalid) == 0 {
		return nil
	}
	sort.Strings(invalid)
	if len(invalid) > 3 {
		invalid = invalid[:3]
	}
	return fmt.Errorf("fields protocol %s expects every field name under %q; invalid fields: %q", protocol, prefix, invalid)
}

func protocolFieldPrefix(protocol envelope.Protocol) (string, bool) {
	switch protocol {
	case envelope.ProtocolGB32960:
		return "gb32960.", true
	case envelope.ProtocolJT808:
		return "jt808.", true
	case envelope.ProtocolYutongMQTT:
		return "yutong_mqtt.", true
	default:
		return "", false
	}
}

func ValidateKnownRawFieldsProtocols(raw map[string]string, fields map[string]string, valueName string) error {
	for protocol, value := range raw {
		if err := ValidateKnownRawTopicProtocol(value, protocol); err != nil {
			return fmt.Errorf("raw %s for %s must match protocol: %w", valueName, protocol, err)
		}
	}
	for protocol, value := range fields {
		if err := ValidateKnownFieldsTopicProtocol(value, protocol); err != nil {
			return fmt.Errorf("fields %s for %s must match protocol: %w", valueName, protocol, err)
		}
	}
	return nil
}

func ValidateKafkaRawFields(raw map[string]string, fields map[string]string) error {
	for name, topic := range raw {
		topic = strings.TrimSpace(topic)
		if topic == "" {
			return fmt.Errorf("raw kafka topic for %s is empty", name)
		}
		if !strings.HasPrefix(topic, RawPrefix) {
			return fmt.Errorf("raw kafka topic for %s must start with %q, got %q", name, RawPrefix, topic)
		}
	}
	for name, topic := range fields {
		topic = strings.TrimSpace(topic)
		if topic == "" {
			return fmt.Errorf("fields kafka topic for %s is empty", name)
		}
		if !strings.HasPrefix(topic, FieldsPrefix) {
			return fmt.Errorf("fields kafka topic for %s must start with %q, got %q", name, FieldsPrefix, topic)
		}
	}
	if err := ValidateKnownRawFieldsProtocols(raw, fields, "kafka topic"); err != nil {
		return err
	}
	return ValidateRawFieldsDisjoint(raw, fields, "kafka topic")
}

func ValidateRawFieldsDisjoint(raw map[string]string, fields map[string]string, valueName string) error {
	rawByValue := map[string]string{}
	for name, value := range raw {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		rawByValue[value] = name
	}
	for fieldsName, value := range fields {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if rawName, ok := rawByValue[value]; ok {
			return fmt.Errorf("raw and fields %s must be different: raw %s and fields %s both use %q", valueName, rawName, fieldsName, value)
		}
	}
	return nil
}
