package paymentsub

import (
	"errors"
	"slices"
	"strings"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/shopspring/decimal"
	"github.com/stripe/stripe-go/v76"
	"go.uber.org/zap"
)

type TaxModel struct {
}

func GetTaxDisplayName(country string, state string, taxType string) (string, error) {
	switch taxType {
	case string(stripe.TaxRateTaxTypeJCT):
		return "Consumption Tax", nil
	case string(stripe.TaxRateTaxTypeVAT):
		return "VAT", nil
	case string(stripe.TaxRateTaxTypeGST):
		return "GST", nil
	case string(stripe.TaxRateTaxTypeSalesTax):
		return "Sales Tax", nil
	case string(stripe.TaxRateTaxTypeHST):
		return "HST", nil
	case string(stripe.TaxRateTaxTypeServiceTax):
		return "Service Tax", nil
	case string(stripe.TaxRateTaxTypeIGST):
		return "IGST", nil
	case string(stripe.TaxRateTaxTypeLeaseTax):
		return "Lease Tax", nil
	case string(stripe.TaxRateTaxTypePST):
		return "PST", nil
	case string(stripe.TaxRateTaxTypeQST):
		return "QST", nil
	case string(stripe.TaxRateTaxTypeRST):
		return "RST", nil
	default:
		log.Error("unknown tax type detected", zap.String("taxType", taxType))
		return taxType, nil
	}
}

func StripeCalculateTaxResultToTaxRate(tenantID uint, in *forms.StripeCalculateTaxResult) (*forms.TaxRate, error) {
	taxDisplayName, err := GetTaxDisplayName(in.Country, in.State, in.TaxType)
	if err != nil {
		return nil, err
	}
	return &forms.TaxRate{
		TenantID:         tenantID,
		Value:            decimal.RequireFromString(in.PercentageDecimal),
		TaxCode:          config.Get().GetString(config.TiDBCloudStripeProductTaxCode),
		DisplayName:      taxDisplayName,
		TaxAbilityReason: in.TaxAbilityReason,
		Inclusive:        in.Inclusive,
		Country:          in.Country,
		State:            in.State,
		TaxType:          in.TaxType,
	}, nil
}

func (t *TaxModel) InactiveTaxRate(stripeTaxRateID string) error {
	if !config.Get().GetBool(config.EnableTaxCalculation) {
		return billing_errors.NewSystemError(errors.New("tax calculation not enable"))
	}
	handler, err := paymenthandler.GetStripeProvider()
	if err != nil {
		return err
	}
	return handler.InactiveTaxRate(stripeTaxRateID)
}

func (t *TaxModel) CreateTenantTaxRate(tenantID uint, rate *forms.TaxRate) (*forms.TaxRate, error) {
	if !config.Get().GetBool(config.EnableTaxCalculation) {
		return nil, billing_errors.NewSystemError(errors.New("tax calculation not enable"))
	}
	handler, err := paymenthandler.GetStripeProvider()
	if err != nil {
		return nil, err
	}
	rate.TenantID = tenantID
	rate, err = handler.CreateTaxRate(rate)
	if err != nil {
		return nil, err
	}
	return rate, nil
}

func (t *TaxModel) ValidateBillingInfoForTax(billingInfo *forms.BillingInformation, subCode string) (*forms.TaxRate, error) {
	if !config.Get().GetBool(config.EnableTaxCalculation) {
		return nil, nil
	}
	sh, err := paymenthandler.GetStripeProvider()
	if err != nil {
		return nil, err
	}
	if billingInfo.CountryCode == constant.OtherCountryCode {
		for _, b := range config.Get().GetStringSlice(config.TradingProhibitedCountryNameList) {
			if strings.EqualFold(b, billingInfo.CustomInputCountryName) {
				return nil, billing_errors.NewSystemBusyError(errors.New("please try later again"))
			}
		}
		if slices.Contains([]string{"TAIWAN", "TAIWAN, CHINA", "CHINA TAIWAN"}, strings.ToUpper(billingInfo.CustomInputCountryName)) {
			billingInfo.CustomInputCountryName = "Taiwan (Province of China)"
		}
		var ok bool
		billingInfo.CountryCode, ok = config.Get().GetStringMapString(config.CountryCodeMapping)[billingInfo.CustomInputCountryName]
		if !ok {
			return nil, billing_errors.NewSystemBusyError(errors.New("please try later again"))
		}
	}
	taxResult, err := sh.CalculateTax(billingInfo, subCode, decimal.NewFromInt(100))
	if err != nil {
		return nil, err
	}
	return StripeCalculateTaxResultToTaxRate(billingInfo.TenantID, taxResult)
}

