package matcher

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strings"
	"text/template"

	"gopkg.in/yaml.v3"

	"jiraport/pkg/log"
)

var CommonFuncs = template.FuncMap{
	"toUpper": strings.ToUpper,
	"toLower": strings.ToLower,
	"join": func(sep string, s []string) string {
		return strings.Join(s, sep)
	},
	"reMatch": func(pattern, text string) bool {
		re := regexp.MustCompile(pattern)
		return re.MatchString(text)
	},
	"reReplaceAll": func(pattern, repl, text string) string {
		re := regexp.MustCompile(pattern)
		return re.ReplaceAllString(text, repl)
	},
	"reSubmatch": func(pattern, text string) []string {
		re := regexp.MustCompile(pattern)
		return re.FindStringSubmatch(text)
	},
	"stringSlice": func(s ...string) []string {
		return s
	},
	"tryUnmarshalJson": func(j interface{}) interface{} {
		jsonStr, ok := j.(string)
		if !ok {
			return nil
		}

		var jsonVal interface{}
		err := json.Unmarshal([]byte(jsonStr), &jsonVal)
		if err != nil {
			return nil
		}
		return jsonVal
	},
}

type Extractor struct {
	rawTemplate string
	Template    *template.Template
}

func (e *Extractor) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var rawTemplate string
	err := unmarshal(&rawTemplate)
	if err != nil {
		return err
	}

	e.rawTemplate = rawTemplate

	tmpl := template.New("").Option("missingkey=zero").Funcs(CommonFuncs)
	tmpl, err = tmpl.Parse(e.rawTemplate)
	if err != nil {
		return fmt.Errorf("parse error in %#v, %w", e.rawTemplate, err)
	}

	e.Template = tmpl

	return err
}

func (e Extractor) MarshalText() (text []byte, err error) {
	return []byte(e.rawTemplate), nil
}

type Extractors map[string]Extractor

type Receiver interface{}

type Receivers []Receiver

var ReceiverTypeError = errors.New("type unmatch, can not transmute receivers")

func (rs *Receivers) TryToIntSlice() ([]int, error) {
	raw := make([]int, 0)
	for _, r := range *rs {
		intR, ok := r.(int)
		if !ok {
			return nil, ReceiverTypeError
		}
		raw = append(raw, intR)
	}
	return raw, nil
}

func (rs *Receivers) TryToStringSlice() ([]string, error) {
	raw := make([]string, 0)
	for _, r := range *rs {
		intR, ok := r.(string)
		if !ok {
			return nil, ReceiverTypeError
		}
		raw = append(raw, intR)
	}
	return raw, nil
}

type ReMatcher struct {
	rawRegexp string
	Regexp    *regexp.Regexp
}

func (rm *ReMatcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var rawRegexp string
	err := unmarshal(&rawRegexp)
	if err != nil {
		return err
	}

	rm.rawRegexp = rawRegexp

	re, err := regexp.Compile(rm.rawRegexp)
	if err != nil {
		return err
	}

	rm.Regexp = re

	return err
}

func (rm ReMatcher) MarshalText() (text []byte, err error) {
	return []byte(rm.rawRegexp), nil
}

type Query struct {
	Negative bool
	Label    string
}

func (q *Query) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var rawQuery string
	err := unmarshal(&rawQuery)
	if err != nil {
		return err
	}
	q.Label = strings.TrimPrefix(rawQuery, "!")
	if q.Label == rawQuery {
		q.Negative = false
	} else {
		q.Negative = true
	}
	return err
}

func (q Query) MarshalText() (text []byte, err error) {
	if q.Negative {
		return []byte("!" + q.Label), nil
	} else {
		return []byte(q.Label), nil
	}
}

type Route struct {
	Receiver Receiver            `yaml:"receiver" json:"receiver"`
	Continue bool                `yaml:"continue" json:"continue"`
	Match    map[Query]ReMatcher `yaml:"match" json:"match"`
}

type Routes []Route

type Matcher struct {
	Extractors Extractors `yaml:"extractors" json:"extractors"`
	Routes     Routes     `yaml:"routes" json:"routes"`
}

func (m *Matcher) Valid() error {
	for _, r := range m.Routes {
		for q := range r.Match {
			_, ok := m.Extractors[q.Label]
			if !ok {
				return fmt.Errorf("label %#v not found in extractors", q.Label)
			}
		}
	}
	return nil
}

func (m *Matcher) Match(target interface{}) (Receivers, error) {
	receivers := make([]Receiver, 0)
	receiverSet := make(map[Receiver]struct{}, 0)
	extraced := make(map[string]string)
	for _, r := range m.Routes {
		isMatch := true
		for q, rm := range r.Match {
			l := q.Label
			qVal, ok := extraced[l]
			if !ok {
				e, ok := m.Extractors[l]
				if !ok {
					return nil, fmt.Errorf("label %#v not found in extractors", l)
				}
				var buf bytes.Buffer
				err := e.Template.Execute(&buf, target)
				if err != nil {
					return nil, err
				}
				qVal = buf.String()
				extraced[l] = qVal
				log.Debug("extractor", e.rawTemplate, "extraced", qVal)
			}
			if rm.Regexp.MatchString(qVal) != !q.Negative {
				isMatch = false
				break
			}
		}
		if isMatch {
			// dedup receivers and keep order
			if _, ok := receiverSet[r.Receiver]; !ok {
				receiverSet[r.Receiver] = struct{}{}
				receivers = append(receivers, r.Receiver)
			}
			if !r.Continue {
				break
			}
		}
	}
	return receivers, nil
}

func (m *Matcher) LoadFile(filepath string) error {
	mtcherBytes, err := os.ReadFile(filepath)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal(mtcherBytes, m)
	if err != nil {
		return err
	}
	err = m.Valid()
	if err != nil {
		return err
	}
	return nil
}
