package api

import (
	"fmt"

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

// ========== Admin/Inner API Handlers ==========

// @Summary Create Referral Campaign
// @Accept json
// @Produce json
// @Param campaign body forms.CreateCampaignReq true "campaign"
// @Success 200 {object} forms.CampaignResponse
// @Router /referral/campaigns [post]
func (s *API) CreateReferralCampaign(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	req := &forms.CreateCampaignReq{}
	if err := c.ShouldBindJSON(req); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	operatorEmail := c.GetHeader("X-Operator-Email")
	log.Info("api: CreateReferralCampaign",
		zap.String("name", req.Name),
		zap.String("totalBudget", req.TotalBudget.String()),
		zap.String("operator", operatorEmail))
	model := &marketing.ReferralModel{}
	return model.CreateCampaign(req, operatorEmail)
}

// @Summary List Referral Campaigns
// @Accept json
// @Produce json
// @Success 200 {object} forms.CampaignListResponse
// @Router /referral/campaigns [get]
func (s *API) ListReferralCampaigns(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	log.Info("api: ListReferralCampaigns")
	model := &marketing.ReferralModel{}
	return model.ListCampaigns()
}

// @Summary Get Referral Campaign
// @Accept json
// @Produce json
// @Param id path int true "campaign_id"
// @Success 200 {object} forms.CampaignResponse
// @Router /referral/campaigns/{id} [get]
func (s *API) GetReferralCampaign(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		return nil, err
	}
	log.Info("api: GetReferralCampaign", zap.Uint("campaignID", id))
	model := &marketing.ReferralModel{}
	return model.GetCampaign(id)
}

// @Summary Pause Referral Campaign
// @Accept json
// @Produce json
// @Param id path int true "campaign_id"
// @Success 200 {object} forms.OKType
// @Router /referral/campaigns/{id}/actions/pause [post]
func (s *API) PauseReferralCampaign(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		return nil, err
	}
	log.Info("api: PauseReferralCampaign", zap.Uint("campaignID", id))
	model := &marketing.ReferralModel{}
	if err := model.PauseCampaign(id); err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Resume Referral Campaign
// @Accept json
// @Produce json
// @Param id path int true "campaign_id"
// @Success 200 {object} forms.OKType
// @Router /referral/campaigns/{id}/actions/resume [post]
func (s *API) ResumeReferralCampaign(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		return nil, err
	}
	log.Info("api: ResumeReferralCampaign", zap.Uint("campaignID", id))
	model := &marketing.ReferralModel{}
	if err := model.ResumeCampaign(id); err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary End Referral Campaign
