package activities

import (
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/activityhub_client"
	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/config"
)

type ActivityGroup struct {
	Name          string                         `json:"name"`
	ContextID     string                         `json:"context_id"`
	Status        string                         `json:"status"`
	Severity      int                            `json:"severity"`
	Rule          []string                       `json:"rule"`
	ApplicationID string                         `json:"application_id"`
	ConceptID     string                         `json:"concept_id"`
	TargetID      string                         `json:"target_id"`
	Activities    []*activityhub_client.Activity `json:"activities"`
	Timeout       int64                          `json:"timeout"`
}

func (g *ActivityGroup) setStatus() {
	if len(g.Rule) < 1 {
		return
	}
	g.Status = "PROCESSING"
	for _, activity := range g.Activities {
		if activity.Name == g.Rule[len(g.Rule)-1] {
			g.Status = "FINISHED"
			return
		}
	}

	// check timeout
	if g.Timeout > 0 {
		if g.Activities[len(g.Activities)-1].CalibrationTime < time.Now().Add(-time.Second*time.Duration(g.Timeout)).Unix() {
			g.Status = "FINISHED"
		}
	}
}

func (g *ActivityGroup) setSeverity() {
	g.Severity = -4
	for _, activity := range g.Activities {
		if activity.Severity > g.Severity {
			g.Severity = activity.Severity
			break
		}
	}
}

// add statut,rule,name and other meta to group with activities
func (g *ActivityGroup) Format(rules map[string]config.ActivityGroupRule) *ActivityGroup {
	// ASC activities in group
	sort.SliceStable(g.Activities, func(i, j int) bool {
		return g.Activities[i].CalibrationTime < g.Activities[j].CalibrationTime
	})

	for _, activity := range g.Activities {
		// Ignore debug activity
		if activity.Severity < 0 {
			continue
		}

		// first usable catalog value as group name
		rule, exist := rules[activity.Catalog]
		groupName := activity.Catalog
		// check event name
		if !exist {
			groupName = strings.Split(activity.Name, ":")[0]
			rule, exist = rules[groupName]
		}

		if exist {
			g.Name = groupName
			g.Rule = rule.Rule
			g.ContextID = activity.ContextID
			g.ApplicationID = activity.ApplicationID
			g.ConceptID = activity.ConceptID
			g.TargetID = activity.TargetID
			g.Timeout = rule.Timeout
			break
		}
	}

	if len(g.Activities) >= 1 && g.Name == "" {
		g.Name = g.Activities[0].Name
		g.ContextID = g.Activities[0].ContextID
		g.ApplicationID = g.Activities[0].ApplicationID
		g.ConceptID = g.Activities[0].ConceptID
		g.TargetID = g.Activities[0].TargetID
	}

	g.setSeverity()
	g.setStatus()

	return g
}

type QueryActivitiesResponse struct {
	Activities []*activityhub_client.Activity `json:"activities"`
	Partial    bool                           `json:"partial"`
	Cursor     string                         `json:"cursor"`
}

type GetActivityResponse struct {
	*activityhub_client.Activity
}

type QueryActivityGroupsResponse struct {
	ActivityGroups []*ActivityGroup `json:"activity_groups"`
}

type GetActivityGroupResponse struct {
	ActivityGroup
}

