package api

import (
	"errors"
	"fmt"
	"strings"

	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"go.uber.org/zap"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/auditloghandler"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/model/marketing/plan"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
)

// @Summary Create Credits
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param credits body forms.CreateCreditsPackageReq true "credits"
// @Success 200 {object} forms.CreditsPackage
// @Router /tenants/{tenant_id}/credits [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/credits [post]
func (s *API) CreateCredits(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	req := &forms.CreateCreditsPackageReq{}
	err = c.ShouldBindJSON(req)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	// create method, body and url param deal.
	// url param replace body.
	input := &forms.CreditsPackage{
		StartDate:             req.StartDate,
		ExpiredDate:           req.ExpiredDate,
		TotalCredits:          req.TotalCredits,
		Usage:                 req.Usage,
		Note:                  req.Note,
		BindTypes:             req.BindTypes,
		UnitFilters:           req.UnitFilter,
		BindedBillingConfigID: &billingCfgStruct.BillingConfigID,
		UsageExtend:           req.UsageExtend,
		DiscountEnabled:       req.DiscountEnabled,
		CopyUnitFilterArgs:    req.CopyUnitFilterArgs,
		ForeignID:             req.ForeignID,
		UserEmail:             req.UserEmail,
	}

	isMspPayment, err := paymentModel.IsMspPayment(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	if isMspPayment {
		return nil, billing_errors.NewInvalidInputError(errors.New("MSP Customer  don't support credits."))
	}
	if len(input.UnitFilters) == 0 {
		if len(input.BindTypes) != 0 {
			input.UnitFilters = input.BindTypes
		} else {
			return nil, billing_errors.NewInvalidInputError(errors.New("unit_filter is empty"))
		}
	}
	ufModel := &unit_filter.Model{}
	for i, t := range input.UnitFilters {
		var tt *forms.UnitFilterSlim
		if t.FilterID != 0 {
			tt, err = ufModel.GetUnitFilterSlim(t.FilterID)
		} else if t.TypeID != 0 {
			tt, err = ufModel.GetUnitFilterSlim(t.TypeID)
		} else {
			return nil, billing_errors.NewInvalidInputError(errors.New("unknown unit filters"))
		}
		if err != nil {
			return nil, err
		}
		input.UnitFilters[i] = tt
	}
	if config.Get().GetBool(config.NonPOCCreditsValidate) {
		if input.Usage != entity.CreditsPackageUsage_POC {
			tenantPlanModel := &plan.TenantPlanModel{}
			planInfo, err := tenantPlanModel.GetTenantOnDemandPlanInfo(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
			if err != nil {
				return nil, err
			}
			if planInfo.Status == entity.TenantPlanStatus_NotStarted {
				return nil, billing_errors.NewInvalidInputError(errors.New("tenant not ondemond, failed to bind no-poc usage credits"))
			}
		}
	}
	copyArgs, err := ufModel.GetCopyArgs(&req.CopyUnitFilterArgs)
	if err != nil {
		return nil, err
	}
	if copyArgs != nil {
		log.Info("need create copied unit filters", zap.Any("copyArgs", copyArgs))
		input.UnitFilters, err = ufModel.CreateCopiedUnitFilters(input.UnitFilters, copyArgs)
		if err != nil {
			return nil, err
		}
	}
	creditsPackageModel := &marketing.CreditsPackageModel{}
	return creditsPackageModel.CreateCreditsPackage(billingCfgStruct.TenantID, input)
}

// @Summary List Tenants Credits
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} []forms.CreditsPackage
// @Router /tenants/{tenant_id}/credits [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/credits [get]
func (s *API) ListTenantCreditsPackages(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	defer func() {
		auditloghandler.Log(auditlog.EventShowCredits, c, billingCfgStruct.TenantID, nil, retErr)
	}()
	creditsPackageModel := &marketing.CreditsPackageModel{}
	res, err := creditsPackageModel.ListTenantCreditsPackages(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	return res, err
}

// @Summary Get Tenants Credits Summary
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} forms.CreditsSummary
// @Router /tenants/{tenant_id}/credits_summary [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/credits_summary [get]
func (s *API) GetTenantCreditsSummary(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	creditsPackageModel := &marketing.CreditsPackageModel{}
	return creditsPackageModel.GetTenantCreditsSummary(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
}

type CreditURIStruct struct {
	TenantID uint `uri:"tenant_id" binding:"required"`
	CreditID uint `uri:"credit_id" binding:"required"`
}

// @Summary Get Credits
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param credit_id path string true "credit_id"
// @Success 200 {object} forms.CreditsPackage
// @Router /tenants/{tenant_id}/credits/{credit_id} [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/credits/{credit_id} [get]
func (s *API) GetCreditsPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	creditIDStruct := &CreditURIStruct{}
	err := c.ShouldBindUri(creditIDStruct)
	if err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	creditsPackageModel := &marketing.CreditsPackageModel{}
	return creditsPackageModel.GetCreditsPackage(creditIDStruct.CreditID)
}

// @Summary Reclaim Credits
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param credit_id path string true "credit_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/credits/{credit_id}/actions/reclaim [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/credits/{credit_id}/actions/reclaim [post]
func (s *API) ReclaimCreditsPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	creditIDStruct := &CreditURIStruct{}
	err := c.ShouldBindUri(creditIDStruct)
	if err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	creditsPackageModel := &marketing.CreditsPackageModel{}
	err = creditsPackageModel.CloseCreditsPackage(creditIDStruct.CreditID, entity.CreditsPackageClosedReason_Reclaimed)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Get All Opened Credits TenantIDs
// @Schemes
// @Accept json
// @Produce json
// @Success 200 {object} []forms.TenantID
// @Router /opened_credits/tenant_ids [get]
func (s *API) GetOpenedCreditsTenantIDS(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	creditsPackageModel := &marketing.CreditsPackageModel{}
	return creditsPackageModel.GetAllCreditsOpenedTenantIDs()
}

func (s *API) creditsStatusToState(status string) forms.CreditsState {
	switch status {
	case entity.CreditsPackageStatus_InActive:
		return forms.CreditsState_Inactive
	case entity.CreditsPackageStatus_Active:
		return forms.CreditsState_Active
	case entity.CreditsPackageStatus_BeforeActive:
		return forms.CreditsState_BeforeActive
	}
	log.Error("unknown credits package status", zap.String("status", status))
	return forms.CreditsState_Inactive
}

// @Id Billing_ListCredits
// @Summary List credits for an organization
// @Tags Partner, PartnerBilling
// @Description This endpoint retrieves a list of credits for an organization.
// @Schemes
// @Accept json
// @Produce json
// @Param accessOrgId query string true "org_id"
// @Success 200 {object} []forms.CreditsForOpenAPI
// @Router /credits [get]
func (s *API) ListPartnerCreditsForOpenAPI(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, err := getBillingCfgFromAuth(c)
	if err != nil {
		return nil, err
	}
	defer func() {
		auditloghandler.Log(auditlog.EventShowCredits, c, billingCfgStruct.TenantID, nil, retErr)
	}()
	creditsPackageModel := &marketing.CreditsPackageModel{}
	var creditsPackages []forms.CreditsPackage
	switch billingCfgStruct.Type {
	case entity.BillingConfigType_MSP, entity.BillingConfigType_ResalePartner:
		creditsPackages, err = creditsPackageModel.ListTenantCreditsPackages(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
		if err != nil {
			return nil, err
		}
	case entity.BillingConfigType_MarketplaceReseller, entity.BillingConfigType_DirectCustomer:
		log.Info("need to retrieve credits from managing billing configs", zap.String("billingConfigType", billingCfgStruct.Type), zap.Uint("tenantID", billingCfgStruct.TenantID))
		billingConfigs, err := (&billingconfig.BillingConfigModel{}).GetManagingBillingConfigs(billingCfgStruct.TenantID)
		if err != nil {
			return nil, err
		}
		if len(billingConfigs) == 0 {
			log.Info("no managing billing configs", zap.Uint("tenantID", billingCfgStruct.TenantID))
			return
		}
		for _, bcfg := range billingConfigs {
			log.Info("start to retrieve credits from managing billing configs", zap.Uint("tenantID", billingCfgStruct.TenantID), zap.Uint("managedTenantID", bcfg.ManagedTenantID), zap.Uint("managingBillingConfigID", bcfg.BillingConfigID))
			customerCreditsList, err := creditsPackageModel.ListTenantCreditsPackages(billingCfgStruct.TenantID, bcfg.BillingConfigID)
			if err != nil {
				return nil, err
			}
			for i := 0; i != len(customerCreditsList); i++ {
				customerCreditsList[i].Customers = []string{fmt.Sprint(bcfg.ManagedTenantID)}
			}
			creditsPackages = append(creditsPackages, customerCreditsList...)
		}
	}
	res := make([]forms.CreditsForOpenAPI, len(creditsPackages))
	for i, credit := range creditsPackages {
		var names []string
		for _, filter := range credit.BindTypes {
			names = append(names, filter.Name)
		}
		res[i] = forms.CreditsForOpenAPI{
			CreditCode:      credit.CreditsCode,
			State:           s.creditsStatusToState(credit.Status),
			Services:        strings.Join(names, ", "),
			Credit:          credit.TotalCredits,
			RemainingCredit: credit.RemainingCredits,
			StartDate:       credit.StartDate,
			ExpiryDate:      credit.ExpiredDate,
			Provider:        *credit.Provider,
			Customers:       credit.Customers,
		}
	}
	return res, err
}

func InitializeCreditsRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("GET", "/tenants/:tenant_id/credits", s.ListTenantCreditsPackages)
	router.HandleWithError("GET", "/tenants/:tenant_id/credits_summary", s.GetTenantCreditsSummary)
	router.HandleWithError("GET", "/opened_credits/tenant_ids", s.GetOpenedCreditsTenantIDS)
	router.HandleWithError("POST", "/tenants/:tenant_id/credits", s.CreateCredits)
	router.HandleWithError("POST", "/tenants/:tenant_id/credits/:credit_id", s.GetCreditsPackage)
	router.HandleWithError("POST", "/tenants/:tenant_id/credits/:credit_id/actions/reclaim", s.ReclaimCreditsPackage)

	// for billing config add api
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/credits", s.ListTenantCreditsPackages)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/credits_summary", s.GetTenantCreditsSummary)
	//router.HandleWithError("GET", "/opened_credits/tenant_ids", s.GetOpenedCreditsTenantIDS)
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/credits", s.CreateCredits)
	//no modify
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/credits/:credit_id", s.GetCreditsPackage)
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/credits/:credit_id/actions/reclaim", s.ReclaimCreditsPackage)
}

func InitializeCreditsRouterForPartnerOpenAPI(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("GET", "/credits", s.ListPartnerCreditsForOpenAPI)

}
