package event

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/samber/lo"
	"github.com/tidbcloud/eventbus-datastruct/event"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic_cloud_server/pkg/client/activityhub"
)

const (
	ServerlessClusterType = "serverless"
	DedicatedClusterType  = "dedicated"
	NextGenClusterType    = "nextgen"
	AllClusterType        = "all"
)

var (
	defaultDisplayType = []string{
		"CreateCluster",
		"ModifyClusterSize",
		"PauseCluster",
		"ResumeCluster",
	}
	defaultDisplayStatus = []event.EventStatus{
		event.EventStatusStarted,
		event.EventStatusSucceeded,
		event.EventStatusFailed,
	}

	didecateDisplayTypeMap   = map[string]bool{}
	serverlessDisplayTypeMap = map[string]bool{}
	displayStatusMap         = map[event.EventStatus]bool{}
)

type BaseResp struct {
	ErrCode string `json:"err_code"`
	ErrMsg  string `json:"err_msg"`
}

type Event struct {
	Type            string `json:"type"`
	CalibrationTime int64  `json:"calibration_time"`
	Status          string `json:"status"`
	Author          string `json:"author"`
	Summary         string `json:"summary"`
}

type ListEventsResp struct {
	BaseResp `json:"base_resp"`
	Events   []Event `json:"events"`
	Partial  bool    `json:"partial"`
	Cursor   string  `json:"cursor"`
}

type ListEventFiltersResp struct {
	BaseResp    `json:"base_resp"`
	EventTypes  []string            `json:"event_types"`
	EventStatus []event.EventStatus `json:"event_status"`
}

type EventWhiteList struct {
	DedicatedTypeWhiteList  []string
	ServerlessTypeWhiteList []string
	NextGenTypeWhiteList    []string
	allTypesWhiteList       []string
	StatusWhiteList         []event.EventStatus
}

func (w EventWhiteList) IsClusterTypeAllowed(t, clusterType string) bool {
	types := []string{}
	switch clusterType {
	case ServerlessClusterType:
		types = w.ServerlessTypeWhiteList

	case DedicatedClusterType:
		types = w.DedicatedTypeWhiteList

	case NextGenClusterType:
		types = w.NextGenTypeWhiteList

	case AllClusterType:
		types = w.allEventType()
	}

	_, ok := lo.Find(types, func(i string) bool {
		return i == t
	})
	return ok
}

func (w EventWhiteList) allEventType() []string {
	if len(w.allTypesWhiteList) == 0 {
		events := []string{}
		events = append(events, w.ServerlessTypeWhiteList...)
		events = append(events, w.DedicatedTypeWhiteList...)
		events = append(events, w.NextGenTypeWhiteList...)

		// de-duplicate
		w.allTypesWhiteList = lo.Union(events)
	}
	return w.allTypesWhiteList
}

func (w EventWhiteList) IsStatusAllowed(s event.EventStatus) bool {
	if b, ok := displayStatusMap[s]; ok {
		return b
	}

	for _, v := range w.StatusWhiteList {
		if v == s {
			displayStatusMap[s] = true
			return true
		}
	}
	displayStatusMap[s] = false
	return false
}

