package activities

import (
	"fmt"
	"strconv"
	"time"

	"github.com/pingcap/log"
	openapiv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/activityhub_client"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/datasource"
	"github.com/pingcap-inc/clinic/pkg/datasource/model"
	"github.com/pingcap-inc/clinic/pkg/datasource/utils"
)

const (
	defaultTimeOut   = 3 * time.Hour // 3 hours
	defaultCheckTime = 6 * time.Minute
	defaultWaitTime  = 3 * time.Second
)

type gitOpsMonitor struct {
	monitorUpgradeChan chan monitorJob
	monitorNormalChan  chan monitorJob
	hubClient          *activityhub_client.ActivityHubClient
}

type monitorJob struct {
	clusterID  string
	tenantID   string
	contextID  string
	gitOpsType string // upgrade patch upgrade
	createAt   time.Time
	checkCount int
	summary    string
}

func NewGitOpsMonitor(hubClient *activityhub_client.ActivityHubClient) *gitOpsMonitor {
	return &gitOpsMonitor{
		monitorUpgradeChan: make(chan monitorJob, 100),
		monitorNormalChan:  make(chan monitorJob, 100),
		hubClient:          hubClient,
	}
}

// addGitOpsMonitorJob  add a new monitor job
func (m *gitOpsMonitor) addGitOpsMonitorJob(clutserID, contextID, tenantID, gitOpsType, summary string) error {
	if gitOpsType == "" {
		gitOpsType = "config-change"
	}

	if summary == "" {
		summary = fmt.Sprintf("%s cluster", gitOpsType)
	}

	if _, err := getCluster(clutserID); err != nil {
		return err
	}

	job := monitorJob{
		clusterID:  clutserID,
		tenantID:   tenantID,
		contextID:  contextID,
		gitOpsType: gitOpsType,
		createAt:   time.Now(),
		checkCount: 0,
		summary:    summary,
	}
	m.monitorUpgradeChan <- job

	return nil
}

// Start start monitor
func (m *gitOpsMonitor) Start() {
	go m.startUpgradeMonitor()
	go m.startNormalMonitor()
}

// monitor cluster status to upgrade
func (m *gitOpsMonitor) startUpgradeMonitor() {
	for {
		time.Sleep(defaultWaitTime) // sleep 3s
		job := <-m.monitorUpgradeChan

		cluster, err := getCluster(job.clusterID)
		if err != nil {
			log.Error("get cluster status failed", zap.Error(err), zap.String("cluster", job.clusterID), zap.String("org", job.tenantID))
		}
		status := utils.GetClusterStatus(cluster)

		// update status and version in clinic db
		err = model.InsertOrUpdateCloudCluster(cluster, true)
		if err != nil {
			log.Error("update cloud cluster failed", zap.Error(err), zap.String("cluster", job.clusterID), zap.String("org", job.tenantID), zap.String("status", status.String()))
		}

		if status == openapiv1.ClusterState_UPGRADING {
			err = m.SendEvent(job, status)
			if err != nil {
				log.Error("send gitops event failed", zap.Error(err), zap.String("cluster", job.clusterID), zap.String("org", job.tenantID), zap.String("status", status.String()))
			}
			m.monitorNormalChan <- job
		} else {
			if !job.createAt.Add(defaultTimeOut).After(time.Now()) {
				log.Warn("monitor gitops start event timemout", zap.String("cluster", job.clusterID), zap.String("org", job.tenantID), zap.String("status", status.String()))
			} else {
				m.monitorUpgradeChan <- job
			}
		}
	}
}

// monitor cluster status to normal
func (m *gitOpsMonitor) startNormalMonitor() {
	for {
		time.Sleep(3 * time.Second) // sleep 3s
		job := <-m.monitorNormalChan

		cluster, err := getCluster(job.clusterID)
		if err != nil {
			log.Error("get cluster status failed", zap.Error(err), zap.String("cluster", job.clusterID), zap.String("org", job.tenantID))
		}
		status := utils.GetClusterStatus(cluster)

		// update status and version in clinic db
		err = model.InsertOrUpdateCloudCluster(cluster, true)
		if err != nil {
			log.Error("update cloud cluster failed", zap.Error(err), zap.String("cluster", job.clusterID), zap.String("org", job.tenantID), zap.String("status", status.String()))
		}

		if status == openapiv1.ClusterState_ACTIVE {
			if job.checkCount <= int(defaultCheckTime/defaultWaitTime) {
				job.checkCount++
				m.monitorNormalChan <- job
				continue
			}

			err = m.SendEvent(job, status)
			if err != nil {
				log.Error("send gitops event failed", zap.Error(err), zap.String("cluster", job.clusterID), zap.String("org", job.tenantID), zap.String("status", status.String()))
			}
		} else {
			if job.checkCount == 0 && !job.createAt.Add(defaultTimeOut).After(time.Now()) {
				log.Warn("monitor gitops finish event timemout", zap.String("cluster", job.clusterID), zap.String("org", job.tenantID), zap.String("status", status.String()))
			} else {
				job.checkCount = 0
				m.monitorNormalChan <- job
			}
		}
	}
}

func getCluster(clusterID string) (*mgmtv1.Cluster, error) {
	cid, err := strconv.ParseUint(clusterID, 10, 64)
	if err != nil {
		return nil, err
	}

	return datasource.RealtimeGetMgmtCluster(&session.CtxUser{
		Internal: true,
		Email:    "clinic@pingcap.com",
	}, cid)
}

func (m *gitOpsMonitor) SendEvent(job monitorJob, status openapiv1.ClusterState) error {
	request := activityhub_client.Activity{
		ApplicationID:   job.tenantID,
		TargetID:        job.clusterID,
		Catalog:         "gitops",
		Severity:        0,
		ContextID:       job.contextID,
		CalibrationTime: time.Now().Unix(),
		Payload: map[string]string{
			"summary": job.summary,
		},
	}

	switch status {
	case openapiv1.ClusterState_ACTIVE:
		request.Name = fmt.Sprintf("%s:finish", job.gitOpsType)
	case openapiv1.ClusterState_UPGRADING:
		request.Name = fmt.Sprintf("%s:start", job.gitOpsType)
	}

	return m.hubClient.SendActivity(request)
}
