package tenantaction

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/clients/billingcli"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

var (
	ActionBillsRecalculate opent.ActionMetadata = opent.ActionMetadata{
		Name:             "BillsRecalculate",
		Type:             opent.ActionTypeAuto,
		AllowAnyoneApply: true,
	}
)

type billsRecalculate struct {
	actions.BaseAction
	comps *actions.Components
}

type billsRecalculateParam struct {
	tenantID        uint64
	billingConfigID uint64
	invoiceID       string
	req             *billingcli.RecalculateBillsReq
	creditsReq      *billingcli.CreateCreditsWithBillingConfigReq
}

func NewBillsRecalculate(comps *actions.Components) actions.AutoAction {
	return &billsRecalculate{comps: comps}
}

func (a *billsRecalculate) Metadata() opent.ActionMetadata {
	return ActionBillsRecalculate
}

func (a *billsRecalculate) PreCheck(ctx context.Context, instance *opent.Instance) error {
	param, err := a.getParam(instance)
	if err != nil {
		return err
	}

	_, err = a.comps.TenantAPI.GetTenant(ctx, param.tenantID)
	if err != nil {
		return err
	}

	return nil
}

func (a *billsRecalculate) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	param, err := a.getParam(instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	if param.creditsReq != nil {
		err = a.comps.BillingCli.CreateCreditsWithBillingConfig(ctx, param.tenantID, param.billingConfigID, param.creditsReq, instance.Applicant)
		if err != nil {
			return actions.FailedActionResult(err), nil
		}
		return actions.SucceededActionResult(nil), nil
	}

	err = a.comps.BillingCli.RecalculateBills(ctx, param.tenantID, param.billingConfigID, param.req, instance.Applicant)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	return actions.SucceededActionResult(nil), nil
}

func (a *billsRecalculate) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	return actions.SucceededActionResult(nil), nil
}

func (a *billsRecalculate) getParam(instance *opent.Instance) (*billsRecalculateParam, error) {
	tid, err := actions.GetTenantID(instance)
	if err != nil {
		return nil, err
	}

	billingConfigIDStr, _ := instance.GetParam(opent.ParamBillingConfigID)
	if billingConfigIDStr == "" {
		return nil, fmt.Errorf("billing_config_id is required")
	}
	billingConfigID, err := strconv.ParseUint(billingConfigIDStr, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("billing_config_id is not a int")
	}
	if billingConfigID == 0 {
		return nil, fmt.Errorf("billing_config_id is empty")
	}

	invoiceID, _ := instance.GetParam(opent.ParamInvoiceID)
	invoiceID = strings.TrimSpace(invoiceID)
	sourceRunIDStr, _ := instance.GetParam(opent.ParamSourceRunID)
	overwriteCurrentCost, _ := instance.GetParam(opent.ParamOverwriteCurrentCost)
	sourceRunIDStr = strings.TrimSpace(sourceRunIDStr)
	overwriteCurrentCost = strings.TrimSpace(overwriteCurrentCost)
	overwriteCurrentCostBool := false
	if invoiceID == "" && overwriteCurrentCost != "" {
		overwriteCurrentCostBool, err = strconv.ParseBool(overwriteCurrentCost)
		if err != nil {
			return nil, fmt.Errorf("overwrite_current_cost is not a bool")
		}
	}
	if invoiceID == "" && overwriteCurrentCostBool {
		if sourceRunIDStr == "" {
			return nil, fmt.Errorf("source_run_id is required")
		}
		return &billsRecalculateParam{
			tenantID:        tid,
			billingConfigID: billingConfigID,
			req: &billingcli.RecalculateBillsReq{
				SourceRunID:          sourceRunIDStr,
				OverwriteCurrentCost: true,
			},
		}, nil
	}

	creditsReq, err := a.getCreditsReq(instance)
	if err != nil {
		return nil, err
	}
	if invoiceID != "" {
		if creditsReq == nil {
			return nil, fmt.Errorf("credits_total_credits is required")
		}
		return &billsRecalculateParam{
			tenantID:        tid,
			billingConfigID: billingConfigID,
			invoiceID:       invoiceID,
			creditsReq:      creditsReq,
		}, nil
	}
	if creditsReq != nil {
		return &billsRecalculateParam{
			tenantID:        tid,
			billingConfigID: billingConfigID,
			creditsReq:      creditsReq,
		}, nil
	}

	if sourceRunIDStr == "" {
		return nil, fmt.Errorf("source_run_id is required")
	}

	return &billsRecalculateParam{
		tenantID:        tid,
		billingConfigID: billingConfigID,
		invoiceID:       invoiceID,
		req: &billingcli.RecalculateBillsReq{
			SourceRunID:          sourceRunIDStr,
			OverwriteCurrentCost: overwriteCurrentCostBool,
		},
	}, nil
}

func (a *billsRecalculate) getCreditsReq(instance *opent.Instance) (*billingcli.CreateCreditsWithBillingConfigReq, error) {
	totalCredits, _ := instance.GetParam(opent.ParamCreditsTotalCredits)
	totalCredits = strings.TrimSpace(totalCredits)
	if totalCredits == "" {
		return nil, nil
	}

	discountEnabled, _ := instance.GetParam(opent.ParamCreditsDiscountEnabled)
	expiredDate, _ := instance.GetParam(opent.ParamCreditsExpiredDate)
	provider, _ := instance.GetParam(opent.ParamCreditsProvider)
	startDate, _ := instance.GetParam(opent.ParamCreditsStartDate)
	unitFiltersStr, _ := instance.GetParam(opent.ParamCreditsUnitFilters)
	usage, _ := instance.GetParam(opent.ParamUsage)
	discountEnabled = strings.TrimSpace(discountEnabled)
	expiredDate = strings.TrimSpace(expiredDate)
	provider = strings.TrimSpace(provider)
	startDate = strings.TrimSpace(startDate)
	unitFiltersStr = strings.TrimSpace(unitFiltersStr)
	usage = strings.TrimSpace(usage)
	if expiredDate == "" || provider == "" || startDate == "" || unitFiltersStr == "" {
		return nil, fmt.Errorf("credits params are required")
	}
	if usage == "" {
		usage = "redeem"
	}

	discountEnabledBool, err := strconv.ParseBool(discountEnabled)
	if err != nil {
		return nil, fmt.Errorf("credits_discount_enabled is not a bool")
	}
	var unitFilters []*billingcli.UnitFilterSlim
	if err := json.Unmarshal([]byte(unitFiltersStr), &unitFilters); err != nil {
		return nil, err
	}
	if len(unitFilters) == 0 {
		return nil, fmt.Errorf("credits_unit_filters is required")
	}

	return &billingcli.CreateCreditsWithBillingConfigReq{
		BindTypes:       unitFilters,
		DiscountEnabled: discountEnabledBool,
		ExpiredDate:     expiredDate,
		Note:            instance.Reason,
		Provider:        provider,
		StartDate:       startDate,
		TotalCredits:    totalCredits,
		Usage:           usage,
	}, nil
}