func (s *Service) listEvents(c *gin.Context) {
	beginTSstr := c.Query("begin_ts")
	if beginTSstr == "" {
		c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "49900001", ErrMsg: "begin_ts cannot be empty"}})
		return
	}
	beginTS, err := strconv.ParseInt(beginTSstr, 10, 64)
	if err != nil {
		c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "49900001", ErrMsg: "begin_ts must by number"}})
	}
	endTSstr := c.Query("end_ts")
	if endTSstr == "" {
		c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "49900001", ErrMsg: "end_ts cannot be empty"}})
		return
	}
	endTS, err := strconv.ParseInt(endTSstr, 10, 64)
	if err != nil {
		c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "49900001", ErrMsg: "end_ts must be number"}})
		return
	}
	if endTS-beginTS > 60*60*24*7 {
		c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "49900001", ErrMsg: "time range must less than 7 days"}})
		return
	}

	clusterType := c.Query("cluster_type")
	if clusterType == "" {
		// c.JSON(http.StatusOK, ListEventsResp{BaseResp: common.BaseResp{ErrCode: "49900001", ErrMsg: "cluster_type cannot be empty"}})
		// return
		clusterType = AllClusterType
	}

	limitStr := c.Query("limit")
	limit, err := strconv.Atoi(limitStr)
	if err != nil || limit < 20 {
		limit = 20
	}

	tagFilter := []string{}
	status := c.Query("status")
	if status != "" {
		if s.EventWhiteList.IsStatusAllowed(event.EventStatus(status)) {
			tagFilter = append(tagFilter, "status::"+status)
		} else {
			c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "49900001", ErrMsg: "unsupported status"}})
			return
		}
	}
	_types := c.QueryArray("type")
	types := []string{}
	if len(_types) == 0 {
		switch clusterType {
		case ServerlessClusterType:
			types = s.EventWhiteList.ServerlessTypeWhiteList

		case DedicatedClusterType:
			types = s.EventWhiteList.DedicatedTypeWhiteList

		case NextGenClusterType:
			types = s.EventWhiteList.NextGenTypeWhiteList

		case AllClusterType:
			types = s.EventWhiteList.allEventType()
		}
	} else {
		for _, t := range _types {
			if s.EventWhiteList.IsClusterTypeAllowed(t, clusterType) {
				types = append(types, t)
			}
		}
	}

	activities, partial, newCursor, err := s.HubClient.ListActivitiesByTimeRange(
		c.Param("cid"),
		beginTS,
		endTS,
		c.GetHeader("cursor"),
		&activityhub.Filter{
			ApplicationID: c.Param("oid"),
			Type:          []string{"Event"},
			Source:        []string{s.Cfg.Activityhub.Source}, // only query events that wriited by "cloud_server"
			Author:        strings.ToLower(c.Query("author")),
			Name:          types,
			Limit:         limit,
			Tags:          tagFilter,
		})
	if err != nil {
		log.Error("get activities failed", zap.Error(err))
		c.JSON(http.StatusOK, ListEventsResp{BaseResp: BaseResp{ErrCode: "150000000", ErrMsg: "failed to fetch events"}})
		return
	}

	resp := ListEventsResp{
		Partial: partial,
		Cursor:  newCursor,
		Events:  []Event{},
	}
	for _, activity := range activities {
		summary := ""
		author := ""
		status := ""
		payload, ok := activity.Payload.(map[string]any)
		if ok {
			summary, _ = payload["summary"].(string)
			author, _ = payload["author"].(string)
			status, _ = payload["display_status"].(string)
		}
		resp.Events = append(resp.Events, Event{
			Type:            activity.DisplayName,
			CalibrationTime: activity.CalibrationTime,
			Status:          status,
			Author:          author,
			Summary:         summary,
		})
	}
	c.JSON(http.StatusOK, resp)
}

//	@Id				ListEvents
//	@Summary		List cloud cluster events
//	@Tags			cloud event
//	@Description	list cloud cluster events from activityhub
//	@Produce		json
//	@Param			oid				path		string		true	"organization id"
//	@Param			pid				path		string		true	"project id"
//	@Param			cid				path		string		true	"cluster id"
//	@Param			X-Provider		header		string		true	"provider aws/gcp"
//	@Param			X-Region		header		string		true	"region"
//	@Param			X-Org-Id		header		string		true	"organization id from dbaas"
//	@Param			X-Project-Id	header		string		true	"project id from dbaas"
//	@Param			X-Cluster-Id	header		string		true	"cluster id from dbaas"
//	@Param			cursor			header		string		false	"cursor for next page"
//	@Param			cluster_type	query		string		true	"cluster type"
//	@Param			begin_ts		query		string		true	"start time in unix seconds"
//	@Param			end_ts			query		string		true	"end time in unix seconds"
//	@Param			limit			query		string		false	"result limit"
//	@Param			status			query		string		false	"event status"
//	@Param			type			query		[]string	false	"task type list; if not specified, any type is allowed"
//	@Success		200				{object}	ListEventsResp
//	@Router			/orgs/{oid}/projects/{pid}/clusters/{cid}/events [get]
//
// ListEventsWithProject list cloud cluster events for project-based path.
func (s *Service) ListEventsWithProject(c *gin.Context) {
	s.listEvents(c)
}

