package service

import (
	"fmt"
	"log"
	"strings"
	"time"

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

	"github.com/pingcap/jira-bot/pkg/job"

	"github.com/robfig/cron/v3"

	"github.com/pingcap/jira-bot/model"
)

var iwService IssueWatchService

type IssueWatchService struct {
	IssueToCrons map[string][]cron.EntryID
}

func init() {
	iwService.IssueToCrons = make(map[string][]cron.EntryID)
}

type IssueJob struct {
	Issue              *model.Issue
	User               *model.User
	NotificationTarget string
	Rule               model.NotificationRule
	EntryId            cron.EntryID
	Receiver           string
}

func NewIssueJob(issue *model.Issue, target string) *IssueJob {
	iJob := new(IssueJob)
	iJob.Issue = issue
	iJob.NotificationTarget = target
	iJob.getRule()
	return iJob
}

func (iJob *IssueJob) getNextNotificationCron() string {
	if iJob.Rule.IsNil() {
		return ""
	}

	var next time.Time
	now := time.Now()
	if iJob.Issue.LastNotificationTimestamp == 0 {
		duration := time.Duration(iJob.Rule.TriggerGap+1) * time.Minute
		next = iJob.Issue.UpdateTime.Add(duration)
		if now.After(next) {
			next = now.Add(duration)
		}
	} else {
		duration := time.Duration(iJob.Rule.Frequency+1) * time.Minute
		next = time.Unix(iJob.Issue.LastNotificationTimestamp, 0).Add(duration)
		if now.After(next) {
			next = now.Add(duration)
		}
	}

	return fmt.Sprintf("%d %d %d %d ?", next.Minute(), next.Hour(), next.Day(), next.Month())
}

// FIXME(cjl) refactor by key-value
func (iJob *IssueJob) getRule() {
	index := 0
	switch iJob.Issue.Priority {
	case "P0":
		index = 0
	case "P1":
		index = 18
	case "P2":
		index = 36
	case "P3":
		index = 54
	case "P4":
		index = 72
	default:
		log.Println("issue priority not match ", iJob.Issue.Priority)
		return
	}

	switch iJob.NotificationTarget {
	case model.NotificationTargetAssignee:
		index += 0
		if iJob.Issue.Status == model.IssueStatusWFS {
			log.Println("assignee will not be notification when status = ", iJob.Issue.Status)
			return
		}
	case model.NotificationTargetReporter:
		index += 4
	case model.NotificationTargetWatcher:
		index += 9
	case model.NotificationTargetLeader:
		index += 14
		if iJob.Issue.Status == model.IssueStatusWFS {
			log.Println("leader will not be notification when status = ", iJob.Issue.Status)
			return
		}
	default:
		log.Println("issue notificationtarget not match ", iJob.NotificationTarget)
		return
	}

	switch iJob.Issue.Status {
	case model.IssueStatusTODO:
		index += 1
	case model.IssueStatusTODOCN:
		index += 2
	case model.IssueStatusUI:
		index += 3
	case model.IssueStatusPL:
		index += 4
	case model.IssueStatusWFS:
		index += 5
	default:
		log.Println("issue status not match ", iJob.Issue.Status)
		return
	}

	iJob.Rule = model.GetApp().GetRule(index)
}