// @Accept json
// @Produce json
// @Param id path int true "campaign_id"
// @Success 200 {object} forms.OKType
// @Router /referral/campaigns/{id}/actions/end [post]
func (s *API) EndReferralCampaign(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	id, err := parseUintParam(c, "id")
	if err != nil {
		return nil, err
	}
	log.Info("api: EndReferralCampaign", zap.Uint("campaignID", id))
	model := &marketing.ReferralModel{}
	if err := model.EndCampaign(id); err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Trigger Referral Reward
// @Accept json
// @Produce json
// @Param reward body forms.ReferralRewardReq true "reward request"
// @Success 200 {object} forms.ReferralRewardResp
// @Router /referral/reward [post]
func (s *API) TriggerReferralReward(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	req := &forms.ReferralRewardReq{}
	if err := c.ShouldBindJSON(req); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	if req.CreditsAmount.IsZero() {
		amount, err := getRewardAmountByEventType(req.EventType)
		if err != nil {
			return nil, err
		}
		req.CreditsAmount = amount
	}

	log.Info("api: TriggerReferralReward",
		zap.String("referralCode", req.ReferralCode),
		zap.Uint("refereeTenantID", req.RefereeTenantID),
		zap.String("eventType", req.EventType),
		zap.String("creditsAmount", req.CreditsAmount.String()))
	model := &marketing.ReferralModel{}
	return model.TriggerReward(req)
}

func (s *API) NotifyRegistration(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	req := &forms.NotifyRegistrationReq{}
	if err := c.ShouldBindJSON(req); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	log.Info("api: NotifyRegistration", zap.Uint("tenantID", req.TenantID))
	model := &marketing.ReferralModel{}
	return model.NotifyRegistration(req)
}

func (s *API) GetTenantPromotionStatus(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	log.Info("api: GetTenantPromotionStatus", zap.Uint("tenantID", tenantID))
	model := &marketing.ReferralModel{}
	return model.GetTenantPromotionStatus(tenantID)
}

// getRewardAmountByEventType maps event types to their config keys.
func getRewardAmountByEventType(eventType string) (decimal.Decimal, error) {
	var configKey string
	switch eventType {
	case entity.ReferralEventType_Signup:
		configKey = config.ReferralAmountSignup
	case entity.ReferralEventType_RefereeSignup:
		configKey = config.ReferralAmountRefereeSignup
	case entity.ReferralEventType_BindCard:
		configKey = config.ReferralAmountBindCard
	case entity.ReferralEventType_RefereeBindCard:
		configKey = config.ReferralAmountRefereeBindCard
	case entity.ReferralEventType_FirstUse:
		configKey = config.ReferralAmountFirstUse
	case entity.ReferralEventType_ContinuousUse:
		configKey = config.ReferralAmountContinuousUse
	case entity.ReferralEventType_RefereeContinuousUse:
		configKey = config.ReferralAmountRefereeContinuousUse
	default:
		return decimal.Zero, billing_errors.NewInvalidInputError(fmt.Errorf("unknown event type: %s", eventType))
	}
	return decimal.NewFromFloat(config.Get().GetFloat64(configKey)), nil
}

// ========== User API Handlers ==========

// @Summary Get or Create Referral Code
// @Accept json
// @Produce json
// @Param tenant_id path int true "tenant_id"
// @Success 200 {object} forms.ReferralCodeResponse
// @Router /tenants/{tenant_id}/referral/code [get]
func (s *API) GetOrCreateReferralCode(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	log.Info("api: GetOrCreateReferralCode", zap.Uint("tenantID", tenantID))
	model := &marketing.ReferralModel{}
	return model.GetOrCreateReferralCode(tenantID, true)
}

func (s *API) GetReferralStats(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	log.Info("api: GetReferralStats", zap.Uint("tenantID", tenantID))
	model := &marketing.ReferralModel{}
	return model.GetReferralStats(tenantID, true)
}

func (s *API) GetOrCreatePromotionCode(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	log.Info("api: GetOrCreatePromotionCode", zap.Uint("tenantID", tenantID))
	model := &marketing.ReferralModel{}
	return model.GetOrCreateReferralCode(tenantID, false)
}

func (s *API) GetPromotionStats(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}
	log.Info("api: GetPromotionStats", zap.Uint("tenantID", tenantID))
	model := &marketing.ReferralModel{}
	return model.GetReferralStats(tenantID, false)
}

// ========== Router Registration ==========

func InitializeReferralInnerRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("POST", "/referral/campaigns", s.CreateReferralCampaign)
	router.HandleWithError("GET", "/referral/campaigns", s.ListReferralCampaigns)
	router.HandleWithError("GET", "/referral/campaigns/:id", s.GetReferralCampaign)
	router.HandleWithError("POST", "/referral/campaigns/:id/actions/pause", s.PauseReferralCampaign)
	router.HandleWithError("POST", "/referral/campaigns/:id/actions/resume", s.ResumeReferralCampaign)
	router.HandleWithError("POST", "/referral/campaigns/:id/actions/end", s.EndReferralCampaign)
	router.HandleWithError("POST", "/referral/reward", s.TriggerReferralReward)
	router.HandleWithError("POST", "/registration/source", s.NotifyRegistration)
	router.HandleWithError("GET", "/tenants/:tenant_id/campaigns", s.GetTenantPromotionStatus)
}

func InitializeReferralUserRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("GET", "/tenants/:tenant_id/referral/code", s.GetOrCreateReferralCode)
	router.HandleWithError("GET", "/tenants/:tenant_id/referral/stats", s.GetReferralStats)
	router.HandleWithError("GET", "/tenants/:tenant_id/promotion/code", s.GetOrCreatePromotionCode)
	router.HandleWithError("GET", "/tenants/:tenant_id/promotion/stats", s.GetPromotionStats)
}

// ========== Helpers ==========

func parseUintParam(c *gin.Context, name string) (uint, error) {
	type uriStruct struct {
		ID uint `uri:"id" binding:"required"`
	}
	s := &uriStruct{}
	if err := c.ShouldBindUri(s); err != nil {
		return 0, billing_errors.NewInvalidInputError(err)
	}
	return s.ID, nil
}
