package model

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"

	"gorm.io/gorm"

	"github.com/pingcap/jira-bot/pkg/config"
	"github.com/pingcap/jira-bot/pkg/util"
)

var issueActiveStatusMap map[string]struct{}

const (
	IssueStatusTODO      = "TODO"
	IssueStatusTODOCN    = "待办"
	IssueStatusUI        = "Under investigation"
	IssueStatusPL        = "Problem located"
	IssueStatusWFS       = "Waiting for support/customer"
	IssueStatusTODOAlias = "To Do"
)

var (
	NotExistErr = errors.New("Issue Does Not Exist")
)

func init() {
	issueActiveStatusMap = make(map[string]struct{})
	issueActiveStatusMap[IssueStatusTODO] = struct{}{}
	issueActiveStatusMap[IssueStatusTODOAlias] = struct{}{}
	issueActiveStatusMap[IssueStatusTODOCN] = struct{}{}
	issueActiveStatusMap[IssueStatusUI] = struct{}{}
	issueActiveStatusMap[IssueStatusPL] = struct{}{}
	issueActiveStatusMap[IssueStatusWFS] = struct{}{}
}

type Issue struct {
	ID                        string    `gorm:"primaryKey"`
	Reporter                  string    `gorm:"type:varchar(255)"` // display name
	ReporterEmail             string    `gorm:"type:varchar(255)"`
	Assignee                  string    `gorm:"type:varchar(255)"` // display name
	AssigneeEmail             string    `gorm:"type:varchar(255)"`
	Component                 string    `gorm:"type:varchar(255)"`
	Priority                  string    `gorm:"type:varchar(255)"`
	AffectVersion             string    `gorm:"type:varchar(255)"`
	Status                    string    `gorm:"type:varchar(255)"`
	Summary                   string    `gorm:"type:text"`
	CreateTime                time.Time `gorm:"type:time"`
	UpdateTime                time.Time `gorm:"type:time"`
	LastNotificationTimestamp int64
	Watchers                  string   `gorm:"type:text"`
	WatcherEmails             []string `gorm:"-"`
}

func (issue Issue) IsIssueDone() bool {
	if _, ok := issueActiveStatusMap[issue.Status]; ok {
		return false
	}
	return true
}

type Issues []Issue

func AllIssue(db *gorm.DB) (Issues, error) {
	issues := Issues{}
	result := db.Find(&issues)
	if result.Error != nil {
		return nil, result.Error
	}
	log.Println("total issue ct=", result.RowsAffected)

	return issues, nil
}

// https://gorm.io/docs/query.html#inline_conditions
func FindIssue(db *gorm.DB, conds ...interface{}) (Issues, error) {
	issues := Issues{}
	result := db.Find(&issues, conds...)
	if result.Error != nil {
		return nil, result.Error
	}

	return issues, nil
}

