package api

import (
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/marketing/discount"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/user"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
)

var discountModel = discount.Model{}

type TenantDiscountAPI struct{}

type DiscountQueryStruct struct {
	TenantID   uint `uri:"tenant_id"`
	DiscountID uint `uri:"discount_id" binding:"required"`
}

func getDiscountQueryFromURI(c *gin.Context) (*DiscountQueryStruct, error) {
	tenantIDStruct := DiscountQueryStruct{}
	err := c.ShouldBindUri(&tenantIDStruct)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	return &tenantIDStruct, nil
}

// @Summary Update Global Discount (deprecated)
// @Tags DiscountInfo
// @Description Update global discount
// @Accept  json
// @Produce json
// @Param discount body forms.DiscountInfo true "discount"
// @Success 200 {object} forms.DiscountInfo
// @Router /plans/ondemand/global-discounts [put]
func (s *TenantDiscountAPI) UpdateGlobalDiscountOld(c *gin.Context) {
	c.JSON(http.StatusBadRequest, map[string]string{
		"error": "unable to update global discount",
	})
}

// @Summary Get Global Discount (deprecated)
// @Tags DiscountInfo
// @Description Get Global DiscountInfo
// @Accept  json
// @Produce json
// @Success 200 {object} forms.DiscountInfo
// @Router /plans/ondemand/global-discounts [get]
func (s *TenantDiscountAPI) GetGlobalDiscount(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	return forms.Discount{Value: decimal.NewFromInt(1)}, nil
}

// @Summary Update Tenant DiscountInfo
// @Tags DiscountInfo
// @Description update discount
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param discount body forms.DiscountInfo true "discount"
// @Success 200 {object} forms.DiscountInfo
// @Router /tenants/{tenant_id}/plans/ondemand/discounts [put]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/plans/ondemand/discounts [put]
func (s *TenantDiscountAPI) SetTenantDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	req := forms.Discount{}
	if err := c.ShouldBindJSON(&req); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	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 discount"))
	}
	return (&discount.Model{}).SetDiscount(&forms.ChangeBindedDiscountInput{
		BindedDiscount:        req.Value,
		ChangerTenantID:       billingCfgStruct.TenantID,
		ChangerUserID:         user.GetCurrentUserID(),
		BindedBillingConfigID: &billingCfgStruct.BillingConfigID,
	})
}

// @Summary Get Tenant DiscountInfo
// @Tags DiscountInfo
// @Description Get discount
// @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.DiscountInfo
// @Router /tenants/{tenant_id}/plans/ondemand/discounts [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/plans/ondemand/discounts [get]
func (s *TenantDiscountAPI) GetTenantDiscount(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	return (&discount.Model{}).GetTenantOnDemandDiscount(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
}

// @Summary Get Tenant Effective DiscountInfo
// @Tags DiscountInfo
// @Description Get discount
// @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.DiscountInfo
// @Router /tenants/{tenant_id}/plans/ondemand/effective-discounts [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/plans/ondemand/effective-discounts [get]
func (s *TenantDiscountAPI) GetTenantEffectiveDiscount(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	return s.GetTenantDiscount(c)
}

// @Summary Create Global Discount
// @Tags DiscountInfo
// @Description Create global discount
// @Accept  json
// @Produce json
// @Param discount body forms.Discount true "discount"
// @Success 200 {object} forms.Discount
// @Router /global-discounts [post]
func (s *TenantDiscountAPI) CreateGlobalDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	return nil, billing_errors.NewAPIOfflineError(fmt.Errorf("API-CreateGlobalDiscount is offline. 20231218"))

	//d := forms.Discount{}
	//if err := c.ShouldBindJSON(&d); err != nil {
	//	return nil, billing_errors.NewInvalidInputError(err)
	//}
	//return discountModel.CreateDiscount(0, &d)
}

// @Summary List Global Discount
// @Tags DiscountInfo
// @Description List global discount
// @Produce json
// @Success 200 {object} []forms.Discount
// @Router /global-discounts [get]
func (s *TenantDiscountAPI) ListGlobalDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	return discountModel.ListGlobalDiscounts()
}

// @Summary Update Global Discount
// @Tags DiscountInfo
// @Description Update global discount
// @Accept  json
// @Produce json
// @Param discount_id path string true "discount_id"
// @Param discount body forms.Discount true "discount"
// @Success 200 {object} forms.Discount
// @Router /global-discounts/{discount_id} [put]
func (s *TenantDiscountAPI) UpdateGlobalDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	return nil, billing_errors.NewAPIOfflineError(fmt.Errorf("API-UpdateGlobalDiscount is offline. 20231218"))

	//q, err := getDiscountQueryFromURI(c)
	//if err != nil {
	//	return nil, err
	//}
	//d := forms.Discount{}
	//if err := c.ShouldBindJSON(&d); err != nil {
	//	return nil, billing_errors.NewInvalidInputError(err)
	//}
	//return discountModel.UpdateDiscount(0, q.DiscountID, d.Value)
}

