package api

import (
	"fmt"
	"net/http"
	"sort"
	"time"

	"github.com/tidbcloud/account/internel/svc/msp"
	orgsvc "github.com/tidbcloud/account/internel/svc/org"
	"github.com/tidbcloud/account/internel/svc/user"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

// create swag doc command: project root directory exec the command
// swag init -d ./ -g central/api/openapi_msp.go --tags Account --output central/docs/openapi/msp

// @title MSP (Managed Service Provider) Customer OPENAPI
// @version v1beta1
// @description This is a account system server for managed service provider (MSP) customer.
// @host msp.tidbapi.com
// @BasePath /v1beta1/msp
func RegisterMspCustomerRouter(e gin.IRouter) {
	r := e.Group("/v1beta1/msp")

	r.Use(openApiRouteAuthNFunc)
	// Check that the org to which apikey belongs must be org_tag=msp
	r.Use(mspOpenApiCheckApikeyFunc)

	r.GET("/mspCustomers/:customerOrgId", GetMspCustomerHandler)
	r.GET("/mspCustomers", ListMspCustomersHandler)
	//r.DELETE("/mspCustomers/:customerOrgId", DeleteMspCustomerHandler)
	r.POST("/customerSignupUrl", CreateMspCustomerSignupUrlHandler)

}

// @Tags Account
// @Summary Retrieve a single MSP customer
// @Description  This endpoint retrieves a single MSP customer by their customer org ID.
// @Schemes
// @Accept json
// @Produce json
// @Param customerOrgId path string true "The MSP customer org ID."
// @Success 200 {object} OpenApiMspCustomer
// @Failure 400  {object}  OpenApiError
// @Router /mspCustomers/{customerOrgId} [GET]
func GetMspCustomerHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "GetMspCustomerHandler")
	defer bm.OnExit(ctx)

	req := OpenApiMspCustomerOrgID{}
	if err := c.ShouldBindUri(&req); err != nil {
		tilog.Warn(ctx, "GetMspCustomerHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "GetMspCustomerHandler request", zap.Any("req", req))

	svc, _ := msp.GetMspSvc()
	customer, err := svc.GetCustomerByCustomerID(ctx, entity.ID(req.CustomerOrgId))
	if err != nil {
		tilog.Error(ctx, "GetMspCustomerHandler GetCustomerByCustomerID err", zap.Any("customerOrgId", req.CustomerOrgId), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	if customer == nil {
		tilog.Warn(ctx, "GetMspCustomerHandler customer not found", zap.Any("customerOrgId", req.CustomerOrgId))
		writeError(ctx, c.Writer, err, http.StatusNotFound, paramInvalidErrorCode, "param invalid")
	}

	keyOperator, _, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "GetMspCustomerHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, getMspCustomerResourceUri, customer.PartnerOrgID.Uint64(), 0)
	if err != nil {
		tilog.Error(ctx, "GetMspCustomerHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "GetMspCustomerHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	orgSvc, _ := orgsvc.GetOrgSvc()
	org, err := orgSvc.GetOrgByID(ctx, customer.CustomerOrgID.Uint64())
	if err != nil {
		tilog.Error(ctx, "GetMspCustomerHandler GetOrgByID error", zap.Any("orgId", customer.CustomerOrgID), zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}

	mspCustomer := convOpenApiMspCustomer(org, customer)
	tilog.Info(ctx, "GetMspCustomerHandler response", zap.Any("mspCustomer", mspCustomer))
	openApiWriteResult(c.Writer, mspCustomer)
}

func convOpenApiMspCustomer(org *entity.Tenant, mspCustomer *entity.PartnerCustomer) *OpenApiMspCustomer {
	id := fmt.Sprintf("%d", mspCustomer.CustomerOrgID.Uint64())
	customer := OpenApiMspCustomer{
		ID:      id,
		OrgID:   id,
		Email:   org.Email,
		OrgName: org.Name,
		State:   string(mspCustomer.Status),
	}
	if mspCustomer.ExpiredAt != nil && !mspCustomer.ExpiredAt.IsZero() {
		customer.ExpiredTime = formatTime(*mspCustomer.ExpiredAt)
	}
	if mspCustomer.EffectedAt != nil && !mspCustomer.EffectedAt.IsZero() {
		customer.EffectedTime = formatTime(*mspCustomer.EffectedAt)
	}
	return &customer
}

func formatTime(t time.Time) string {
	return t.Local().Format("2006-01-02 15:04:05")
}

// @Tags Account
// @Summary Get a list of MSP customers
// @Description  This endpoint returns a list of MSP customers.
// @Schemes
// @Accept json
// @Produce json
// @Param pageToken query string false "The page token of the next page."
// @Param pageSize query int false "The page size of the next page. If `pageSize` is set to 0, it returns all MSP customers in one page."
// @Success 200 {object} OpenApiListMspCustomerRsp
// @Failure 400  {object}  OpenApiError
// @Router /mspCustomers [GET]
func ListMspCustomersHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "ListMspCustomersHandler")
	defer bm.OnExit(ctx)

	keyOperator, orgId, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "ListMspCustomersHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, listMspCustomerResourceUri, orgId, 0)
	if err != nil {
		tilog.Error(ctx, "ListMspCustomersHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "ListMspCustomersHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	req := OpenApiPageReq{}
	if err = c.ShouldBindQuery(&req); err != nil {
		tilog.Info(ctx, "ListMspCustomersHandler ShouldBindQuery err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "ListMspCustomersHandler request", zap.Any("req", req))
	pageSize, pageToken := getPageInfo(ctx, req)
	prevId, err := getPrevIdFromPageToken(pageToken)
	if err != nil {
		tilog.Info(ctx, "ListMspCustomersHandler getPrevIdFromPageToken err", zap.Any("pageToken", pageToken), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}

	svc, _ := msp.GetMspSvc()
	customerMap, err := svc.GetCustomerByMspID(ctx, entity.ID(orgId))
	if err != nil {
		tilog.Error(ctx, "ListMspCustomersHandler GetCustomerByMspID error", zap.Any("orgID", orgId), zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	var dbMspCustomers []entity.PartnerCustomer
	var dbCustomerOrgMap = map[uint64]entity.Tenant{}
	for customer, org := range customerMap {
		dbMspCustomers = append(dbMspCustomers, customer)
		dbCustomerOrgMap[customer.CustomerOrgID.Uint64()] = org
	}
	sort.Slice(dbMspCustomers, func(i, j int) bool {
		return dbMspCustomers[i].CustomerOrgID < dbMspCustomers[j].CustomerOrgID
	})

	var retCustomers []OpenApiMspCustomer
	var count int32 = 0
	var curId uint64 = 0
	for _, customer := range dbMspCustomers {
		// prevId == 0 means from start
		if prevId != 0 && customer.CustomerOrgID.Uint64() <= prevId {
			continue
		}
		org := dbCustomerOrgMap[customer.CustomerOrgID.Uint64()]
		mspCustomer := convOpenApiMspCustomer(&org, &customer)
		retCustomers = append(retCustomers, *mspCustomer)

		curId = customer.CustomerOrgID.Uint64()
		count++
		if pageSize != 0 && count >= pageSize {
			break
		}
	}

	nextPageToken := ""
	if curId != 0 && curId != dbMspCustomers[len(dbMspCustomers)-1].CustomerOrgID.Uint64() {
		nextPageToken = createNextPageToken(curId)
	}

	rsp := OpenApiListMspCustomerRsp{
		MspCustomers:  retCustomers,
		NextPageToken: nextPageToken,
	}

	tilog.Info(ctx, "ListMspCustomersHandler response", zap.Any("rsp", rsp))
	openApiWriteResult(c.Writer, rsp)
}

// @Tags Account-Delete // just change tag to Account-Delete for no this api in doc
// @Summary Delete an existing MSP customer
// @Description  This endpoint deletes an existing MSP customer.
// @Schemes
// @Accept json
// @Produce json
// @Param customersOrgId path string true "The MSP customer org ID."
// @Success 200
// @Failure 400  {object}  OpenApiError
// @Router /mspCustomers/{customerOrgId} [DELETE]
func DeleteMspCustomerHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "DeleteMspCustomerHandler")
	defer bm.OnExit(ctx)

	req := OpenApiMspCustomerOrgID{}
	if err := c.ShouldBindUri(&req); err != nil {
		tilog.Warn(ctx, "DeleteMspCustomerHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "DeleteMspCustomerHandler request", zap.Any("req", req))

	svc, _ := msp.GetMspSvc()
	customer, err := svc.GetCustomerByCustomerID(ctx, entity.ID(req.CustomerOrgId))
	if err != nil {
		tilog.Error(ctx, "DeleteMspCustomerHandler GetCustomerByCustomerID err", zap.Any("customerOrgId", req.CustomerOrgId), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	if customer == nil || customer.Status != entity.Active {
		tilog.Warn(ctx, "DeleteMspCustomerHandler customer not found org status not active", zap.Any("customerOrgId", req.CustomerOrgId))
		writeError(ctx, c.Writer, err, http.StatusNotFound, paramInvalidErrorCode, "param invalid")
	}

	keyOperator, _, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "DeleteMspCustomerHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, deleteMspCustomerResourceUri, customer.PartnerOrgID.Uint64(), 0)
	if err != nil {
		tilog.Error(ctx, "DeleteMspCustomerHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "DeleteMspCustomerHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	err = svc.DeleteCustomer(ctx, *customer)
	if err != nil {
		tilog.Error(ctx, "DeleteMspCustomerHandler DeleteCustomer error", zap.Any("customer", customer), zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}

	tilog.Info(ctx, "DeleteMspCustomerHandler DeleteCustomer success", zap.Any("customer", customer))
}

// @Tags Account
// @Summary Create a new signup URL for an MSP customer
// @Description  This endpoint creates a new signup URL for an MSP customer.
// @Schemes
// @Accept json
// @Produce json
// @Param mspCustomerOrgId body OpenApiCreateMspCustomerSignupUrlReq true "The MSP customer org ID."
// @Success 200 {object} OpenApiMspCustomerSignupUrl
// @Failure 400  {object}  OpenApiError
// @Router /customerSignupUrl [POST]
func CreateMspCustomerSignupUrlHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "CreateMspCustomerSignupUrlHandler")
	defer bm.OnExit(ctx)

	/* delete  org id
	req := OpenApiCreateMspCustomerSignupUrlReq{}
		if err := c.ShouldBindJSON(&req); err != nil {
			tilog.Warn(ctx, "CreateMspCustomerSignupUrlHandler ShouldBindJSON err", zap.Error(err))
			writeError(ctx, c.Writer,  err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
			return
		}
		tilog.Info(ctx, "CreateMspCustomerSignupUrlHandler request", zap.Any("req", req))
		if req.OrgID == "" || req.OrgID == "0" {
			tilog.Warn(ctx, "CreateMspCustomerSignupUrlHandler orgID invalid", zap.Any("orgId", req.OrgID))
			writeError(ctx, c.Writer nil, http.StatusBadRequest, paramInvalidErrorCode, "orgId invalid")
			return
		}
		orgId, err := strconv.ParseUint(req.OrgID, 10, 64)
		if err != nil {
			tilog.Warn(ctx, "CreateMspCustomerSignupUrlHandler ParseUint err", zap.Any("orgId", req.OrgID), zap.Error(err))
			writeError(ctx, c.Writer,  err, http.StatusBadRequest, paramInvalidErrorCode, "orgId invalid")
			return
		}
	*/

	keyOperator, orgId, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "CreateMspCustomerSignupUrlHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, createMspCustomerSignupUrlResourceUri, orgId, 0)
	if err != nil {
		tilog.Error(ctx, "CreateMspCustomerSignupUrlHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "CreateMspCustomerSignupUrlHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	orgSvc, _ := orgsvc.GetOrgSvc()
	mspOrg, err := orgSvc.GetOrgByID(ctx, orgId)
	if err != nil || mspOrg == nil {
		tilog.Warn(ctx, "CreateMspCustomerSignupUrlHandler GetOrgByID err", zap.Any("orgId", orgId), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "orgId invalid")
		return
	}
	if mspOrg.OrgTag != string(entity.OrgTagMsp) && mspOrg.OrgTag != string(entity.OrgTagResalePartner) {
		tilog.Warn(ctx, "CreateMspCustomerSignupUrlHandler org is not msp org", zap.Any("orgId", orgId))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "org is not msp")
		return
	}
	var method string
	var partnerKey string
	var provider string
	switch mspOrg.OrgTag {
	case string(entity.OrgTagMsp):
		method = config.MspCustomerRegister
		provider = entity.MspThirdPartyAccountProvider
		partnerKey = "msp"
	case string(entity.OrgTagResalePartner):
		method = config.ResaleCustomerRegister
		provider = entity.ResaleThirdPartyAccountProvider
		partnerKey = "reseller"
	}
	userSvc, _ := user.GetUserSvc()
	registerToken, err := userSvc.CreateInviteToken(ctx, mspOrg.ID.Uint64(), mspOrg.Email, method)
	if err != nil {
		tilog.Error(ctx, "CreateMspCustomerSignupUrlHandler CreateInviteToken err", zap.Any("orgId", mspOrg.ID), zap.Any("email", mspOrg.Email), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "create token error")
		return
	}
	signupUrl := fmt.Sprintf("https://%s/signup?%s=%s&third_party_account_provider=%s&third_party_account_token=%s", config.Global().ServerConfig.FrontendDNS, partnerKey, mspOrg.Name, provider, registerToken.TokenStr)

	customerSignupUrl := OpenApiMspCustomerSignupUrl{
		CustomerSignupUrl: signupUrl,
		ExpiredAt:         formatTime(registerToken.InviteToken.ExpireAt),
	}
	tilog.Info(ctx, "CreateMspCustomerSignupUrlHandler response", zap.Any("customerSignupUrl", customerSignupUrl))
	//openApiWriteResult(c.Writer, &customerSignupUrl)
	SuccessPureJSON(c, signupUrl, http.StatusOK)

}

func mspOpenApiCheckApikeyFunc(c *gin.Context) {
	ctx := c.Request.Context()
	accessKey, orgId, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		c.Abort()
		return
	}
	orgSvc, _ := orgsvc.GetOrgSvc()
	mspOrg, err := orgSvc.GetOrgByID(ctx, orgId)
	if err != nil || mspOrg == nil || mspOrg.OrgTag != string(entity.OrgTagMsp) {
		tilog.Warn(ctx, "mspOpenApiCheckApikeyFunc not pass", zap.Any("orgId", orgId), zap.Any("accessKey", accessKey), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authenticateErrorCode, "Incorrect type of API key. The API requires an MSP API key.")
		c.Abort()
		return
	}

	tilog.Info(ctx, "mspOpenApiCheckApikeyNFunc  success", zap.Any("accessKey", accessKey))
	c.Next()
}

type OpenApiListMspCustomerRsp struct {
	// The list of matching MSP Customers.
	MspCustomers []OpenApiMspCustomer `json:"mspCustomers"`
	// `next_page_token` can be sent in a subsequent call to fetch more results
	NextPageToken string `json:"nextPageToken" example:"498c7f44-656f-4944-b455-0695faa5d4f9"`
}

func SuccessPureJSON(c *gin.Context, result interface{}, code int) {
	if result == nil {
		result = gin.H{}
	}

	c.PureJSON(code, result)
}