type JiraIssueInfo struct {
	IssueEventTypeName string `json:"issue_event_type_name"`
	WebhookEvent       string `json:"webhookEvent"`
	User               struct {
		DisplayName  string `json:"displayName"`
		Active       bool   `json:"active"`
		TimeZone     string `json:"timeZone"`
		Self         string `json:"self"`
		Name         string `json:"name"`
		Key          string `json:"key"`
		EmailAddress string `json:"emailAddress"`
	} `json:"user"`
	Issue struct {
		ID     string `json:"id"`
		Self   string `json:"self"` // issue link
		Key    string `json:"key"`
		Fields struct {
			Summary    string `json:"summary"`    // issue name
			LastViewed string `json:"lastViewed"` // "2021-07-28T06:19:34.734+0000"
			Issuetype  struct {
				Self        string `json:"self"`
				ID          string `json:"id"`
				Description string `json:"description"`
				IconURL     string `json:"iconUrl"`
				Name        string `json:"name"`
				Subtask     bool   `json:"subtask"`
				AvatarID    int    `json:"avatarId"`
			} `json:"issuetype"`
			Issuelinks []interface{} `json:"issuelinks"`
			Project    struct {
				Self           string `json:"self"`
				ID             string `json:"id"`
				Key            string `json:"key"`
				Name           string `json:"name"`
				ProjectTypeKey string `json:"projectTypeKey"`
			} `json:"project"`
			Created string `json:"created"` // create time 2021-07-22T08:56:22.000+0000
			Comment struct {
				StartAt    int           `json:"startAt"`
				Comments   []interface{} `json:"comments"`
				MaxResults int           `json:"maxResults"`
				Total      int           `json:"total"`
			} `json:"comment"`
			Assignee struct {
				DisplayName  string `json:"displayName"`
				Active       bool   `json:"active"`
				TimeZone     string `json:"timeZone"`
				Self         string `json:"self"`
				Name         string `json:"name"`
				Key          string `json:"key"`
				EmailAddress string `json:"emailAddress"`
			} `json:"assignee"`
			Creator struct {
				DisplayName  string `json:"displayName"`
				Active       bool   `json:"active"`
				TimeZone     string `json:"timeZone"`
				Self         string `json:"self"`
				Name         string `json:"name"`
				Key          string `json:"key"`
				EmailAddress string `json:"emailAddress"`
			} `json:"creator"`
			Timeestimate          interface{} `json:"timeestimate"`
			Aggregatetimeestimate interface{} `json:"aggregatetimeestimate"`
			Watches               struct {
				WatchCount int    `json:"watchCount"`
				Self       string `json:"self"`
			} `json:"watches"`
			Description interface{} `json:"description"`
			Environment interface{} `json:"environment"`
			Priority    struct {
				Self    string `json:"self"`
				IconURL string `json:"iconUrl"`
				Name    string `json:"name"` // priority name
				ID      string `json:"id"`
			} `json:"priority"`
			Updated           string `json:"updated"`
			Aggregateprogress struct {
				Progress int `json:"progress"`
				Total    int `json:"total"`
			} `json:"aggregateprogress"`
			Worklog struct {
				Total      int           `json:"total"`
				Worklogs   []interface{} `json:"worklogs"`
				StartAt    int           `json:"startAt"`
				MaxResults int           `json:"maxResults"`
			} `json:"worklog"`
			Resolution interface{} `json:"resolution"`
			Timespent  interface{} `json:"timespent"`
			Reporter   struct {
				Self         string `json:"self"`
				Name         string `json:"name"`
				Key          string `json:"key"`
				EmailAddress string `json:"emailAddress"`
				DisplayName  string `json:"displayName"`
				Active       bool   `json:"active"`
				TimeZone     string `json:"timeZone"`
			} `json:"reporter"`
			Components []struct {
				ID          string `json:"id"`
				Name        string `json:"name"`
				Description string `json:"description"`
				Self        string `json:"self"`
			} `json:"components"`
			Versions []struct {
				Self        string `json:"self"`
				ID          string `json:"id"`
				Description string `json:"description"`
				Name        string `json:"name"`
				Archived    bool   `json:"archived"`
				Released    bool   `json:"released"`
				ReleaseDate string `json:"releaseDate"`
			} `json:"versions"`
			Status struct {
				ID             string `json:"id"`
				StatusCategory struct {
					Self      string `json:"self"`
					ID        int    `json:"id"`
					Key       string `json:"key"`
					ColorName string `json:"colorName"`
					Name      string `json:"name"`
				} `json:"statusCategory"`
				Self        string `json:"self"`
				Description string `json:"description"`
				IconURL     string `json:"iconUrl"`
				Name        string `json:"name"`
			} `json:"status"`
			Subtasks []interface{} `json:"subtasks"`
			Duedate  interface{}   `json:"duedate"`
		} `json:"fields"`
	} `json:"issue"`
	Changelog struct {
		ID    string `json:"id"`
		Items []struct {
			Field      string      `json:"field"`
			Fieldtype  string      `json:"fieldtype"`
			From       string      `json:"from"` // from status
			FromString string      `json:"fromString"`
			To         interface{} `json:"to"` // to status
		} `json:"items"`
	} `json:"changelog"`
	Timestamp int64 `json:"timestamp"`
}

