package experiment

import (
	"context"
	"fmt"
	"time"

	"github.com/andygrunwald/go-jira"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/manager/template"
	sjira "github.com/pingcap/schrodinger/pkg/jira"
	"github.com/pingcap/schrodinger/pkg/types"
	"github.com/pingcap/schrodinger/util"
	"github.com/trivago/tgo/tcontainer"
)

const (
	testGroup             sjira.FieldKey = "Test group"
	testName                             = "Test name"
	testType                             = "Test type"
	testStartTime                        = "Test start time"
	testEndTime                          = "Test end time"
	testTerminationStatus                = "Test termination status"
	testCaseName                         = "Test case name"
	testCaseKey                          = "Test case key"
	failureCaseName                      = "Failure case name"
	failureCaseKey                       = "Failure case key"
	testingObjectName                    = "Testing object Name"
	testingObjectVersion                 = "Testing object version"
	tidbVersion                          = "TiDB version"
	tikvVersion                          = "TiKV version"
	pdVersion                            = "PD version"
	testDetail                           = "Test detail"
	experimentID                         = "Experiment ID"
	experimentOwner                      = "Experiment owner"
)

// CustomField defines some custom fields for recordIssue
var CustomField = []sjira.FieldKey{
	testGroup,
	testName,
	testType,
	testStartTime,
	testEndTime,
	testTerminationStatus,
	testCaseName,
	testCaseKey,
	failureCaseName,
	failureCaseKey,
	testingObjectName,
	testingObjectVersion,
	tidbVersion,
	tikvVersion,
	pdVersion,
	testDetail,
	experimentID,
	experimentOwner,
}

// RecordIssue defines a jira issue for experiment report
type RecordIssue struct {
	issue *jira.Issue

	cli *sjira.Jira
}

// CreateRecordIssue creates a record issue for experiment
func CreateRecordIssue(ctx context.Context, expr *types.ExperimentProcess, cli *sjira.Jira) (*RecordIssue, error) {
	fields := &jira.IssueFields{
		Summary: expr.Name,
		Type: jira.IssueType{
			Name: cli.Config().IssueType,
		},
		Project: jira.Project{
			Key: cli.Config().Project,
		},
		Unknowns: tcontainer.NewMarshalMap(),
	}

	initCustomFields(expr, cli, fields)

	issue := &jira.Issue{
		Fields: fields,
	}

	err := util.RetryOnError2(ctx, 5, 2*time.Second, func() (e error, b bool) {
		if err := cli.CreateIssue(issue); err != nil {
			if util.CheckIsNetworkError(err) {
				return errors.Trace(err), false
			}
			return errors.Trace(err), true
		}
		return nil, true
	})
	if err != nil {
		log.Errorf("create jira issue %+v failed, error: %v", issue, err)
		return nil, errors.Trace(err)
	}

	recordIssue := &RecordIssue{
		issue: issue,
		cli:   cli,
	}

	jql := fmt.Sprintf("project = '%s' AND '%s' ~  '%d'",
		issue.Fields.Project.Key, experimentID, expr.ID)

	err = util.RetryOnError2(ctx, 5, 2*time.Second, func() (e error, b bool) {
		var issues []jira.Issue
		issues, err = cli.SearchIssue(jql)
		if err != nil {
			if util.CheckIsNetworkError(err) {
				return errors.Trace(err), false
			}
			return errors.Trace(err), true
		}
		issue.ID = issues[0].ID
		issue.Key = issues[0].Key
		return nil, true
	})
	if err != nil {
		log.Errorf("search jira issue [%s] failed, error: %v", jql, err)
		return nil, errors.Trace(err)
	}

	if expr.Stage == types.ExperimentStageFinished {
		err = util.RetryOnError2(ctx, 5, 2*time.Second, func() (e error, b bool) {
			if err = cli.DoAllTransitions(context.Background(), issue.ID); err != nil {
				if util.CheckIsNetworkError(err) {
					return errors.Trace(err), false
				}
				return errors.Trace(err), true
			}
			return nil, true
		})

		if err != nil {
			log.Errorf("jira issue %s do transitions failed, error: %v", issue.ID, err)
			return nil, errors.Trace(err)
		}
	}

	return recordIssue, nil
}

