package webhooks

import (
	"net/http"
	"regexp"
	"strings"

	"github.com/OliveTin/OliveTin/internal/config"
	log "github.com/sirupsen/logrus"
)

type WebhookMatcher struct {
	config    config.WebhookConfig
	req       *http.Request
	bodyBytes []byte
}

func NewWebhookMatcher(cfg config.WebhookConfig, r *http.Request, bodyBytes []byte) *WebhookMatcher {
	return &WebhookMatcher{
		config:    cfg,
		req:       r,
		bodyBytes: bodyBytes,
	}
}

func (m *WebhookMatcher) Matches() bool {
	if !m.matchHeaders() {
		return false
	}

	if !m.matchQuery() {
		return false
	}

	if !m.matchPath() {
		return false
	}

	return true
}

func (m *WebhookMatcher) matchHeaders() bool {
	if len(m.config.MatchHeaders) == 0 {
		return true
	}

	for key, expectedValue := range m.config.MatchHeaders {
		actualValue := m.req.Header.Get(key)
		if !m.compareValues(actualValue, expectedValue) {
			log.WithFields(log.Fields{
				"header":   key,
				"expected": expectedValue,
				"actual":   actualValue,
			}).Debugf("Header mismatch")
			return false
		}
	}
	return true
}

func (m *WebhookMatcher) matchQuery() bool {
	if len(m.config.MatchQuery) == 0 {
		return true
	}

	query := m.req.URL.Query()
	for key, expectedValue := range m.config.MatchQuery {
		actualValue := query.Get(key)
		if !m.compareValues(actualValue, expectedValue) {
			log.WithFields(log.Fields{
				"query":    key,
				"expected": expectedValue,
				"actual":   actualValue,
			}).Debugf("Query parameter mismatch")
			return false
		}
	}
	return true
}

func (m *WebhookMatcher) matchPath() bool {
	if m.config.MatchPath == "" {
		return true
	}

	jsonPath, expectedValue := m.parseMatchPath()
	matcher, err := NewJSONMatcher(m.bodyBytes)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Debugf("Failed to create JSON matcher")
		return false
	}

	return m.matchPathValue(matcher, jsonPath, expectedValue)
}

func (m *WebhookMatcher) parseMatchPath() (string, string) {
	parts := strings.SplitN(m.config.MatchPath, "=", 2)
	jsonPath := parts[0]
	expectedValue := ""
	if len(parts) == 2 {
		expectedValue = parts[1]
	}
	return jsonPath, expectedValue
}

func (m *WebhookMatcher) matchPathValue(matcher *JSONMatcher, jsonPath, expectedValue string) bool {
	if expectedValue == "" {
		_, err := matcher.ExtractValue(jsonPath)
		return err == nil
	}

	matches, err := matcher.MatchPath(jsonPath, expectedValue)
	if err != nil {
		log.WithFields(log.Fields{
			"jsonPath": jsonPath,
			"error":    err,
		}).Debugf("Failed to match JSONPath")
		return false
	}
	return matches
}

func (m *WebhookMatcher) compareValues(actual, expected string) bool {
	if pattern, hasRegex := strings.CutPrefix(expected, "regex:"); hasRegex {
		matched, err := regexp.MatchString(pattern, actual)
		if err != nil {
			log.WithFields(log.Fields{
				"pattern": pattern,
				"error":   err,
			}).Warnf("Invalid regex pattern")
			return false
		}
		return matched
	}
	return actual == expected
}

func (m *WebhookMatcher) ExtractJustification() (string, error) {
	if m.config.Justification == "" {
		return "", nil
	}

	matcher, err := NewJSONMatcher(m.bodyBytes)
	if err != nil {
		return "", err
	}

	return matcher.ExtractValue(m.config.Justification)
}

func (m *WebhookMatcher) ExtractArguments() (map[string]string, error) {
	matcher, err := NewJSONMatcher(m.bodyBytes)
	if err != nil {
		return nil, err
	}

	args := m.extractJSONPathValues(matcher)
	m.addWebhookMetadata(args)
	m.addWebhookHeaders(args)

	return args, nil
}

func (m *WebhookMatcher) extractJSONPathValues(matcher *JSONMatcher) map[string]string {
	args := make(map[string]string)

	for argName, jsonPath := range m.config.Extract {
		value, err := matcher.ExtractValue(jsonPath)
		if err != nil {
			log.WithFields(log.Fields{
				"argName":  argName,
				"jsonPath": jsonPath,
				"error":    err,
			}).Debugf("Failed to extract value")
			continue
		}
		args[argName] = value
	}

	return args
}

func (m *WebhookMatcher) addWebhookMetadata(args map[string]string) {
	args["webhook_method"] = m.req.Method
	args["webhook_path"] = m.req.URL.Path
	args["webhook_query"] = m.req.URL.RawQuery
}

func (m *WebhookMatcher) addWebhookHeaders(args map[string]string) {
	for key, values := range m.req.Header {
		if len(values) > 0 {
			args["webhook_header_"+strings.ToLower(key)] = values[0]
		}
	}
}