func (iJob *IssueJob) Run() {
	if iJob.Rule.IsNil() {
		log.Println("IssueJob err rule == nil, ", iJob.Issue, iJob.NotificationTarget)
		return
	}

	if iJob.User == nil {
		var err error
		user := model.User{JiraUserDisplayName: iJob.Receiver}
		if user.JiraUserDisplayName == "" {
			log.Println("IssueJob run err JiraUserDisplayName is empty [it's normal for watcher]",
				iJob.Issue, iJob.NotificationTarget)
			return
		}

		iJob.User, err = GetUserInfo(user)
		if err != nil {
			log.Println("IssueJob GetUserInfo err ", err, iJob.Issue, iJob.NotificationTarget)
			return
		}
	}

	hourPass := time.Since(iJob.Issue.UpdateTime).Hours()
	var notificationContent string
	switch iJob.NotificationTarget {
	case model.NotificationTargetReporter,
		model.NotificationTargetWatcher,
		model.NotificationTargetAssignee:
		notificationContent = fmt.Sprintf(iJob.Rule.Content, int(hourPass))
	case model.NotificationTargetLeader:
		notificationContent = fmt.Sprintf(iJob.Rule.Content, iJob.Issue.Assignee, int(hourPass))
		go specialNotifcication(iJob, config.GetString("specialnotificationuser"))
	case model.NotificationTargetSpecialLeader:
		notificationContent = fmt.Sprintf(iJob.Rule.Content, iJob.Issue.Assignee, int(hourPass))
	}

	log.Println("Job Run ", iJob, iJob.Issue)
	message, err := NotifyIssue(iJob, notificationContent)
	if err != nil {
		log.Println("NotifyIssue err: ", err)
		return
	}

	// log save message
	err = model.Save(model.GetApp().DB, message)
	if err != nil {
		log.Println("save message err:", err, message)
	}

	if iJob.EntryId != 0 {
		iJob.Issue.LastNotificationTimestamp = time.Now().Unix()
		err = model.Save(model.GetApp().DB, iJob.Issue)
		if err != nil {
			log.Println("update issue err:", err, iJob.Issue)
		}

		if iJob.NotificationTarget == model.NotificationTargetWatcher {
			AddLog(message, model.WatcherSendEvent)
		} else {
			AddLog(message, model.SendEvent)
		}
		// remove previous job
		job.RemoveJob(iJob.EntryId)
		// add new watching job
		watchingIssue(iJob.Issue, iJob.NotificationTarget, iJob.Receiver)
	} else {
		AddLog(message, model.TriggerSendEvent)
	}
}

func specialNotifcication(iJob *IssueJob, receiverEmail string) {
	specialJob := *iJob
	specialUser, err := GetUserInfo(model.User{Email: receiverEmail})
	if err != nil {
		log.Println("specialNotifcication GetUserInfo err: ", err, receiverEmail)
		return
	}

	specialJob.User = specialUser
	specialJob.EntryId = 0
	specialJob.NotificationTarget = model.NotificationTargetSpecialLeader
	specialJob.Run()
}

func CheckIssuesOnStartup() {
	issues, err := model.FindIssue(model.GetApp().DB)
	if err != nil {
		log.Fatal(err)
	}

	for i := range issues {
		issue := issues[i]
		if issue.IsIssueDone() {
			continue
		}

		watchingIssue(&issue, model.NotificationTargetAssignee, issue.Assignee)
		watchingIssue(&issue, model.NotificationTargetReporter, issue.Reporter)
		watchingIssue(&issue, model.NotificationTargetLeader, issue.Assignee)
		if issue.Watchers != "" {
			tmpArr := strings.Split(issue.Watchers, ",")
			for _, receiver := range tmpArr {
				watchingIssue(&issue, model.NotificationTargetWatcher, receiver)
			}
		}
	}
}

func UpdateIssue(issue *model.Issue) {
	cancleWatchingIssue(issue)
	if !issue.IsIssueDone() {
		watchingIssue(issue, model.NotificationTargetAssignee, issue.Assignee)
		watchingIssue(issue, model.NotificationTargetReporter, issue.Reporter)
		watchingIssue(issue, model.NotificationTargetLeader, issue.Assignee)
		if issue.Watchers != "" {
			tmpArr := strings.Split(issue.Watchers, ",")
			for _, receiver := range tmpArr {
				watchingIssue(issue, model.NotificationTargetWatcher, receiver)
			}
		}
	}
}

func watchingIssue(issue *model.Issue, target string, receiver string) {
	if (target == model.NotificationTargetReporter && issue.Assignee == receiver) ||
		(target == model.NotificationTargetWatcher && (receiver == issue.Assignee || receiver == issue.Reporter)) {
		return
	}

	ijob := NewIssueJob(issue, target)
	ijob.Receiver = receiver

	cronStr := ijob.getNextNotificationCron()
	entryId, err := job.AddJob(cronStr, ijob)
	if err != nil {
		log.Println("watchingIssue err:", err, ijob.Issue, ijob.Rule)
		return
	}

	ijob.EntryId = entryId
	iwService.IssueToCrons[issue.ID] = append(iwService.IssueToCrons[issue.ID], entryId)
	log.Println("watchingIssue add job ", entryId, ijob.Issue, cronStr, target, ijob.Receiver)
}

func cancleWatchingIssue(issue *model.Issue) {
	if entries, ok := iwService.IssueToCrons[issue.ID]; ok {
		for _, entryId := range entries {
			log.Println("cancleWatchingIssue removejob ", entryId)
			job.RemoveJob(entryId)
		}
		iwService.IssueToCrons[issue.ID] = entries[:0]
		log.Println("IssueToCrons len: ", len(iwService.IssueToCrons[issue.ID]), issue)
	}
}
