package api

import (
	"errors"
	"strconv"
	"time"

	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"

	"github.com/pingcap/dbaas/billing2/db/entity"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"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/utils/ginutils"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
)

type SupportPlanAPI struct{}

// @Summary Get Tenant Active Support Plan
// @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.BindedSupportPlan
// @Router /tenants/{tenant_id}/binded_support_plan [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/binded_support_plan [get]
func (*SupportPlanAPI) GetActiveTenantSupportPlan(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	method, err := paymentModel.GetActivePaymentMethod(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil && !errors.Is(err, billing_errors.ObjectNotExistsError) {
		return false, err
	}
	supportPlanModel := &marketing.SupportPlanModel{}
	if err == nil && method.PaymentType == entity.PaymentType_MSP {
		tenant, err := strconv.ParseUint(method.PaymentCode, 10, 0)
		if err != nil {
			return false, billing_errors.NewSystemError(err)
		}
		mspTenantID := uint(tenant)
		info, err := supportPlanModel.GetTenantActiveBindedPlan(mspTenantID)
		if err != nil {
			return nil, err
		}
		res := forms.BindedSupportPlan{}
		res.Level = info.Level
		res.TenantID = billingCfgStruct.TenantID
		res.Status = info.Status
		return res, nil
	}

	info, err := supportPlanModel.GetTenantActiveBindedPlan(billingCfgStruct.TenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return marketing.DefaultBindedSupportPlan(billingCfgStruct.TenantID), nil
		}
		return nil, err
	}
	return info, nil
}

// @Summary List Tenant Support Plans
// @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.BindedSupportPlan
// @Router /tenants/{tenant_id}/binded_support_plan/list [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/binded_support_plan/list [get]
func (*SupportPlanAPI) ListTenantSupportPlans(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	method, err := paymentModel.GetActivePaymentMethod(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil && !errors.Is(err, billing_errors.ObjectNotExistsError) {
		return false, err
	}
	supportPlanModel := &marketing.SupportPlanModel{}
	if err == nil && method.PaymentType == entity.PaymentType_MSP {
		tenant, err := strconv.ParseUint(method.PaymentCode, 10, 0)
		if err != nil {
			return false, billing_errors.NewSystemError(err)
		}
		mspTenantID := uint(tenant)
		info, err := supportPlanModel.GetTenantActiveBindedPlan(mspTenantID)
		if err != nil {
			return nil, err
		}
		res := []forms.BindedSupportPlan{{
			SupportPlan: forms.SupportPlan{
				Level: info.Level,
			},
			TenantID: billingCfgStruct.TenantID,
			Status:   info.Status,
		}}
		return res, nil
	}
	var ret []*forms.BindedSupportPlan
	for _, status := range []string{entity.BindedSupportPlanStatus_ACTIVE, entity.BindedSupportPlanStatus_TOBEACTIVATED, entity.BindedSupportPlanStatus_Expired, entity.BindedSupportPlanStatus_CANCELED} {
		plans, err := supportPlanModel.ListTenantsBindedSupportPlan(billingCfgStruct.TenantID, status)
		if err != nil {
			return nil, err
		}
		if status == entity.BindedSupportPlanStatus_ACTIVE && len(plans) == 0 {
			ret = append(ret, marketing.DefaultBindedSupportPlan(billingCfgStruct.TenantID))
		} else {
			for _, p := range plans {
				if p.SupportPlanID != 0 {
					ret = append(ret, p)
				}
			}
		}
	}
	return ret, nil
}

// @Summary Get Public Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Success 200 {object} []forms.SupportPlan
// @Router /support_plan [get]
func (*SupportPlanAPI) ListAllPublicSupportPlan(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	supportPlanModel := &marketing.SupportPlanModel{}
	return supportPlanModel.ListPublicSupportPlan()
}

type SupportPlanURIStruct struct {
	SupportPlanID uint `uri:"support_plan_id" binding:"required"`
}

// @Summary Update Public Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Param UpdateSupportPlanReq body forms.SupportPlanInput true "UpdateSupportPlanReq"
// @Success 200 {object} forms.SupportPlan
// @Router /support_plan/{support_plan_id} [put]
func (*SupportPlanAPI) UpdatePublicSupportPlan(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	supportPlanURIStruct := &SupportPlanURIStruct{}
	err := c.ShouldBindUri(supportPlanURIStruct)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	input := &forms.SupportPlan{}
	err = c.ShouldBindJSON(input)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	supportPlanModel := &marketing.SupportPlanModel{}

	info, err := supportPlanModel.GetSupportPlan(supportPlanURIStruct.SupportPlanID)
	if err != nil {
		return nil, err
	}
	if !info.Public {
		return nil, billing_errors.NewInvalidInputError(errors.New("private support plan can not be update"))
	}

	return supportPlanModel.UpdateSupportPlan(supportPlanURIStruct.SupportPlanID, input)
}

// @Summary Tenant Bind Public Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param BindSupportPlanReq body forms.BindedSupportPlan true "BindSupportPlanReq"
// @Success 200 {object} forms.BindedSupportPlan
// @Router /tenants/{tenant_id}/binded_support_plan/public [post]
func (*SupportPlanAPI) TenantBindPublicSupportPlan(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	input := &forms.BindedSupportPlanInput{}
	err = c.ShouldBindJSON(input)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	model := &billingconfig.BillingConfigModel{}
	isMspPayment, err := model.IsMspWithCache(tenantID)
	if err != nil {
		return nil, err
	}
	if isMspPayment {
		return nil, billing_errors.NewInvalidInputError(errors.New("MSP Customer dose not support subscribing support plan"))
	}
	supportPlanModel := &marketing.SupportPlanModel{}
	supportPlan, err := supportPlanModel.GetSupportPlan(input.SupportPlanID)
	if err != nil {
		return nil, err
	}
	defer func() {
		auditloghandler.Log(auditlog.EventBindSupportPlan, c, tenantID, auditloghandler.SupportPlan2AuditLogDetails(supportPlan), retErr)
	}()
	if !supportPlan.Public {
		return nil, billing_errors.NewInvalidInputError(errors.New("the support plan is not public"))
	}

	// alicloud subscriber are forbidden to bind support plan by self
	paymentModel := &paymentsub.PaymentModel{}
	paymentMethod, err := paymentModel.GetPaymentMethodWithPaymentTypeCurrentBillingConfig(tenantID, entity.PaymentType_AliCloud)
	if err != nil {
		if !errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil, err
		}
	} else {
		if paymentMethod.Payable {
			return nil, billing_errors.NewForbiddenError(errors.New("customers subscribed through the Alibaba Cloud Marketplace are automatically enrolled in the Enterprise support plan"))
		}
	}

	return supportPlanModel.BindSupportPlanForPayableCustomer(
		tenantID, input.SupportPlanID,
		forms.DateInterval{StartDate: timeutils.GetTimeAddr(timeutils.GetDayDate(timeutils.GetNow()))},
	)
}

