package parser

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"

	coremodel "security-center-backend/internal/model"
	caasmmodel "security-center-backend/internal/modules/caasm/model"

	"gopkg.in/yaml.v3"
)

type KongParseResult struct {
	Assets []caasmmodel.APIRouteAsset
}

type kongConfigBlock struct {
	Domains           flexibleStringSliceMap `yaml:"domains"`
	IngressNamePrefix string                 `yaml:"ingressNamePrefix"`
	Ingresses         []kongIngress          `yaml:"ingresses"`
}

type kongIngress struct {
	NamespaceMap map[string]string `yaml:"-"`
	Services     []kongService     `yaml:"services"`
}

type kongService struct {
	ServiceName string              `yaml:"serviceName"`
	ServicePort flexibleInt         `yaml:"servicePort"`
	TestCases   flexibleStringSlice `yaml:"testCases"`
	Regex       flexibleStringSlice `yaml:"regex"`
	Plugins     flexibleStringSlice `yaml:"plugins"`
	External    flexibleBool        `yaml:"external"`
	StripPath   flexibleBool        `yaml:"stripPath"`
}

type flexibleInt struct {
	Value *int
}

type flexibleStringSlice []string

type flexibleStringSliceMap map[string][]string

type flexibleBool bool

func ParseKongYAML(rawYAML string) (*KongParseResult, error) {
	if strings.TrimSpace(rawYAML) == "" {
		return nil, errors.New("raw_yaml is required")
	}

	var blocks []kongConfigBlock
	if err := yaml.Unmarshal([]byte(rawYAML), &blocks); err != nil {
		return nil, fmt.Errorf("parse kong yaml: %w", err)
	}
	if len(blocks) == 0 {
		return nil, errors.New("kong yaml must contain at least one config block")
	}

	deduped := make(map[string]int)
	assets := make([]caasmmodel.APIRouteAsset, 0)
	for groupIndex, block := range blocks {
		domainsJSON, err := rawJSON(block.Domains)
		if err != nil {
			return nil, fmt.Errorf("encode domains for group %d: %w", groupIndex, err)
		}
		for ingressIndex, ingress := range block.Ingresses {
			namespaceJSON, err := rawJSON(ingress.NamespaceMap)
			if err != nil {
				return nil, fmt.Errorf("encode namespace map for group %d ingress %d: %w", groupIndex, ingressIndex, err)
			}
			for serviceIndex, svc := range ingress.Services {
				rawPlugins := strings.Join(svc.Plugins, ",")
				plugins := splitPlugins(rawPlugins)
				routes := appendRouteSpecs(nil, caasmmodel.APIRouteKindTestCase, svc.TestCases)
				routes = appendRouteSpecs(routes, caasmmodel.APIRouteKindRegex, svc.Regex)
				for routeIndex, route := range routes {
					routeKey := buildRouteKey(block, ingress, svc, route)
					if existingIndex, ok := deduped[routeKey]; ok {
						assets[existingIndex].OccurrenceCount++
						continue
					}
					deduped[routeKey] = len(assets)
					assets = append(assets, caasmmodel.APIRouteAsset{
						GroupIndex:        groupIndex,
						IngressIndex:      ingressIndex,
						ServiceIndex:      serviceIndex,
						RouteIndex:        routeIndex,
						RouteKind:         route.kind,
						RoutePath:         route.path,
						RouteKey:          routeKey,
						Domains:           domainsJSON,
						NamespaceMap:      namespaceJSON,
						IngressNamePrefix: strings.TrimSpace(block.IngressNamePrefix),
						ServiceName:       strings.TrimSpace(svc.ServiceName),
						ServicePort:       svc.ServicePort.Value,
						External:          bool(svc.External),
						StripPath:         bool(svc.StripPath),
						Plugins:           coremodel.StringArray(plugins),
						RawPlugins:        rawPlugins,
						OccurrenceCount:   1,
					})
				}
			}
		}
	}

	return &KongParseResult{Assets: assets}, nil
}

func (i *kongIngress) UnmarshalYAML(value *yaml.Node) error {
	type rawIngress struct {
		Services     []kongService     `yaml:"services"`
		Namespaces   map[string]string `yaml:"namespaces"`
		NamespaceMap map[string]string `yaml:"namespaceMap"`
		Namespace    map[string]string `yaml:"namespace"`
		Labels       map[string]string `yaml:"labels"`
		Annotations  map[string]string `yaml:"annotations"`
	}
	var raw rawIngress
	if err := value.Decode(&raw); err != nil {
		return err
	}

	namespaces := map[string]string{}
	mergeStringMap(namespaces, raw.Namespaces)
	mergeStringMap(namespaces, raw.NamespaceMap)
	mergeStringMap(namespaces, raw.Namespace)

	if value.Kind == yaml.MappingNode {
		for idx := 0; idx+1 < len(value.Content); idx += 2 {
			key := strings.TrimSpace(value.Content[idx].Value)
			if key == "" || isReservedIngressKey(key) {
				continue
			}
			if value.Content[idx+1].Kind == yaml.ScalarNode {
				val := strings.TrimSpace(value.Content[idx+1].Value)
				if val != "" {
					namespaces[key] = val
				}
			}
		}
	}

	i.NamespaceMap = namespaces
	i.Services = raw.Services
	return nil
}

