package jira

import (
	"encoding/json"
	"fmt"

	"github.com/gin-gonic/gin"
	"go.uber.org/dig"

	"github.com/tidbcloud/devops/api"
	jiraapi "github.com/tidbcloud/devops/api/jira"
	jiraent "github.com/tidbcloud/devops/entity/jira"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	timeutil "github.com/tidbcloud/devops/pkg/infra/utils/time"
	jirasvcapi "github.com/tidbcloud/devops/pkg/service/jira/api"
)

type ControllerParam struct {
	dig.In

	Conf *config.RootConfig
	Jira jirasvcapi.JiraServiceAPI
}

type JiraController struct {
	jira jirasvcapi.JiraServiceAPI
}

func (c *JiraController) Name() string {
	return "JiraController"
}

func NewJiraController(p ControllerParam) ctrl.Controller {
	return &JiraController{
		jira: p.Jira,
	}
}

func (c *JiraController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router
	g := r.Group("/jira")
	{
		g.GET("/issue/unified_issues", c.ListUnifiedIssues)
		g.POST("/issue/unified_issues/search", c.ListUnifiedIssuesAdvanced)
		g.GET("/issue/unified_issues/:key", c.GetUnifiedIssue)

		g.POST("/issue/repo_issues", c.SyncIssueSnapshotByKey)
		g.POST("/issue/repo_issues/batch", c.SyncIssueSnapshotByJQL)
		g.POST("/issue/repo_issues/triage", c.UpsertIssueTriage)
		g.GET("/issue/repo_issues/triage/tag_definitions", c.ListIssueTriageTagDefinitions)
		g.POST("/issue/repo_issues/triage/tag_definitions", c.CreateIssueTriageTagDefinition)
		g.DELETE("/issue/repo_issues/triage/tag_definitions", c.DeleteIssueTriageTagDefinition)
		g.POST("/issue/repo_issues/timeline_summary", c.SetTimelineSummary)

		g.GET("/issue/jira_issues", c.ListIssuesFromJira)
		g.GET("/issue/jira_issues/:key", c.GetIssueFromJira)

	}

	return nil
}

// ListUnifiedIssues
//
//	@Id				JiraControllerListUnifiedIssues
//	@Description	list issues from local repo
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		jiraapi.ListUnifiedIssuesRequest	false	"request param"
//	@Success		200	{object}	jiraapi.ListUnifiedIssuesResponse
//	@Router			/api/v1/devops/jira/issue/unified_issues [get]
func (c *JiraController) ListUnifiedIssues(ctx *gin.Context) {
	var req jiraapi.ListUnifiedIssuesRequest
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	issues, total, err := c.jira.ListUnifiedIssues(ctx, buildListUnifiedIssuesParam(req))
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.ListUnifiedIssuesResponse{
		Total:  total,
		Issues: issues,
	}
	ctrl.Success(ctx, resp)
}

// ListUnifiedIssuesAdvanced
//
//	@Id				JiraControllerListUnifiedIssuesAdvanced
//	@Description	list issues from local repo
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.ListUnifiedIssuesRequest	false	"request param"
//	@Success		200	{object}	jiraapi.ListUnifiedIssuesResponse
//	@Router			/api/v1/devops/jira/issue/unified_issues/search [post]
func (c *JiraController) ListUnifiedIssuesAdvanced(ctx *gin.Context) {
	var req jiraapi.ListUnifiedIssuesRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	issues, total, err := c.jira.ListUnifiedIssues(ctx, buildListUnifiedIssuesParam(req))
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.ListUnifiedIssuesResponse{
		Total:  total,
		Issues: issues,
	}
	ctrl.Success(ctx, resp)
}

// GetUnifiedIssue
//
//	@Id				JiraControllerGetUnifiedIssue
//	@Description	get issue from local repo by key
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			key	path		string							true	"issue key"
//	@Param			req	query		jiraapi.GetUnifiedIssueRequest	false	"request param"
//	@Success		200	{object}	jiraapi.GetUnifiedIssueResponse
//	@Router			/api/v1/devops/jira/issue/unified_issues/{key} [get]
func (c *JiraController) GetUnifiedIssue(ctx *gin.Context) {
	key := ctx.Param("key")
	if key == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("issue key is required"), api.ErrCodeInvalidArgument)
		return
	}

	var req jiraapi.GetUnifiedIssueRequest
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := jirasvcapi.GetUnifiedIssueParam{
		WithTriage:          req.WithTriage,
		WithTimelineSummary: req.WithTimelineSummary,
	}
	issue, err := c.jira.GetUnifiedIssue(ctx, key, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.GetUnifiedIssueResponse{
		Issue: issue,
	}
	ctrl.Success(ctx, resp)
}