//	@Id				ListEventsWithoutProject
//	@Summary		List cloud cluster events
//	@Tags			cloud event
//	@Description	list cloud cluster events from activityhub
//	@Produce		json
//	@Param			oid				path		string		true	"organization id"
//	@Param			cid				path		string		true	"cluster id"
//	@Param			X-Provider		header		string		true	"provider aws/gcp"
//	@Param			X-Region		header		string		true	"region"
//	@Param			X-Org-Id		header		string		true	"organization id from dbaas"
//	@Param			X-Cluster-Id	header		string		true	"cluster id from dbaas"
//	@Param			cursor			header		string		false	"cursor for next page"
//	@Param			cluster_type	query		string		true	"cluster type"
//	@Param			begin_ts		query		string		true	"start time in unix seconds"
//	@Param			end_ts			query		string		true	"end time in unix seconds"
//	@Param			limit			query		string		false	"result limit"
//	@Param			status			query		string		false	"event status"
//	@Param			type			query		[]string	false	"task type list; if not specified, any type is allowed"
//	@Success		200				{object}	ListEventsResp
//	@Router			/orgs/{oid}/clusters/{cid}/events [get]
//
// ListEvents list cloud cluster events for no-project path.
func (s *Service) ListEvents(c *gin.Context) {
	s.listEvents(c)
}

func (s *Service) listEventFilters(c *gin.Context) {
	clusterType := c.Query("cluster_type")
	if clusterType == "" {
		clusterType = AllClusterType
	}

	resp := ListEventFiltersResp{
		EventStatus: defaultDisplayStatus,
		EventTypes:  defaultDisplayType,
	}

	if len(s.EventWhiteList.StatusWhiteList) != 0 {
		resp.EventStatus = s.EventWhiteList.StatusWhiteList
	}

	switch clusterType {
	case ServerlessClusterType:
		resp.EventTypes = s.EventWhiteList.ServerlessTypeWhiteList

	case DedicatedClusterType:
		resp.EventTypes = s.EventWhiteList.DedicatedTypeWhiteList

	case NextGenClusterType:
		resp.EventTypes = s.EventWhiteList.NextGenTypeWhiteList

	case AllClusterType:
		resp.EventTypes = s.EventWhiteList.allEventType()
	}

	c.JSON(http.StatusOK, resp)
}

//	@Id				ListEventFilters
//	@Summary		List cloud cluster event filters
//	@Tags			cloud event
//	@Description	list cloud cluster events type and status filters
//	@Produce		json
//	@Param			oid				path		string	true	"organization id"
//	@Param			pid				path		string	true	"project id"
//	@Param			cid				path		string	true	"cluster id"
//	@Param			X-Provider		header		string	true	"provider aws/gcp"
//	@Param			X-Region		header		string	true	"region"
//	@Param			X-Org-Id		header		string	true	"organization id from dbaas"
//	@Param			X-Project-Id	header		string	true	"project id from dbaas"
//	@Param			X-Cluster-Id	header		string	true	"cluster id from dbaas"
//	@Param			cluster_type	query		string	true	"cluster type serverless/dedicated/nextgen"
//	@Success		200				{object}	ListEventFiltersResp
//	@Router			/orgs/{oid}/projects/{pid}/clusters/{cid}/eventfilters [get]
//
// ListEventFiltersWithProject list cloud cluster event filters for project-based path.
func (s *Service) ListEventFiltersWithProject(c *gin.Context) {
	s.listEventFilters(c)
}

//	@Id				ListEventFiltersWithoutProject
//	@Summary		List cloud cluster event filters
//	@Tags			cloud event
//	@Description	list cloud cluster events type and status filters
//	@Produce		json
//	@Param			oid				path		string	true	"organization id"
//	@Param			cid				path		string	true	"cluster id"
//	@Param			X-Provider		header		string	true	"provider aws/gcp"
//	@Param			X-Region		header		string	true	"region"
//	@Param			X-Org-Id		header		string	true	"organization id from dbaas"
//	@Param			X-Cluster-Id	header		string	true	"cluster id from dbaas"
//	@Param			cluster_type	query		string	true	"cluster type serverless/dedicated/nextgen"
//	@Success		200				{object}	ListEventFiltersResp
//	@Router			/orgs/{oid}/clusters/{cid}/eventfilters [get]
//
// ListEventFilters list cloud cluster event filters for no-project path.
func (s *Service) ListEventFilters(c *gin.Context) {
	s.listEventFilters(c)
}