func (t *TaxModel) MatchTaxRate(a, b *forms.TaxRate) bool {
	if a == nil || b == nil {
		return false
	}
	return a.TenantID == b.TenantID && a.Value.Equal(b.Value) && a.TaxType == b.TaxType && a.TaxAbilityReason == b.TaxAbilityReason && a.TaxCode == b.TaxCode && a.Inclusive == b.Inclusive && a.Country == b.Country && a.State == b.State
}

func (t *TaxModel) RetrieveAndUpdateTenantTaxRate(tenantID uint) (*forms.TaxRate, error) {
	if !config.Get().GetBool(config.EnableTaxCalculation) {
		return nil, nil
	}
	handler, err := paymenthandler.GetStripeProvider()
	if err != nil {
		return nil, err
	}
	billingInfo, err := (&BillingInformationModel{}).GetBillingInfo(tenantID)
	if err != nil {
		return nil, err
	}
	billingInfoModel := &BillingInformationModel{}
	tenantCompany, err := billingInfoModel.GetTenantBillingCompany(tenantID)
	if err != nil {
		return nil, err
	}
	billingCompany, err := billingInfoModel.GetBillingCompany(tenantCompany.CompanyKey)
	if err != nil {
		return nil, err
	}
	var taxResult *forms.StripeCalculateTaxResult
	if billingCompany.UsingDesignatedTaxAddressToCalculateTaxRate {
		taxResult, err = handler.CalculateTax(&forms.BillingInformation{BillingAddress: *billingCompany.DesignatedTaxAddress}, billingCompany.PaymentSubCode, decimal.NewFromInt(100))
		if err != nil {
			return nil, err
		}
	} else {
		if billingInfo.CountryCode == constant.OtherCountryCode {
			for _, b := range config.Get().GetStringSlice(config.TradingProhibitedCountryNameList) {
				if strings.EqualFold(b, billingInfo.CustomInputCountryName) {
					return nil, billing_errors.NewSystemBusyError(errors.New("please try later again"))
				}
			}
			if slices.Contains([]string{"TAIWAN", "TAIWAN, CHINA", "CHINA TAIWAN"}, strings.ToUpper(billingInfo.CustomInputCountryName)) {
				billingInfo.CustomInputCountryName = "Taiwan (Province of China)"
			}
			var ok bool
			billingInfo.CountryCode, ok = config.Get().GetStringMapString(config.CountryCodeMapping)[billingInfo.CustomInputCountryName]
			if !ok {
				return nil, billing_errors.NewSystemBusyError(errors.New("please try later again"))
			}
		}
		taxResult, err = handler.CalculateTax(billingInfo, billingCompany.PaymentSubCode, decimal.NewFromInt(100))
		if err != nil {
			return nil, err
		}
	}
	rate, err := StripeCalculateTaxResultToTaxRate(tenantID, taxResult)
	if err != nil {
		return nil, err
	}
	if !t.MatchTaxRate(billingInfo.TaxRate, rate) {
		rate, err = handler.CreateTaxRate(rate)
		if err != nil {
			return nil, err
		}
		err = (&BillingInformationModel{}).UpdateTaxRate(tenantID, rate.StripeTaxRateID)
		if err != nil {
			return nil, err
		}
		if billingInfo.TaxRate != nil {
			err = t.InactiveTaxRate(billingInfo.TaxRate.StripeTaxRateID)
			if err != nil {
				log.Error("failed to inactive old tax rate")
				// do not return because of no effect to the main process
			}
		}
	} else {
		rate = billingInfo.TaxRate
	}
	return rate, nil
}

func (t *TaxModel) GetTaxRate(taxRateID string) (*forms.TaxRate, error) {
	if !config.Get().GetBool(config.EnableTaxCalculation) {
		return nil, nil
	}
	sh, err := paymenthandler.GetStripeProvider()
	if err != nil {
		return nil, err
	}
	return sh.GetTaxRate(taxRateID)
}