// @Summary Reclaim Global Discount
// @Tags DiscountInfo
// @Description Reclaim global discount
// @Accept  json
// @Produce json
// @Param discount_id path string true "discount_id"
// @Success 200 {object} forms.OKType
// @Router /global-discounts/{discount_id} [delete]
func (s *TenantDiscountAPI) DeleteGlobalDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	q, err := getDiscountQueryFromURI(c)
	if err != nil {
		return nil, err
	}
	err = discountModel.DeleteDiscount(0, q.DiscountID)
	if err == nil {
		ret = forms.OK
	}
	return
}

// @Summary Create Tenant Discount
// @Tags DiscountInfo
// @Description Create tenant discount
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param discount body forms.Discount true "discount"
// @Success 200 {object} forms.Discount
// @Router /tenants/{tenant_id}/discounts [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/discounts [post]
func (s *TenantDiscountAPI) CreateTenantDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	d := forms.Discount{}
	if err := c.ShouldBindJSON(&d); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	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 discount."))
	}
	d.BindedBillingConfigID = &billingCfgStruct.BillingConfigID
	ufModel := &unit_filter.Model{}
	copyArgs, err := ufModel.GetCopyArgs(&d.CopyUnitFilterArgs)
	if err != nil {
		return nil, err
	}
	if copyArgs != nil {
		log.Info("need create copied unit filters", zap.Any("copyArgs", copyArgs))
		d.UnitFilters, err = ufModel.CreateCopiedUnitFilters(d.UnitFilters, copyArgs)
		if err != nil {
			return nil, err
		}
	}
	return discountModel.CreateDiscount(billingCfgStruct.TenantID, &d)
}

// @Summary List Tenant Discount
// @Tags DiscountInfo
// @Description List tenant discount
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} []forms.Discount
// @Router /tenants/{tenant_id}/discounts [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/discounts [get]
func (s *TenantDiscountAPI) ListTenantDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	return discountModel.ListTenantDiscounts(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
}

// @Summary Update Tenant Discount
// @Tags DiscountInfo
// @Description Update tenant discount
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param discount_id path string true "discount_id"
// @Param discount body forms.Discount true "discount"
// @Success 200 {object} forms.Discount
// @Router /tenants/{tenant_id}/discounts/{discount_id} [put]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/discounts/{discount_id} [put]
func (s *TenantDiscountAPI) UpdateTenantDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	q, err := getDiscountQueryFromURI(c)
	if err != nil {
		return nil, err
	}
	d := forms.Discount{}
	if err := c.ShouldBindJSON(&d); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	return discountModel.UpdateDiscount(tenantID, q.DiscountID, d.Value)
}

// @Summary Reclaim Tenant Discount
// @Tags DiscountInfo
// @Description Reclaim tenant discount
// @Accept  json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param discount_id path string true "discount_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/discounts/{discount_id} [delete]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/discounts/{discount_id} [delete]
func (s *TenantDiscountAPI) DeleteTenantDiscount(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	q, err := getDiscountQueryFromURI(c)
	if err != nil {
		return nil, err
	}
	if q.TenantID == 0 {
		return nil, billing_errors.NewInvalidInputError(errors.New("invalid tenantID"))
	}
	err = discountModel.DeleteDiscount(q.TenantID, q.DiscountID)
	if err == nil {
		ret = forms.OK
	}
	return
}

// @Summary List Discount (User portal)
// @Tags DiscountInfo
// @Description List Discount (User portal)
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Success 200 {object} []forms.Discount
// @Router /tenants/{tenant_id}/users/discounts [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/users/discounts [get]
func (s *TenantDiscountAPI) ListTenantDiscountConsoleView(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}

	res, err := discountModel.ListDiscountsConsoleView(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}

	// do not shown discount console view
	for _, discount := range res {
		discount.Note = ""
	}
	return res, nil
}

func (s *TenantDiscountAPI) discountStatusToState(status string) forms.DiscountState {
	switch status {
	case constant.DiscountStatus_Expired:
		return forms.DiscountState_Expired
	case constant.DiscountStatus_Valid:
		return forms.DiscountState_Valid
	case constant.DiscountStatus_Reclaimed:
		return forms.DiscountState_Reclaimed
	}
	log.Error("unknown discount status", zap.String("status", status))
	return forms.DiscountState_Invalid
}