// TODO: set `TestType` with the type of experiment,
// TODO: set test key, eg: QA-1
// TODO: set failure test key, eg: QA-1
func initCustomFields(expr *types.ExperimentProcess, cli *sjira.Jira, fields *jira.IssueFields) {
	testGroupID, err := cli.GetFieldID(testGroup)
	if err == nil {
		fields.Unknowns[testGroupID] = expr.BoxName
	}

	testNameID, err := cli.GetFieldID(testName)
	if err == nil {
		fields.Unknowns[testNameID] = expr.Name
	}

	testTypeID, err := cli.GetFieldID(testType)
	if err == nil {
		fields.Unknowns[testTypeID] = map[string]string{"value": "QA Test"}
	}

	testStartTimeID, err := cli.GetFieldID(testStartTime)
	if err == nil {
		var startTime time.Time
		if expr.StartTime == "" {
			startTime = time.Now()
		} else {
			if startTime, err = time.Parse(util.TimeFormat, expr.StartTime); err != nil {
				log.Errorf("parse start time %s failed, error: %v", expr.StartTime, err)
				startTime = time.Now()
			}
		}
		fields.Unknowns[testStartTimeID] = util.FormatJiraTime(startTime)
	}

	testEndTimeID, err := cli.GetFieldID(testEndTime)
	if err == nil {
		var endTime time.Time
		if expr.EndTime == "" {
			endTime = time.Now()
		} else {
			if endTime, err = time.Parse(util.TimeFormat, expr.EndTime); err != nil {
				log.Errorf("parse end time %s failed, error: %v", expr.EndTime, err)
				endTime = time.Now()
			}
		}
		fields.Unknowns[testEndTimeID] = util.FormatJiraTime(endTime)
	}

	testTerminationStatusID, err := cli.GetFieldID(testTerminationStatus)
	if err == nil {
		fields.Unknowns[testTerminationStatusID] = map[string]string{"value": genTerminationState(expr.Stage, expr.State)}
	}

	testCaseNameID, err := cli.GetFieldID(testCaseName)
	if err == nil {
		fields.Unknowns[testCaseNameID] = expr.TestsName()
	}

	failureCaseNameID, err := cli.GetFieldID(failureCaseName)
	if err == nil {
		fields.Unknowns[failureCaseNameID] = expr.FailedCases().Names()
	}

	if expr.Cat != nil {
		var tidbVersionID string
		tidbVersionID, err = cli.GetFieldID(tidbVersion)
		if err == nil && expr.Cat.Config.TiDBConfig.Version.Value != "" {
			fields.Unknowns[tidbVersionID] = []string{expr.Cat.Config.TiDBConfig.Version.Value}
		}

		var tikvVersionID string
		tikvVersionID, err = cli.GetFieldID(tikvVersion)
		if err == nil && expr.Cat.Config.TiKVConfig.Version.Value != "" {
			fields.Unknowns[tikvVersionID] = []string{expr.Cat.Config.TiKVConfig.Version.Value}
		}

		var pdVersionID string
		pdVersionID, err = cli.GetFieldID(pdVersion)
		if err == nil && expr.Cat.Config.PDConfig.Version.Value != "" {
			fields.Unknowns[pdVersionID] = []string{expr.Cat.Config.PDConfig.Version.Value}
		}
	}

	if expr.Report != nil {
		var testDetailID string
		testDetailID, err = cli.GetFieldID(testDetail)
		if err == nil {
			fields.Unknowns[testDetailID] = fmt.Sprintf("{code}%s{code}",
				getReortString(expr.Report))
		}
	}

	experimentIDFieldID, err := cli.GetFieldID(experimentID)
	if err == nil {
		fields.Unknowns[experimentIDFieldID] = fmt.Sprintf("%d", expr.ID)
	}

	experimentOwnerID, err := cli.GetFieldID(experimentOwner)
	if err == nil {
		fields.Unknowns[experimentOwnerID] = expr.Creator
	}
}

func genTerminationState(stage types.ExperimentStage, state types.ExperimentState) string {
	// TODO: judge the termination state from experiment stage and state
	if state == types.ExperimentStateNormal {
		return "Finished"
	}

	return "Unknown error"
}

func getReortString(report *types.ExperimentReport) string {
	str, err := template.GenerateReport(template.DetailReportTemplateName, template.GenDetailReportTemplate(), report)
	if err != nil {
		log.Errorf("get report failed, error: %v", err)
		return ""
	}

	return str
}

// TODO: support update recordIssue
// func (r *RecordIssue) update() error {
// }