// SyncIssueSnapshotByKey
//
//	@Id				JiraControllerSyncIssueSnapshotByKey
//	@Description	store issue into local repo by key
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.SyncIssueSnapshotByKeyRequest	true	"request param"
//	@Success		200	{object}	jiraapi.SyncIssueSnapshotByKeyResponse
//	@Router			/api/v1/devops/jira/issue/repo_issues [post]
func (c *JiraController) SyncIssueSnapshotByKey(ctx *gin.Context) {
	var req jiraapi.SyncIssueSnapshotByKeyRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	issue, err := c.jira.SyncIssueSnapshotByKey(ctx, req.Key)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.SyncIssueSnapshotByKeyResponse{
		Issue: issue,
	}
	ctrl.Success(ctx, resp)
}

// SyncIssueSnapshotByJQL
//
//	@Id				JiraControllerSyncIssueSnapshotByJQL
//	@Description	store issues into local repo by JQL
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.SyncIssueSnapshotByJQLRequest	true	"request param"
//	@Success		200	{object}	jiraapi.SyncIssueSnapshotByJQLResponse
//	@Router			/api/v1/devops/jira/issue/repo_issues/batch [post]
func (c *JiraController) SyncIssueSnapshotByJQL(ctx *gin.Context) {
	var req jiraapi.SyncIssueSnapshotByJQLRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := jirasvcapi.SyncIssueSnapshotByJQLParam{
		MaxCount: req.MaxCount,
		FailFast: req.FailFast,
		PerPage:  req.PerPage,
	}

	result, err := c.jira.SyncIssueSnapshotByJQL(ctx, req.JQL, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.SyncIssueSnapshotByJQLResponse{
		Total:         result.Total,
		SuccessIssues: result.SuccessIssues,
		FailureIssues: result.FailureIssues,
	}
	ctrl.Success(ctx, resp)
}

// UpsertIssueTriage
//
//	@Id				JiraControllerUpsertIssueTriage
//	@Description	upsert issue triage
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.UpsertIssueTriageRequest	true	"request param"
//	@Success		200	{object}	api.BaseResp
//	@Router			/api/v1/devops/jira/issue/repo_issues/triage [post]
func (c *JiraController) UpsertIssueTriage(ctx *gin.Context) {
	var req jiraapi.UpsertIssueTriageRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	_, err = c.jira.UpsertIssueTriage(ctx, req.Triage)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ListIssueTriageTags
//
//	@Id				JiraControllerListIssueTriageTags
//	@Description	list issue triage tags
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.ListIssueTriageTagDefinitionsRequest	true	"request param"
//	@Success		200	{object}	jiraapi.ListIssueTriageTagDefinitionsResponse
//	@Router			/api/v1/devops/jira/issue/repo_issues/triage/tag_definitions [get]
func (c *JiraController) ListIssueTriageTagDefinitions(ctx *gin.Context) {
	var req jiraapi.ListIssueTriageTagDefinitionsRequest
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.LabelsContainJSON != "" {
		err := json.Unmarshal([]byte(req.LabelsContainJSON), &req.LabelsContain)
		if err != nil {
			ctrl.FailWithCode(ctx, fmt.Errorf("invalid labels_contain_json: %w", err), api.ErrCodeInvalidArgument)
			return
		}
	}

	param := jirasvcapi.ListIssueTriageTagDefinitionsParam{
		TypesIn:       req.TypesIn,
		NamesIn:       req.NamesIn,
		LabelsContain: req.LabelsContain,
	}
	tags, err := c.jira.ListIssueTriageTagDefinitions(ctx, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.ListIssueTriageTagDefinitionsResponse{
		Tags: tags,
	}
	ctrl.Success(ctx, resp)
}

// CreateIssueTriageTagDefinition
//
//	@Id				JiraControllerCreateIssueTriageTagDefinition
//	@Description	create a issue triage tag
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.CreateIssueTriageTagDefinitionRequest	true	"request param"
//	@Success		200	{object}	jiraapi.CreateIssueTriageTagDefinitionResponse
//	@Router			/api/v1/devops/jira/issue/repo_issues/triage/tag_definitions [post]
func (c *JiraController) CreateIssueTriageTagDefinition(ctx *gin.Context) {
	var req jiraapi.CreateIssueTriageTagDefinitionRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	tag, err := c.jira.CreateIssueTriageTagDefinition(ctx, req.Tag)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.CreateIssueTriageTagDefinitionResponse{
		Tag: tag,
	}
	ctrl.Success(ctx, resp)
}

// DeleteIssueTriageTagDefinition
//
//	@Id				JiraControllerDeleteIssueTriageTagDefinition
//	@Description	delete a issue triage tag
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.DeleteIssueTriageTagDefinitionRequest	true	"request param"
//	@Success		200	{object}	api.BaseResp
//	@Router			/api/v1/devops/jira/issue/repo_issues/triage/tag_definitions [delete]
func (c *JiraController) DeleteIssueTriageTagDefinition(ctx *gin.Context) {
	var req jiraapi.DeleteIssueTriageTagDefinitionRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.jira.DeleteIssueTriageTagDefinition(ctx, req.Name, req.Type)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// SetTimelineSummary
//
//	@Id				JiraControllerSetTimelineSummary
//	@Description	set issue timeline summary
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	body		jiraapi.SetIssueTimelineSummaryRequest	true	"request param"
//	@Success		200	{object}	api.BaseResp
//	@Router			/api/v1/devops/jira/issue/repo_issues/timeline_summary [post]
func (c *JiraController) SetTimelineSummary(ctx *gin.Context) {
	var req jiraapi.SetIssueTimelineSummaryRequest
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.jira.SetIssueTimelineSummary(ctx, req.Key, req.Summary)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	ctrl.Success(ctx, nil)
}

// ListIssuesFromJira
//
//	@Id				JiraControllerListIssuesFromJira
//	@Description	list issues from jira
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			req	query		jiraapi.ListIssuesFromJiraRequest	false	"request param"
//	@Success		200	{object}	jiraapi.ListIssuesFromJiraResponse
//	@Router			/api/v1/devops/jira/issue/jira_issues [get]
func (c *JiraController) ListIssuesFromJira(ctx *gin.Context) {
	var req jiraapi.ListIssuesFromJiraRequest
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := jirasvcapi.ListIssuesFromJiraParam{
		WithComments: req.WithComment,
		PageSize:     req.PerPage,
		PageToken:    req.PageToken,
	}
	result, err := c.jira.ListIssuesFromJira(ctx, req.JQL, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.ListIssuesFromJiraResponse{
		Issues:        result.Issues,
		IsLast:        result.IsLast,
		NextPageToken: result.NextPageToken,
	}
	ctrl.Success(ctx, resp)
}

// GetIssueFromJira
//
//	@Id				JiraControllerGetIssueFromJira
//	@Description	get issue from jira
//	@Tags			JiraController
//	@Accept			json
//	@Produce		json
//	@Param			key	path		string							true	"issue key"
//	@Param			req	query		jiraapi.GetIssueFromJiraRequest	false	"request param"
//	@Success		200	{object}	jiraapi.GetIssueFromJiraResponse
//	@Router			/api/v1/devops/jira/issue/jira_issues/{key} [get]
func (c *JiraController) GetIssueFromJira(ctx *gin.Context) {
	key := ctx.Param("key")
	if key == "" {
		ctrl.FailWithCode(ctx, fmt.Errorf("issue key is required"), api.ErrCodeInvalidArgument)
		return
	}

	var req jiraapi.GetIssueFromJiraRequest
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	param := jirasvcapi.GetIssueFromJiraParam{
		WithComments: req.WithComments,
	}
	issue, err := c.jira.GetIssueFromJira(ctx, key, param)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	resp := &jiraapi.GetIssueFromJiraResponse{
		Issue: issue,
	}
	ctrl.Success(ctx, resp)
}

func buildListUnifiedIssuesParam(req jiraapi.ListUnifiedIssuesRequest) jirasvcapi.ListUnifiedIssuesParam {
	param := jirasvcapi.ListUnifiedIssuesParam{
		Page:    uint32(req.Page),
		PerPage: uint32(req.PerPage),
	}
	if req.HaveTriage != nil {
		param.HaveTriage = req.HaveTriage
	}
	if req.HaveTimelineSummary != nil {
		param.HaveTimelineSummary = req.HaveTimelineSummary
	}
	if len(req.StatusIn) > 0 {
		param.StatusIn = pointer.Pointer(req.StatusIn)
	}
	if req.CreatedAfter != nil {
		param.CreatedAfter = timeutil.TsToTime(req.CreatedAfter)
	}
	if req.CreatedBefore != nil {
		param.CreatedBefore = timeutil.TsToTime(req.CreatedBefore)
	}
	if len(req.ProjectIn) > 0 {
		param.ProjectKeyIn = pointer.Pointer(req.ProjectIn)
	}
	if req.CustomerEnvIs != nil {
		param.CustomerEnvIs = pointer.Pointer(jiraent.IssueCustomerEnvironment(*req.CustomerEnvIs))
	}
	if len(req.ClusterIDIn) > 0 {
		param.ClusterIDIn = pointer.Pointer(req.ClusterIDIn)
	}
	if len(req.TenantIDIn) > 0 {
		param.TenantIDIn = pointer.Pointer(req.TenantIDIn)
	}
	if len(req.ComponentIn) > 0 {
		param.ComponentIn = pointer.Pointer(req.ComponentIn)
	}
	if len(req.IssueKeyIn) > 0 {
		param.KeyIn = pointer.Pointer(req.IssueKeyIn)
	}
	if req.WithTriage {
		param.WithTriage = true
	}
	if req.WithTimelineSummary {
		param.WithTimelineSummary = true
	}
	if req.WithJira {
		param.WithJira = true
	}
	if req.WithJiraComments {
		param.WithJiraComments = true
	}

	return param
}