// @Summary		list activities
// @Description	list activities
// @Accept			json
// @Param			application_id	path		string	true	"application_id"
// @Param			target_id		path		string	true	"target_id"
// @Param			begin_ts		query		int		true	"begin_ts"
// @Param			end_ts			query		int		true	"end_ts"
// @Param			catalog			query		string	false	"catalog"
// @Param			type			query		string	false	"type"
// @Param			name			query		string	false	"name"
// @Param			severity		query		int		false	"severity"
// @Param			limit			query		int		false	"limit"
// @Param			cursor			header		string	false	"coursor for page limit"
// @Success		200				{object}	QueryActivitiesResponse
// @Router			/activityhub/applications/{application_id}/targets/{target_id}/activities   [get]
func (s *EventManagerService) QueryActivities(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil || !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	beginTSstr := c.Query("begin_ts")
	if beginTSstr == "" {
		_ = c.Error(error2.ErrBadRequest.New("bad param"))
		return
	}
	beginTS, _ := strconv.ParseInt(beginTSstr, 10, 64)
	endTSstr := c.Query("end_ts")
	if endTSstr == "" {
		_ = c.Error(error2.ErrBadRequest.New("bad param"))
		return
	}
	endTS, _ := strconv.ParseInt(endTSstr, 10, 64)
	limitStr := c.Query("limit")
	limit, err := strconv.Atoi(limitStr)
	if err != nil || limit < 20 {
		limit = 20
	}
	cursor := c.GetHeader("cursor")
	severityStr := c.Query("severity")
	severity, _ := strconv.ParseInt(severityStr, 10, 64)

	activities, partial, newCursor, err := s.hubClient.SearchActivitiesByTimeRange(c.Param("target_id"), beginTS, endTS, limit, cursor, &activityhub_client.Activity{
		// ApplicationID: c.Param("application_id"),
		TargetID: c.Param("target_id"),
		// ContextID:     c.Query("context_id"),
		Catalog:  c.Query("catalog"),
		Type:     c.Query("type"),
		Name:     c.Query("name"),
		Severity: int(severity),
		// Source:        c.Query("source"),
	})
	if err != nil {
		log.Error("get activities failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("query activities failed"))
		return
	}
	c.JSON(http.StatusOK, QueryActivitiesResponse{
		Activities: activities,
		Partial:    partial,
		Cursor:     newCursor,
	})
}

// @Summary		get activity
// @Description	get activity
// @Accept			json
// @Param			application_id	path		string	true	"application_id"
// @Param			target_id		path		string	true	"target_id"
// @Param			activity_id		path		string	true	"activity id"
// @Success		200				{object}	GetActivityResponse
// @Router			/activityhub/applications/{application_id}/targets/{target_id}/activities/{activity_id} [get]
func (s *EventManagerService) GetActivitiy(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil || !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	activity, err := s.hubClient.GetActivity(c.Param("activity_id"))
	if err != nil {
		log.Error("get activity failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("query activities failed"))
	}
	c.JSON(http.StatusOK, GetActivityResponse{
		activity,
	})
}

// @Summary		list activity groups
// @Description	list activity groups
// @Accept			json
// @Param			application_id	path		string	true	"application_id"
// @Param			target_id		path		string	true	"target_id"
// @Param			begin_ts		query		int		true	"begin_ts"
// @Param			end_ts			query		int		true	"end_ts"
// @Param			severity		query		int		true	"severity"
// @Success		200				{object}	QueryActivityGroupsResponse
// @Router			/activityhub/applications/{application_id}/targets/{target_id}/activitygroups  [get]
func (s *EventManagerService) QueryActivityGroups(c *gin.Context) {
	u := session.GetUser(c)
	// TODO: fix external user permission
	if u == nil {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	beginTSstr := c.Query("begin_ts")
	if beginTSstr == "" {
		_ = c.Error(error2.ErrBadRequest.New("bad param"))
		return
	}
	beginTS, _ := strconv.ParseInt(beginTSstr, 10, 64)
	endTSstr := c.Query("end_ts")
	if endTSstr == "" {
		_ = c.Error(error2.ErrBadRequest.New("bad param"))
		return
	}
	endTS, _ := strconv.ParseInt(endTSstr, 10, 64)
	severityStr := c.Query("severity")
	severity, err := strconv.Atoi(severityStr)
	if err != nil {
		severity = 0
	}

	activities, err := s.hubClient.GetAllActivitiesByTimeRange(c.Param("target_id"), beginTS, endTS, -4)
	if err != nil {
		log.Error("get activities failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("query activities failed"))
		return
	}

	c.JSON(http.StatusOK, QueryActivityGroupsResponse{ActivityGroups: s.activitiesToGroups(activities, severity)})
}

// @Summary		get activitygroup
// @Description	get activitygroup
// @Accept			json
// @Param			application_id	path		string	true	"application_id"
// @Param			target_id		path		string	true	"target_id"
// @Param			context_id		path		string	true	"context id"
// @Param			type			query		string	false	"type"
// @Success		200				{object}	GetActivityGroupResponse
// @Router			/activityhub/applications/{application_id}/targets/{target_id}/activitygroups/{context_id} [get]
func (s *EventManagerService) GetActivityGroup(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil || !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	activities, err := s.hubClient.GetActivitiesByContextID(c.Param("context_id"), c.Query("type"))
	if err != nil {
		log.Error("get activities failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("query activities failed"))
	}

	group := (&ActivityGroup{Activities: activities}).Format(s.cfg.ActivityManagerConfig.GroupRule)
	c.JSON(http.StatusOK, GetActivityGroupResponse{
		*group,
	})
}

func (s *EventManagerService) activitiesToGroups(activities []*activityhub_client.Activity, severity int) []*ActivityGroup {
	groupMap := make(map[string]*ActivityGroup) // contextID -> group

	for _, activity := range activities {
		group, exist := groupMap[activity.ContextID]
		if exist {
			group.Activities = append(group.Activities, activity)
		} else {
			group = &ActivityGroup{
				Activities: []*activityhub_client.Activity{activity},
			}
			groupMap[activity.ContextID] = group
		}
	}

	groups := []*ActivityGroup{}
	for _, group := range groupMap {
		group.Format(s.cfg.ActivityManagerConfig.GroupRule)
		if group.Severity >= severity {
			groups = append(groups, group)
		}
	}

	// DESC groups
	sort.SliceStable(groups, func(i, j int) bool {
		return groups[i].Activities[0].CalibrationTime > groups[j].Activities[0].CalibrationTime
	})
	return groups
}

// @Summary		post gitops activity
// @Description	post gitops activity
// @Param			application_id	path		string	true	"application_id"
// @Param			target_id		path		string	true	"target_id"
// @Param			context_id		path		string	true	"context id"
// @Param			type			query		string	false	"type"
// @Param			summary			query		string	false	"summary"
// @Success		202				{string}	success
// @Router			/activityhub/applications/{application_id}/targets/{target_id}/contexts/{context_id} [post]
func (s *EventManagerService) PostGitOpsActivity(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil || !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	s.lazyLoadGitOpsMonitor()
	err := s.gitOpsMonitor.addGitOpsMonitorJob(c.Param("target_id"), c.Param("context_id"), c.Param("application_id"), c.Query("type"), c.Query("summary"))
	if err != nil {
		log.Error("post gitops event failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("post gitops event failed failed"))
		return
	}

	// //group := (&ActivityGroup{Activities: activities}).Format(s.cfg.ActivityManagerConfig.GroupRule)
	c.JSON(http.StatusAccepted, common.SuccessResponse())
}

func (s *EventManagerService) lazyLoadGitOpsMonitor() {
	s.once.Do(func() {
		s.gitOpsMonitor = NewGitOpsMonitor(s.hubClient)
		s.gitOpsMonitor.Start()
	})
}
