package entity

import (
	"bytes"
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"strings"
	"text/template"
	"time"

	"github.com/tidbcloud/jiraport/pkg/matcher"

	"github.com/andygrunwald/go-jira"
	"github.com/spf13/viper"
)

type Issue struct {
	Key             string    `gorm:"primaryKey;type:varchar(255)"`
	URL             string    `gorm:"url;type:varchar(255)"`
	ReporterEmail   string    `gorm:"type:varchar(255)"`
	AssigneeEmail   string    `gorm:"type:varchar(255)"`
	Component       string    `gorm:"type:varchar(255)"`
	Priority        string    `gorm:"type:varchar(64)"`
	Status          string    `gorm:"type:varchar(64)"`
	Summary         string    `gorm:"type:varchar(255)"`
	Description     string    `gorm:"type:text"`
	CreateTime      time.Time `gorm:"type:timestamp"`
	RawIssue        RawIssue  `gorm:"type:longtext"`
	ReceivedVersion int64
}

type Issues []Issue

// AlertLabel returns one label of the alert this issue was filed for, read from
// the raw alert JSON in customfield_10160. The second return value reports
// whether the label is set at all — a rule may set a label to an empty value,
// and for some labels that is still a signal. It is false for a non-alert issue
// or an unparseable field.
func (i *Issue) AlertLabel(name string) (string, bool) {
	alertData, ok := i.RawIssue.Fields.Unknowns["customfield_10160"].(string)
	if !ok {
		return "", false
	}
	var parsed struct {
		Labels map[string]string `json:"labels"`
	}
	if err := json.Unmarshal([]byte(alertData), &parsed); err != nil {
		return "", false
	}
	value, ok := parsed.Labels[name]
	return strings.TrimSpace(value), ok
}

// OncallTeam returns the alert's labels.oncall_team value (from customfield_10160),
// or "" if absent/unparseable. Used to route leader/escalation to the team's
// oncall managers.
func (i *Issue) OncallTeam() string {
	team, _ := i.AlertLabel("oncall_team")
	return team
}

type RawIssue jira.Issue

// read IssueExtraData of json string format from database
func (a *RawIssue) Scan(value interface{}) error {
	bytes, ok := value.([]byte)
	if !ok {
		return fmt.Errorf("unsupported value: %v", value)
	}

	result := RawIssue{}

	err := json.Unmarshal(bytes, &result)
	*a = result
	return err
}

// store IssueExtraData as json string in database
func (a RawIssue) Value() (driver.Value, error) {
	b, err := json.Marshal(a)
	if err != nil {
		return nil, err
	}
	s := string(b)
	return s, err
}

// convert andygrunwald's go-jira Issue to jiraport's Issue
func FromGoJiraIssue(jiraIssue *jira.Issue) (*Issue, error) {
	fields := jiraIssue.Fields
	component := make([]string, 0, len(fields.Components))
	for i := range fields.Components {
		component = append(component, fields.Components[i].Name)
	}
	componentStr := strings.Join(component, ",")
	return &Issue{
		Key:           jiraIssue.Key,
		URL:           jiraIssue.Self,
		Summary:       fields.Summary,
		Description:   fields.Description,
		ReporterEmail: fields.Reporter.EmailAddress,
		AssigneeEmail: fields.Assignee.EmailAddress,
		Component:     componentStr,
		Priority:      fields.Priority.Name,
		Status:        fields.Status.Name,
		CreateTime:    time.Time(fields.Created),
		RawIssue:      RawIssue(*jiraIssue),
	}, nil
}

func (a Issue) GetTenant() string {
	var tenantID string
	parseExtractor := func(e string) (*template.Template, error) {
		tmpl := template.New("").Option("missingkey=zero").Funcs(matcher.CommonFuncs)
		tmpl, err := tmpl.Parse(e)
		if err != nil {
			return nil, fmt.Errorf("parse error in %#v, %w", e, err)
		}
		return tmpl, nil
	}
	tenantExtractor, err := parseExtractor(viper.GetString("tenant-extractor"))
	if err == nil {
		var buf bytes.Buffer
		err := tenantExtractor.Execute(&buf, a.RawIssue)
		if err == nil {
			tenantID = buf.String()
		}
	}
	return tenantID
}

func (a Issue) GetFBOwnerEmail() string {
	var fbOwnerEmail string
	parseExtractor := func(e string) (*template.Template, error) {
		tmpl := template.New("").Option("missingkey=zero").Funcs(matcher.CommonFuncs)
		tmpl, err := tmpl.Parse(e)
		if err != nil {
			return nil, fmt.Errorf("parse error in %#v, %w", e, err)
		}
		return tmpl, nil
	}
	fbownerExtractor, err := parseExtractor(viper.GetString("fbowner-extractor"))
	if err == nil {
		var buf bytes.Buffer
		err := fbownerExtractor.Execute(&buf, a.RawIssue)
		if err == nil {
			fbOwnerEmail = buf.String()
		}
	}
	return fbOwnerEmail
}

func (a Issue) GetCluster() string {
	var cluster string
	parseExtractor := func(e string) (*template.Template, error) {
		tmpl := template.New("").Option("missingkey=zero").Funcs(matcher.CommonFuncs)
		tmpl, err := tmpl.Parse(e)
		if err != nil {
			return nil, fmt.Errorf("parse error in %#v, %w", e, err)
		}
		return tmpl, nil
	}
	clusterExtractor, err := parseExtractor(viper.GetString("cluster-extractor"))
	if err == nil {
		var buf bytes.Buffer
		err := clusterExtractor.Execute(&buf, a.RawIssue)
		if err == nil {
			cluster = buf.String()
		}
	}
	return cluster
}