func (b *flexibleBool) UnmarshalYAML(value *yaml.Node) error {
	if value.Kind == yaml.ScalarNode && strings.TrimSpace(value.Value) == "" {
		return nil
	}
	var parsed bool
	if err := value.Decode(&parsed); err == nil {
		*b = flexibleBool(parsed)
		return nil
	}
	text := strings.TrimSpace(value.Value)
	if text == "" {
		return nil
	}
	parsed, err := strconv.ParseBool(text)
	if err != nil {
		return fmt.Errorf("invalid boolean %q", text)
	}
	*b = flexibleBool(parsed)
	return nil
}

func (f *flexibleInt) UnmarshalYAML(value *yaml.Node) error {
	if value.Kind == yaml.ScalarNode && strings.TrimSpace(value.Value) == "" {
		return nil
	}
	var parsed int
	if err := value.Decode(&parsed); err == nil {
		f.Value = &parsed
		return nil
	}
	text := strings.TrimSpace(value.Value)
	if text == "" {
		return nil
	}
	parsed, err := strconv.Atoi(text)
	if err != nil {
		return fmt.Errorf("invalid integer %q", text)
	}
	f.Value = &parsed
	return nil
}

func (s *flexibleStringSlice) UnmarshalYAML(value *yaml.Node) error {
	switch value.Kind {
	case yaml.SequenceNode:
		values := make([]string, 0, len(value.Content))
		for _, item := range value.Content {
			values = append(values, strings.TrimSpace(item.Value))
		}
		*s = trimEmpty(values)
		return nil
	case yaml.ScalarNode:
		*s = trimEmpty(splitComma(value.Value))
		return nil
	case 0:
		return nil
	default:
		return fmt.Errorf("expected string or string array")
	}
}

func (m *flexibleStringSliceMap) UnmarshalYAML(value *yaml.Node) error {
	if value.Kind == 0 {
		return nil
	}
	if value.Kind != yaml.MappingNode {
		return fmt.Errorf("expected domains map")
	}
	out := make(map[string][]string)
	for idx := 0; idx+1 < len(value.Content); idx += 2 {
		key := strings.TrimSpace(value.Content[idx].Value)
		if key == "" {
			continue
		}
		var values flexibleStringSlice
		if err := values.UnmarshalYAML(value.Content[idx+1]); err != nil {
			return err
		}
		out[key] = []string(values)
	}
	*m = out
	return nil
}

type routeSpec struct {
	kind string
	path string
}

func appendRouteSpecs(routes []routeSpec, kind string, paths []string) []routeSpec {
	for _, path := range paths {
		path = strings.TrimSpace(path)
		if path == "" {
			continue
		}
		routes = append(routes, routeSpec{kind: kind, path: path})
	}
	return routes
}

func buildRouteKey(block kongConfigBlock, ingress kongIngress, svc kongService, route routeSpec) string {
	payload := map[string]interface{}{
		"domains":             map[string][]string(block.Domains),
		"namespace_map":       ingress.NamespaceMap,
		"ingress_name_prefix": strings.TrimSpace(block.IngressNamePrefix),
		"service_name":        strings.TrimSpace(svc.ServiceName),
		"service_port":        svc.ServicePort.Value,
		"route_kind":          route.kind,
		"route_path":          route.path,
		"external":            bool(svc.External),
		"strip_path":          bool(svc.StripPath),
		"plugins":             splitPlugins(strings.Join(svc.Plugins, ",")),
	}
	data, _ := json.Marshal(payload)
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}

func rawJSON(v interface{}) (coremodel.RawJSON, error) {
	data, err := json.Marshal(v)
	if err != nil {
		return nil, err
	}
	return coremodel.RawJSON(data), nil
}

func splitPlugins(raw string) []string {
	parts := trimEmpty(splitComma(raw))
	sort.Strings(parts)
	return parts
}

func splitComma(raw string) []string {
	if strings.TrimSpace(raw) == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		out = append(out, strings.TrimSpace(part))
	}
	return out
}

func trimEmpty(values []string) []string {
	out := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value != "" {
			out = append(out, value)
		}
	}
	return out
}

func isReservedIngressKey(key string) bool {
	switch key {
	case "services", "namespaces", "namespaceMap", "namespace", "labels", "annotations":
		return true
	default:
		return false
	}
}

func mergeStringMap(dst map[string]string, src map[string]string) {
	for key, value := range src {
		key = strings.TrimSpace(key)
		value = strings.TrimSpace(value)
		if key != "" && value != "" {
			dst[key] = value
		}
	}
}