// @Summary Cancel Activated Tenant Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/binded_support_plan [delete]
func (*SupportPlanAPI) CancelActivatedTenantSupportPlan(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}

	supportPlanModel := &marketing.SupportPlanModel{}

	info, err := supportPlanModel.GetTenantActiveBindedPlan(tenantID)
	if err != nil {
		return nil, err
	}
	_, err = supportPlanModel.CancelBindedSupportPlan(info.BindedSupportPlanID)
	if err != nil {
		return nil, err
	}

	return forms.OK, nil
}

// @Summary Cancel Tenant Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/binded_support_plan/{binded_support_plan_id} [delete]
func (*SupportPlanAPI) CancelTenantSupportPlan(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	var params struct {
		TenantIDStruct
		BindedSupportPlanID uint `uri:"binded_support_plan_id"`
	}
	err := c.ShouldBindUri(&params)
	if err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	supportPlanModel := &marketing.SupportPlanModel{}
	info, err := supportPlanModel.GetBindedSupportPlan(params.BindedSupportPlanID)
	if err != nil {
		return nil, err
	}
	if info.TenantID != info.TenantID {
		return nil, billing_errors.ObjectNotExistsError
	}
	_, err = supportPlanModel.CancelBindedSupportPlan(info.BindedSupportPlanID)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Cancel Tenant Public Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/binded_support_plan/public [delete]
func (*SupportPlanAPI) CancelTenantPublicSupportPlan(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}

	supportPlanModel := &marketing.SupportPlanModel{}

	info, err := supportPlanModel.GetTenantActiveBindedPlan(tenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			return forms.OK, nil
		}
		return nil, err
	}
	defer func() {
		auditloghandler.Log(auditlog.EventCancelSupportPlan, c, tenantID, auditloghandler.SupportPlan2AuditLogDetails(&info.SupportPlan), retErr)
	}()
	if !info.Public {
		return nil, billing_errors.NewInvalidInputError(errors.New("binded support plan is not public"))
	}

	// alicloud subscriber are forbidden to cancel binded support plan
	paymentModel := &paymentsub.PaymentModel{}
	paymentMethod, err := paymentModel.GetPaymentMethodWithPaymentTypeCurrentBillingConfig(tenantID, entity.PaymentType_AliCloud)
	if err != nil {
		if !errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil, err
		}
	} else {
		if paymentMethod.Payable {
			return nil, billing_errors.NewForbiddenError(errors.New("customers subscribed through the Alibaba Cloud Marketplace are automatically enrolled in the Enterprise support plan"))
		}
	}

	_, err = supportPlanModel.CancelBindedSupportPlan(info.BindedSupportPlanID)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Tenant Bind Private Support Plan
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param PrivateSupportPlanReq body forms.SupportPlanInput true "PrivateSupportPlanReq"
// @Success 200 {object} forms.BindedSupportPlan
// @Router /tenants/{tenant_id}/binded_support_plan/private [post]
func (*SupportPlanAPI) TenantBindPrivateSupportPlan(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	input := &forms.SupportPlanInput{}
	err = c.ShouldBindJSON(input)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	if input.Public {
		return nil, billing_errors.NewInvalidInputError(errors.New("private support plan should not be public"))
	}
	now := timeutils.GetNow()
	input.ActiveDate = timeutils.GetDayDate(input.ActiveDate)
	if input.ActiveDate.Before(timeutils.GetDayDate(now)) {
		return nil, billing_errors.NewInvalidInputError(errors.New("start time passed"))
	}
	if input.ExpiredDate != nil {
		input.ExpiredDate = timeutils.GetTimeAddr(timeutils.GetDayDate(*input.ExpiredDate).AddDate(0, 0, 1).Add(-time.Second))
		if !input.ExpiredDate.After(input.ActiveDate) {
			return nil, billing_errors.NewInvalidInputError(errors.New("start time after end date"))
		}
	}
	model := &billingconfig.BillingConfigModel{}
	isMspCustomer, err := model.IsMspCustomer(tenantID)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	if isMspCustomer {
		return nil, billing_errors.NewInvalidInputError(errors.New("MSP Customer dose not support subscribing support plan"))
	}
	supportPlanModel := &marketing.SupportPlanModel{}
	supportPlan, err := supportPlanModel.CreateSupportPlan(&input.SupportPlan)
	if err != nil {
		return nil, err
	}
	bindedSupportPlan, err := supportPlanModel.BindSupportPlanForPayableCustomer(tenantID, supportPlan.SupportPlanID, forms.DateInterval{
		StartDate: &input.ActiveDate,
		EndDate:   input.ExpiredDate,
	})
	if err != nil {
		return nil, err
	}
	return bindedSupportPlan, nil
}

