package render

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

	"github.com/tidbcloud/jiraport/pkg/clients/clinic"
	"github.com/tidbcloud/jiraport/pkg/clients/engineer"

	"github.com/spf13/viper"
)

type Render struct {
	tmpl *template.Template
}

const timeOutputLayout = "2006-01-02 15:04:05 MST"

func tmplFuncs(
	clinicClient clinic.Interface,
	engineerClient engineer.Interface,
) template.FuncMap {
	return 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)
		},
		"reAllSubmatch": func(pattern, text string) [][]string {
			re := regexp.MustCompile(pattern)
			return re.FindAllStringSubmatch(text, -1)
		},
		"unique": func(s []string) []string {
			set := make(map[string]struct{})
			for _, v := range s {
				set[v] = struct{}{}
			}
			us := make([]string, 0, len(set))
			for v := range set {
				us = append(us, v)
			}
			return us
		},
		"stringSlice": func(s ...string) []string {
			return s
		},
		"appendString": func(s []string, es ...string) []string {
			return append(s, es...)
		},
		"getAliasByTenant": func(tenantId string) string {
			alias, _ := clinicClient.GetAliasByTenantIDAndClusterID(tenantId, "")
			return alias
		},
		"getAliasByCluster": func(clusterId string) string {
			alias, _ := clinicClient.GetAliasByTenantIDAndClusterID("", clusterId)
			return alias
		},
		"getNameByCluster": func(clusterId string) string {
			name, _ := clinicClient.GetClusterName(clusterId)
			return name
		},
		"getDeployTypeByCluster": func(clusterId string) string {
			deployType, _ := clinicClient.GetDeployTypeByCluster(clusterId)
			return deployType
		},
		"getUserID": func(email string) string {
			engineer, _ := engineerClient.GetEngineerByEmail(email)
			return engineer.UserID
		},
		"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
		},
		"isValidClusterID": func(clusterID interface{}) bool {
			// Check if cluster ID is valid (numeric or UUID-like format)
			clusterStr, ok := clusterID.(string)
			if !ok {
				return false
			}

			if clusterStr == "" {
				return false
			}

			// Valid if it's all digits or contains hyphens (UUID format like dp-xxx-xxx)
			allDigits := true
			for _, c := range clusterStr {
				if (c < '0' || c > '9') && c != '-' {
					allDigits = false
					break
				}
			}
			return allDigits
		},
		"extractClusterIDFromAlertData": func(alertDataField interface{}) string {
			// alertDataField is the raw alert data JSON (customfield_10160)
			// We need to extract the numeric part from the namespace field

			alertStr, ok := alertDataField.(string)
			if !ok {
				return ""
			}

			// Parse the JSON to get the namespace
			var alertData map[string]interface{}
			err := json.Unmarshal([]byte(alertStr), &alertData)
			if err != nil {
				return ""
			}

			// Get labels from alert data
			labels, ok := alertData["labels"].(map[string]interface{})
			if !ok {
				return ""
			}

			// Get namespace from labels
			namespace, ok := labels["namespace"].(string)
			if !ok {
				return ""
			}

			// Extract numeric part from namespace (e.g., "tidb1445428518469785" -> "1445428518469785")
			// Remove leading non-digit characters
			i := 0
			for i < len(namespace) && (namespace[i] < '0' || namespace[i] > '9') {
				i++
			}
			if i < len(namespace) {
				return namespace[i:]
			}

			return ""
		},
	}
}

func New(
	clilicClient clinic.Interface,
	engineerClient engineer.Interface,
) (*Render, error) {
	tmplPath := viper.GetString("template-file")
	b, err := os.ReadFile(tmplPath)
	if err != nil {
		return nil, err
	}
	tmplStr := string(b)
	tmpl, err := template.New("").Funcs(tmplFuncs(clilicClient, engineerClient)).Parse(tmplStr)
	if err != nil {
		return nil, err
	}
	return &Render{tmpl: tmpl}, nil
}

func (r *Render) Render(tmplName string, data map[string]interface{}) (title string, text string, color string, err error) {
	data["DefaultTimeFormat"] = timeOutputLayout

	textTmplName := tmplName + ".text"
	titleTmplName := tmplName + ".title"
	colorTmplName := tmplName + ".color"

	var buf bytes.Buffer
	err = r.tmpl.ExecuteTemplate(&buf, textTmplName, data)
	if err != nil {
		return "", "", "", err
	}
	// text = strings.ReplaceAll(buf.String(), "\n", "\\n")
	text = buf.String()

	buf.Reset()
	err = r.tmpl.ExecuteTemplate(&buf, titleTmplName, data)
	if err != nil {
		return "", "", "", err
	}
	// title := strings.ReplaceAll(buf.String(), "\n", "\\n")
	title = buf.String()

	buf.Reset()
	err = r.tmpl.ExecuteTemplate(&buf, colorTmplName, data)
	if err != nil {
		return "", "", "", err
	}
	color = buf.String()

	return title, text, color, nil
}