// @Id Billing_ListDiscounts
// @Summary List discounts for an organization
// @Tags Partner, PartnerBilling
// @Description This endpoint retrieves a list of discounts available for an organization.
// @Produce json
// @Param accessOrgId query string true "org_id"
// @Success 200 {object} []forms.DiscountForOpenAPI
// @Router /discounts [get]
func (s *TenantDiscountAPI) ListPartnerDiscounts(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	billingCfgStruct, err := getBillingCfgFromAuth(c)
	if err != nil {
		return nil, err
	}
	var discounts []*forms.Discount
	switch billingCfgStruct.Type {
	case entity.BillingConfigType_MSP, entity.BillingConfigType_ResalePartner:
		discounts, err = (&discount.Model{}).ListDiscountsConsoleView(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
		if err != nil {
			return nil, err
		}
	case entity.BillingConfigType_MarketplaceReseller, entity.BillingConfigType_DirectCustomer:
		log.Info("need to retrieve discounts 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 nil, nil
		}
		for _, bcfg := range billingConfigs {
			log.Info("start to retrieve discount from managing billing configs", zap.Uint("tenantID", billingCfgStruct.TenantID), zap.Uint("managedTenantID", bcfg.ManagedTenantID), zap.Uint("managingBillingConfigID", bcfg.BillingConfigID))
			customerDiscounts, err := (&discount.Model{}).ListDiscountsConsoleView(billingCfgStruct.TenantID, bcfg.BillingConfigID)
			if err != nil {
				return nil, err
			}
			for i := 0; i != len(customerDiscounts); i++ {
				customerDiscounts[i].Customers = []string{fmt.Sprint(bcfg.ManagedTenantID)}
			}
			discounts = append(discounts, customerDiscounts...)
		}
	}
	res := make([]forms.DiscountForOpenAPI, len(discounts))
	for i, d := range discounts {
		var names []string
		for _, filter := range d.UnitFilters {
			names = append(names, filter.Name)
		}
		res[i] = forms.DiscountForOpenAPI{
			DiscountCode:       d.Code,
			State:              s.discountStatusToState(d.Status),
			Services:           strings.Join(names, ", "),
			PercentageDiscount: decimal.NewFromFloat(1.00).Sub(d.Value).Mul(decimal.NewFromInt(100)).String() + "%",
			Description:        d.Description,
			StartDate:          d.StartedAt,
			ExpiryDate:         d.ExpiredAt,
			Provider:           *d.Provider,
			Customers:          d.Customers,
		}
	}

	return res, nil
}

func InitializeTenantDiscountRouter(router ginutils.MyRoute) {
	s := &TenantDiscountAPI{}

	router.Handle("PUT", "/plans/ondemand/global-discounts", s.UpdateGlobalDiscountOld)
	router.HandleWithError("GET", "/plans/ondemand/global-discounts", s.GetGlobalDiscount)
	router.HandleWithError("GET", "/tenants/:tenant_id/plans/ondemand/discounts", s.GetTenantDiscount)
	router.HandleWithError("GET", "/tenants/:tenant_id/plans/ondemand/effective-discounts", s.GetTenantEffectiveDiscount)
	router.HandleWithError("PUT", "/tenants/:tenant_id/plans/ondemand/discounts", s.SetTenantDiscount)

	router.HandleWithError("GET", "/global-discounts", s.ListGlobalDiscount)
	router.HandleWithError("POST", "/global-discounts", s.CreateGlobalDiscount)
	router.HandleWithError("PUT", "/global-discounts/:discount_id", s.UpdateGlobalDiscount)
	router.HandleWithError("DELETE", "/global-discounts/:discount_id", s.DeleteGlobalDiscount)

	router.HandleWithError("GET", "/tenants/:tenant_id/discounts", s.ListTenantDiscount)
	router.HandleWithError("POST", "/tenants/:tenant_id/discounts", s.CreateTenantDiscount)
	router.HandleWithError("PUT", "/tenants/:tenant_id/discounts/:discount_id", s.UpdateTenantDiscount)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/discounts/:discount_id", s.DeleteTenantDiscount)

	router.HandleWithError("GET", "/tenants/:tenant_id/users/discounts", s.ListTenantDiscountConsoleView)

	// for billing config add api
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/plans/ondemand/discounts", s.GetTenantDiscount)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/plans/ondemand/effective-discounts", s.GetTenantEffectiveDiscount)
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/plans/ondemand/discounts", s.SetTenantDiscount)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/discounts", s.ListTenantDiscount)
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/discounts", s.CreateTenantDiscount)
	// param use PRIMARY key, no modify
	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/discounts/:discount_id", s.UpdateTenantDiscount)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/billing_config_id/:billing_config_id/discounts/:discount_id", s.DeleteTenantDiscount)

	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/users/discounts", s.ListTenantDiscountConsoleView)

}

func InitializeDiscountsRouterForPartnerOpenAPI(router ginutils.MyRoute) {
	s := &TenantDiscountAPI{}

	router.HandleWithError("GET", "/discounts", s.ListPartnerDiscounts)

}