// @Id Billing_ListPublicSupportPlans
// @Tags Partner, PartnerSupportPlan
// @Summary List public support plans
// @Schemes
// @Accept json
// @Produce json
// @Param accessOrgId query string true "org_id"
// @Success 200 {object} []forms.SupportPlanForOpenAPI
// @Router /publicSupportPlans [get]
func (*SupportPlanAPI) ListAllPublicSupportPlanForOpenAPI(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	supportPlanModel := &marketing.SupportPlanModel{}
	plans, err := supportPlanModel.ListPublicSupportPlan()
	if err != nil {
		return nil, err
	}
	ret := make([]forms.SupportPlanForOpenAPI, len(plans))
	for i, p := range plans {
		ret[i] = forms.SupportPlanForOpenAPI{
			FixedChargePrice:       p.FixedChargePrice,
			SupportPricePercentage: p.SupportPricePercentage,
			Type:                   forms.SupportPlanType(p.Type),
			Public:                 p.Public,
			Level:                  forms.SupportPlanLevel(p.Level),
		}
	}
	return ret, nil
}

// @Id Billing_GetOrgActiveSupportPlanForOpenAPI
// @Tags Partner, PartnerSupportPlan
// @Summary Get organization active support plan
// @Description This endpoint retrieves the active support plan for an organization.
// @Schemes
// @Accept json
// @Produce json
// @Param accessOrgId query string true "org_id"
// @Success 200 {object} forms.OrgSupportPlanForOpenAPI
// @Router /boundSupportPlans [get]
func (*SupportPlanAPI) GetOrgActiveSupportPlanForOpenAPI(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromAuth(c)
	if err != nil {
		return nil, err
	}
	method, err := paymentModel.GetActivePaymentMethod(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil && !errors.Is(err, billing_errors.ObjectNotExistsError) {
		return false, err
	}
	supportPlanModel := &marketing.SupportPlanModel{}
	if err == nil && method.PaymentType == entity.PaymentType_MSP {
		tenant, err := strconv.ParseUint(method.PaymentCode, 10, 0)
		if err != nil {
			return false, billing_errors.NewSystemError(err)
		}
		mspTenantID := uint(tenant)
		info, err := supportPlanModel.GetTenantActiveBindedPlan(mspTenantID)
		if err != nil {
			return nil, err
		}
		res := forms.OrgSupportPlanForOpenAPI{}
		res.Level = forms.SupportPlanLevel(info.Level)
		res.Type = forms.SupportPlanType_FixedPrice
		return res, nil
	}

	info, err := supportPlanModel.GetTenantActiveBindedPlan(billingCfgStruct.TenantID)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			info = marketing.DefaultBindedSupportPlan(billingCfgStruct.TenantID)
		} else {
			return nil, err
		}
	}
	res := forms.OrgSupportPlanForOpenAPI{}
	res.Level = forms.SupportPlanLevel(info.Level)
	res.Type = forms.SupportPlanType(info.Type)
	res.SupportPricePercentage = info.SupportPricePercentage
	res.FixedChargePrice = info.FixedChargePrice
	res.Public = info.Public
	res.ExpiredDate = info.ExpiredDate
	res.ActiveDate = info.ActiveDate
	return res, nil
}