type WatchersInfo struct {
	Self       string `json:"self"`
	IsWatching bool   `json:"isWatching"`
	WatchCount int    `json:"watchCount"`
	Watchers   []struct {
		Self         string `json:"self"`
		Name         string `json:"name"`
		Key          string `json:"key"`
		EmailAddress string `json:"emailAddress"`
		AvatarUrls   struct {
			Four8X48  string `json:"48x48"`
			Two4X24   string `json:"24x24"`
			One6X16   string `json:"16x16"`
			Three2X32 string `json:"32x32"`
		} `json:"avatarUrls"`
		DisplayName string `json:"displayName"`
		Active      bool   `json:"active"`
		TimeZone    string `json:"timeZone"`
	} `json:"watchers"`
}

func (info *JiraIssueInfo) ToIssue() (*Issue, error) {
	createTime, err := time.Parse(TimeLayout, info.Issue.Fields.Created)
	if err != nil {
		return nil, err
	}
	updateTime := time.Unix(info.Timestamp/1000, 0)

	fields := info.Issue.Fields
	component := func() string {
		var builder strings.Builder
		for index, comp := range fields.Components {
			if index != 0 {
				builder.WriteString(", ")
			}
			builder.WriteString(comp.Name)
		}
		return builder.String()
	}()

	version := func() string {
		var builder strings.Builder
		for index, afv := range fields.Versions {
			if index != 0 {
				builder.WriteString(", ")
			}
			builder.WriteString(afv.Name)
		}
		return builder.String()
	}()

	watchers, watcherEmails, err := func() (string, []string, error) {
		if info.Issue.Fields.Watches.WatchCount > 0 {
			req, err := http.NewRequest(http.MethodGet, info.Issue.Fields.Watches.Self, nil)
			if err != nil {
				log.Println("get watchinfo error", info.Issue.Fields.Watches.Self, err)
				return "", nil, err
			}
			req.Header.Set("User-Agent", "PostmanRuntime/7.26.8")
			req.Header.Set("Authorization", config.GetString("jiraaccesstoken"))

			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				log.Println("get watchinfo resp error", info.Issue.Fields.Watches.Self, err)
				return "", nil, err
			}

			defer func() {
				_ = resp.Body.Close()
			}()
			bodyBytes, _ := ioutil.ReadAll(resp.Body)
			watchinfo := WatchersInfo{}
			_ = json.Unmarshal(bodyBytes, &watchinfo)
			if watchinfo.Self == "" {
				_ = util.NotifyLark("https://open.feishu.cn/open-apis/bot/v2/hook/105575ee-26ca-487f-bc2c-4108f582dc62",
					fmt.Sprintf("get watchinfo failed : %s", util.ByteSliceToString(bodyBytes)))

				if strings.Contains(util.ByteSliceToString(bodyBytes), NotExistErr.Error()) {
					return "", nil, NotExistErr
				}
				return "", nil, nil
			}

			tmpArr := make([]string, 0, len(watchinfo.Watchers))
			tmpEmailArr := make([]string, 0, len(watchinfo.Watchers))
			for _, watcher := range watchinfo.Watchers {
				if watcher.Active {
					tmpArr = append(tmpArr, watcher.DisplayName)
					tmpEmailArr = append(tmpEmailArr, watcher.EmailAddress)
				}
			}

			return strings.Join(tmpArr, ","), tmpEmailArr, nil
		}

		return "", nil, nil
	}()

	return &Issue{
		ID:            info.Issue.Key,
		Summary:       fields.Summary,
		Reporter:      fields.Reporter.DisplayName,
		ReporterEmail: fields.Reporter.EmailAddress,
		Assignee:      fields.Assignee.DisplayName,
		AssigneeEmail: fields.Assignee.EmailAddress,
		Component:     component,
		Priority:      fields.Priority.Name,
		Status:        fields.Status.Name,
		CreateTime:    createTime.Local(),
		UpdateTime:    updateTime.Local(),
		AffectVersion: version,
		Watchers:      watchers,
		WatcherEmails: watcherEmails,
	}, err
}
