package feature_wl

import (
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	userapiv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	"github.com/tidbcloud/devops/api"
	featurewlapi "github.com/tidbcloud/devops/api/feature-wl"
	auditent "github.com/tidbcloud/devops/entity/audit"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/clients/accountcli"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	auditsvc "github.com/tidbcloud/devops/pkg/service/audit"
	"go.uber.org/dig"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type ControllerParam struct {
	dig.In

	AccountCli   accountcli.Client
	AuditService auditsvc.AuditServiceAPI
}

type FeatureWhitelistController struct {
	accountCli accountcli.Client
	auditSvc   auditsvc.AuditServiceAPI
}

func (c *FeatureWhitelistController) Name() string {
	return "MgmtPortalController"
}

func NewFeatureWhitelistController(p ControllerParam) ctrl.Controller {
	return &FeatureWhitelistController{
		accountCli: p.AccountCli,
		auditSvc:   p.AuditService,
	}
}

func (c *FeatureWhitelistController) RegisterAPI(suite *ctrl.Suite) error {
	r := suite.Router
	group := r.Group("/feature-wl")
	{
		group.GET("", c.ListFeatureWhitelists)
		group.POST("", c.CreateFeatureWhitelist)
		group.PUT("/:name", c.UpdateFeatureWhitelist)
		group.DELETE("/:name", c.DeleteFeatureWhitelist)
	}
	return nil
}

// CreateFeatureWhitelist
//
//	@Id				FeatureWhitelistControllerCreateFeatureWhitelist
//	@Description	create feature whitelist
//	@Tags			FeatureWhitelistController
//	@Accept			json
//	@Produce		json
//	@Param			req		body		featurewlapi.FeatureWhitelistInfo	true	"request param"
//	@Success		200		{object}	api.BaseResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/feature-wl [post]
func (c *FeatureWhitelistController) CreateFeatureWhitelist(ctx *gin.Context) {
	req := &featurewlapi.FeatureWhitelistInfo{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	if req.Name == "" {
		ctrl.FailWithCode(ctx, errors.New("feature name is blank"), api.ErrCodeInvalidArgument)
		return
	}
	if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(req.Name) {
		ctrl.FailWithCode(ctx, errors.New("feature name can only contain letters, digits and underscores"), api.ErrCodeInvalidArgument)
		return
	}

	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)
	// set creator
	req.Owner = userProfile.UserEmail

	accountCliReq, err := toAccountCliFeatureWhitelistInfo(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.accountCli.CreateFeatureWhitelist(ctx, accountCliReq)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	marshal, _ := json.Marshal(req)
	_ = c.auditSvc.Create(ctx, &auditent.AuditLog{
		Type:      auditent.AuditLogTypeCtl,
		ObjectID:  req.Name,
		Operation: "create feature whitelist",
		Operator:  req.Owner,
		OperateAt: pointer.Pointer(time.Now()),
		Details:   marshal,
	})

	ctrl.Success(ctx, nil)
}

// UpdateFeatureWhitelist
//
//	@Id				FeatureWhitelistControllerUpdateFeatureWhitelist
//	@Description	update feature whitelist
//	@Tags			FeatureWhitelistController
//	@Accept			json
//	@Produce		json
//	@Param			name	path		string								true	"feature name"
//	@Param			req		body		featurewlapi.FeatureWhitelistInfo	true	"request param"
//	@Success		200		{object}	api.BaseResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/feature-wl/{name} [put]
func (c *FeatureWhitelistController) UpdateFeatureWhitelist(ctx *gin.Context) {
	name := ctx.Param("name")
	if name == "" {
		ctrl.FailWithCode(ctx, errors.New("feature name is blank"), api.ErrCodeInvalidArgument)
		return
	}

	req := &featurewlapi.FeatureWhitelistInfo{}
	err := ctx.ShouldBindJSON(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}
	req.Name = name

	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)
	req.Owner = userProfile.UserEmail

	accountCliReq, err := toAccountCliFeatureWhitelistInfo(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	err = c.accountCli.UpdateFeatureWhitelist(ctx, accountCliReq)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	marshal, _ := json.Marshal(req)
	_ = c.auditSvc.Create(ctx, &auditent.AuditLog{
		Type:      auditent.AuditLogTypeCtl,
		ObjectID:  req.Name,
		Operation: "update feature whitelist",
		Operator:  req.Owner,
		OperateAt: pointer.Pointer(time.Now()),
		Details:   marshal,
	})

	ctrl.Success(ctx, nil)
}

// DeleteFeatureWhitelist
//
//	@Id				FeatureWhitelistControllerDeleteFeatureWhitelist
//	@Description	delete feature whitelist
//	@Tags			FeatureWhitelistController
//	@Accept			json
//	@Produce		json
//	@Param			name	path		string	true	"feature name"
//	@Success		200		{object}	api.BaseResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/feature-wl/{name} [delete]
func (c *FeatureWhitelistController) DeleteFeatureWhitelist(ctx *gin.Context) {
	name := ctx.Param("name")
	if name == "" {
		ctrl.FailWithCode(ctx, errors.New("feature name is blank"), api.ErrCodeInvalidArgument)
		return
	}

	user, ok := ctx.Get(consts.CtxUserProfile)
	if !ok {
		ctrl.FailWithCode(ctx, fmt.Errorf("user profile not found"), api.ErrCodeUnauthenticated)
		return
	}
	userProfile := user.(*userapiv1.PrincipalProfile)

	err := c.accountCli.DeleteFeatureWhitelist(ctx, name)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	_ = c.auditSvc.Create(ctx, &auditent.AuditLog{
		Type:      auditent.AuditLogTypeCtl,
		ObjectID:  name,
		Operation: "delete feature whitelist",
		Operator:  userProfile.UserEmail,
		OperateAt: pointer.Pointer(time.Now()),
		Details:   []byte(`{"name":"` + name + `"}`),
	})

	ctrl.Success(ctx, nil)
}

// ListFeatureWhitelists
//
//	@Id				FeatureWhitelistControllerListFeatureWhitelists
//	@Description	list feature whitelists
//	@Tags			FeatureWhitelistController
//	@Accept			json
//	@Produce		json
//	@Param			req		query		accountcli.ListFeatureWhitelistsReq	false	"request param"
//	@Success		200		{object}	featurewlapi.ListFeatureWhitelistsResp
//	@Failure		default	{object}	api.BaseResp
//	@Router			/api/v1/devops/feature-wl [get]
func (c *FeatureWhitelistController) ListFeatureWhitelists(ctx *gin.Context) {
	req := &accountcli.ListFeatureWhitelistsReq{}
	err := ctx.ShouldBindQuery(req)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return
	}

	result, err := c.accountCli.ListFeatureWhitelists(ctx, req)
	if err != nil {
		ctrl.Fail(ctx, err)
		return
	}

	items := make([]*featurewlapi.FeatureWhitelistInfo, 0, len(result.Items))
	for _, item := range result.Items {
		items = append(items, fromAccountCliFeatureWhitelistInfo(item))
	}

	ctrl.Success(ctx, &featurewlapi.ListFeatureWhitelistsResp{
		Total:      result.Total,
		TotalPages: result.TotalPages,
		Page:       result.Page,
		Items:      items,
	})
}

func toAccountCliFeatureWhitelistInfo(info *featurewlapi.FeatureWhitelistInfo) (*accountcli.FeatureWhitelistInfo, error) {
	orgIds := make([]uint64, 0, len(info.OrgIds))
	for _, idStr := range info.OrgIds {
		id, err := strconv.ParseUint(idStr, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid org_id %q: %w", idStr, err)
		}
		orgIds = append(orgIds, id)
	}
	result := &accountcli.FeatureWhitelistInfo{
		Name:          info.Name,
		Description:   info.Description,
		Owner:         info.Owner,
		EmailSuffixes: info.EmailSuffixes,
		MinOrgId:      info.MinOrgId,
		OrgIds:        orgIds,
	}
	if info.CreatedAt != nil {
		result.CreatedAt = timestamppb.New(*info.CreatedAt)
	}
	if info.UpdatedAt != nil {
		result.UpdatedAt = timestamppb.New(*info.UpdatedAt)
	}
	return result, nil
}

func fromAccountCliFeatureWhitelistInfo(info *accountcli.FeatureWhitelistInfo) *featurewlapi.FeatureWhitelistInfo {
	orgIds := make([]string, 0, len(info.OrgIds))
	for _, id := range info.OrgIds {
		orgIds = append(orgIds, strconv.FormatUint(id, 10))
	}
	result := &featurewlapi.FeatureWhitelistInfo{
		Name:          info.Name,
		Description:   info.Description,
		Owner:         info.Owner,
		EmailSuffixes: info.EmailSuffixes,
		MinOrgId:      info.MinOrgId,
		OrgIds:        orgIds,
	}
	if info.CreatedAt != nil {
		t := info.CreatedAt.AsTime()
		result.CreatedAt = &t
	}
	if info.UpdatedAt != nil {
		t := info.UpdatedAt.AsTime()
		result.UpdatedAt = &t
	}
	return result
}