// @Summary List activated support plans for tenant. Additionally, return whether the current tenant is still eligible for a free trial.
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.BindedSupportPlanDetails
// @Router /tenants/{tenant_id}/binded_support_plan/details [get]
func (api *SupportPlanAPI) ListTenantBindedSupportPlans(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	bindedSupportPlans, err := (&marketing.SupportPlanModel{}).ListTenantsBindedSupportPlan(billingCfgStruct.TenantID, entity.BindedSupportPlanStatus_ACTIVE, entity.BindedSupportPlanStatus_Expired, entity.BindedSupportPlanStatus_CANCELED, entity.BindedSupportPlanStatus_TOBEACTIVATED)
	if err != nil {
		return nil, err
	}
	isEligibleForFreeTrial := true
	if len(bindedSupportPlans) > 0 {
		isEligibleForFreeTrial = false
	}
	return forms.BindedSupportPlanDetails{
		BindedSupportPlans:     bindedSupportPlans,
		IsEligibleForFreeTrial: isEligibleForFreeTrial, // for tenant
	}, nil
}

func InitializeSupportPlanRouter(router ginutils.MyRoute) {
	s := &SupportPlanAPI{}
	router.HandleWithError("GET", "/support_plan", s.ListAllPublicSupportPlan)
	router.HandleWithError("PUT", "/support_plan/:support_plan_id", s.UpdatePublicSupportPlan)
	router.HandleWithError("GET", "/tenants/:tenant_id/binded_support_plan", s.GetActiveTenantSupportPlan)
	router.HandleWithError("GET", "/tenants/:tenant_id/binded_support_plan/list", s.ListTenantSupportPlans)
	router.HandleWithError("POST", "/tenants/:tenant_id/binded_support_plan/public", s.TenantBindPublicSupportPlan)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/binded_support_plan/public", s.CancelTenantPublicSupportPlan)
	router.HandleWithError("POST", "/tenants/:tenant_id/binded_support_plan/private", s.TenantBindPrivateSupportPlan)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/binded_support_plan", s.CancelActivatedTenantSupportPlan)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/binded_support_plan/:binded_support_plan_id", s.CancelTenantSupportPlan)
	// for billing config add api
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/binded_support_plan", s.GetActiveTenantSupportPlan)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/binded_support_plan/list", s.ListTenantSupportPlans)

	// for free trial
	router.HandleWithError("GET", "/tenants/:tenant_id/binded_support_plan/details", s.ListTenantBindedSupportPlans)
}

func InitializeSupportPlanRouterForOpenAPI(router ginutils.MyRoute) {
	s := &SupportPlanAPI{}
	router.HandleWithError("GET", "/publicSupportPlans", s.ListAllPublicSupportPlanForOpenAPI)
	router.HandleWithError("GET", "/boundSupportPlans", s.GetOrgActiveSupportPlanForOpenAPI)
}
