package support_web

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/amir20/dozzle/internal/container"
	"github.com/rs/zerolog/log"
	orderedmap "github.com/wk8/go-ordered-map/v2"
)

// PatternMatcher defines the interface for a regex pattern matcher
type PatternMatcher struct {
	Regex       *regexp.Regexp
	MarkerStart string
	MarkerEnd   string
}

// NewPatternMatcher creates a new pattern matcher with the specified regex and markers
func NewPatternMatcher(re *regexp.Regexp, markerStart, markerEnd string) *PatternMatcher {
	return &PatternMatcher{
		Regex:       re,
		MarkerStart: markerStart,
		MarkerEnd:   markerEnd,
	}
}

// CreateRegex compiles a regex pattern with optional case-insensitive flag
func CreateRegex(pattern string, caseInsensitive bool) (*regexp.Regexp, error) {
	flags := ""
	if caseInsensitive || pattern == strings.ToLower(pattern) {
		flags = "(?i)"
	}

	re, err := regexp.Compile(flags + pattern)
	if err != nil {
		log.Debug().Err(err).Str("pattern", pattern).Msg("failed to compile regex")
		return nil, err
	}

	return re, nil
}

// MarkInLogEvent applies the pattern matcher to the log event's message
func (pm *PatternMatcher) MarkInLogEvent(logEvent *container.LogEvent) bool {
	switch value := logEvent.Message.(type) {
	case string:
		if pm.Regex.MatchString(value) {
			logEvent.Message = pm.Regex.ReplaceAllString(value, pm.MarkerStart+"$0"+pm.MarkerEnd)
			return true
		}

	case []container.LogFragment:
		found := false
		for i, fragment := range value {
			if pm.Regex.MatchString(fragment.Message) {
				value[i].Message = pm.Regex.ReplaceAllString(fragment.Message, pm.MarkerStart+"$0"+pm.MarkerEnd)
				found = true
			}
		}
		return found

	case *orderedmap.OrderedMap[string, any]:
		return pm.markMapAny(value)

	case *orderedmap.OrderedMap[string, string]:
		return pm.markMapString(value)

	case map[string]interface{}:
		return pm.markMap(value)

	case map[string]string:
		panic("not implemented")

	default:
		log.Debug().Type("type", value).Msg("unknown logEvent type")
	}

	return false
}

func (pm *PatternMatcher) markMapAny(orderedMap *orderedmap.OrderedMap[string, any]) bool {
	found := false
	for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
		switch value := pair.Value.(type) {
		case string:
			if replaced, matched := pm.markString(value); matched {
				found = true
				orderedMap.Set(pair.Key, replaced)
			}

		case []any:
			if pm.markArray(value) {
				found = true
			}

		case *orderedmap.OrderedMap[string, any]:
			if pm.markMapAny(value) {
				found = true
			}

		case *orderedmap.OrderedMap[string, string]:
			if pm.markMapString(value) {
				found = true
			}

		case map[string]interface{}:
			if pm.markMap(value) {
				found = true
			}

		case int, float64, bool:
			formatted := fmt.Sprintf("%v", value)
			if pm.Regex.MatchString(formatted) {
				orderedMap.Set(pair.Key, pm.Regex.ReplaceAllString(formatted, pm.MarkerStart+"$0"+pm.MarkerEnd))
				found = true
			}

		default:
			log.Debug().Type("type", value).Msg("unknown logEvent type inside markMapAny")
		}
	}

	return found
}

func (pm *PatternMatcher) markMap(data map[string]interface{}) bool {
	found := false
	for key, value := range data {
		switch value := value.(type) {
		case string:
			if replaced, matched := pm.markString(value); matched {
				found = true
				data[key] = replaced
			}
		case []any:
			if pm.markArray(value) {
				found = true
			}

		case map[string]interface{}:
			if pm.markMap(value) {
				found = true
			}

		case int, float64, bool:
			formatted := fmt.Sprintf("%v", value)
			if pm.Regex.MatchString(formatted) {
				data[key] = pm.Regex.ReplaceAllString(formatted, pm.MarkerStart+"$0"+pm.MarkerEnd)
				found = true
			}
		default:
			log.Debug().Type("type", value).Msg("unknown logEvent type inside markMap")
		}
	}

	return found
}

func (pm *PatternMatcher) markMapString(orderedMap *orderedmap.OrderedMap[string, string]) bool {
	found := false
	for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
		if replaced, matched := pm.markString(pair.Value); matched {
			found = true
			orderedMap.Set(pair.Key, replaced)
		}
	}
	return found
}

func (pm *PatternMatcher) markArray(data []any) bool {
	found := false
	for i, value := range data {
		switch value := value.(type) {
		case string:
			if replaced, matched := pm.markString(value); matched {
				found = true
				data[i] = replaced
			}
		case int, float64, bool:
			formatted := fmt.Sprintf("%v", value)
			if pm.Regex.MatchString(formatted) {
				data[i] = pm.Regex.ReplaceAllString(formatted, pm.MarkerStart+"$0"+pm.MarkerEnd)
				found = true
			}
		case []any:
			if pm.markArray(value) {
				found = true
			}
		case map[string]interface{}:
			if pm.markMap(value) {
				found = true
			}
		}
	}

	return found
}

func (pm *PatternMatcher) markString(value string) (string, bool) {
	if pm.Regex.MatchString(value) {
		replaced := pm.Regex.ReplaceAllString(value, pm.MarkerStart+"$0"+pm.MarkerEnd)
		return replaced, true
	}

	return value, false
}
