package impl

import (
	"errors"
	"fmt"
	"slices"
	"time"

	"github.com/pingcap/dbaas/billing2/handler/cloudmarketplacehandler"

	pingcaperrors "github.com/pingcap/errors"
	"github.com/shopspring/decimal"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"go.uber.org/zap"
)

type AzurePaymentHandlerImpl struct {
}
type AzureInvoice struct {
	TenantID   uint
	ResourceId string
	Dimension  string
	Invoices   []forms.Invoice
}
type AzureMeasureRequest struct {
	Request []AzureMeasure `json:"request"`
}
type AzureMeasure struct {
	ResourceId         string `json:"resourceId"`
	Quantity           int64  `json:"quantity"`
	Dimension          string `json:"dimension"`
	EffectiveStartTime string `json:"effectiveStartTime"`
	PlanId             string `json:"planId"`
}
type UsageEvent struct {
	UsageEventID       string      `json:"usageEventId"`
	Status             string      `json:"status"`
	MessageTime        time.Time   `json:"messageTime"`
	ResourceID         string      `json:"resourceId"`
	Quantity           float64     `json:"quantity"`
	Dimension          string      `json:"dimension"`
	EffectiveStartTime time.Time   `json:"effectiveStartTime"`
	PlanID             string      `json:"planId"`
	Error              *ErrorAzure `json:"error,omitempty"`
}
type ErrorAzure struct {
	Message        string          `json:"message"`
	Code           string          `json:"code"`
	AdditionalInfo *AdditionalInfo `json:"additionalInfo"`
}
type AdditionalInfo struct {
	AcceptedMessage UsageEvent `json:"acceptedMessage"`
}
type AzureMeasureResponse struct {
	Count  int          `json:"count"`
	Result []UsageEvent `json:"result"`
}

func (azure *AzurePaymentHandlerImpl) Setup(tenant *forms.Tenant) (string, error) {
	if !config.Get().GetBool(config.AZUREPaymentEnable) {
		return fmt.Sprintf("mock_azure_%v", tenant.TenantID), nil
	}
	if tenant.ThirdPartyAccountProvider != constant.AzureAccountProvider && !tenant.IsCPPOReseller {
		return "", billing_errors.NewSystemError(errors.New("invalid third_party_account_handler"))
	}

	return tenant.ThirdPartyAccountCode, nil
}

func (azure *AzurePaymentHandlerImpl) Payable(tenantID uint, userCode string) (bool, error) {
	handler, err := cloudmarketplacehandler.GetMarketplaceHandler().GetMarketplace(entity.PaymentType_AZURE)
	if err != nil {
		log.Error("get azure metering handler failed", zap.Error(err))
		return false, err
	}
	subscription, err := handler.GetSubscription(userCode)
	return subscription.SaasSubscriptionStatus == constant.AzureSubscriptionStatusSubscribed, err
}

func (azure *AzurePaymentHandlerImpl) NeedMultiInvoiceSendOnce() bool {
	return true
}

func (azure *AzurePaymentHandlerImpl) Pay(paymentInfo *forms.PaymentMethod, billingCompany *forms.BillingCompany, invoice *forms.Invoice) (*forms.Invoice, error) {
	return nil, billing_errors.NewNotImplementedError(errors.New("can not send azure marketplace once per invoice"))
}
func (azure *AzurePaymentHandlerImpl) PayMultiInvoice(paymentInfo *forms.PaymentMethod, billingCompany *forms.BillingCompany, invoices []forms.Invoice) ([]forms.Invoice, error) {

	if !config.Get().GetBool(config.AZUREPaymentEnable) {
		return invoices, nil
	}

	lastInvoiceSentDate, err := (&payment.InvoiceModel{}).GetLastSendInvoiceDateByPaymentCode(entity.PaymentType_AZURE, paymentInfo.PaymentCode)
	if err != nil {
		return nil, err
	}
	if lastInvoiceSentDate.Add(time.Hour).After(timeutils.GetNow()) {
		log.Info("azure can not send invoice to azure twice in less than one hour ", zap.Uint("tenantId", invoices[0].TenantID), zap.String("paymentCode", paymentInfo.PaymentCode))
		return nil, nil
	}
	handler, err := cloudmarketplacehandler.GetMarketplaceHandler().GetMarketplace(entity.PaymentType_AZURE)
	if err != nil {
		log.Error("get azure metering handler failed", zap.Error(err))
		return nil, err
	}
	subscription, err := handler.GetSubscription(paymentInfo.PaymentCode)
	if err != nil {
		log.Error("GetMarketplaceHandler().GetSubscription ", zap.Error(err))
		return nil, err
	}

	dimension := config.Get().GetString(config.AZUREProductDimension)
	productPriceFactor := config.Get().GetInt64(config.AZUREProductUnitPriceFactor)
	planID := subscription.PlanId
	azurePaymentCode := paymentInfo.PaymentCode

	client := handler.GetAPIClient()

	req := AzureMeasureRequest{}
	resp := AzureMeasureResponse{}
	start := timeutils.GetHourDate(timeutils.GetNow())
	if subscription.SaasSubscriptionStatus != constant.AzureSubscriptionStatusSubscribed {
		start = start.Add(-1 * time.Hour)
	}
	quantity := int64(0)
	var invoiceIDs string

	for _, invoice := range invoices {
		invoiceIDs = fmt.Sprintf("%s-%d", invoiceIDs, invoice.InvoiceID)
		quantity += invoice.CostPrice.Mul(decimal.NewFromInt(productPriceFactor)).IntPart()
	}
	req.Request = append(req.Request, AzureMeasure{
		ResourceId:         azurePaymentCode,
		Quantity:           quantity,
		Dimension:          dimension,
		EffectiveStartTime: start.Format(time.RFC3339),
		PlanId:             planID,
	})
	err = client.Request("POST", "api/batchUsageEvent?api-version=2018-08-31", nil, req, &resp)

	if err != nil {
		err = pingcaperrors.Trace(err)
		log.Error("failed call azure metering", zap.Any("input", req), zap.Error(err))
		return nil, err
	}

	for _, result := range resp.Result {
		if result.Status != "Accepted" {
			log.Error("azure marketplace metering usage records status not success", zap.Uint("tenantId", paymentInfo.TenantID), zap.String("status", result.Status), zap.Any("input", req), zap.String("invoiceIDs", invoiceIDs))
			return nil, billing_errors.NewSystemError(errors.New("azure response error"))
		}
	}
	log.Info("success call azure metering", zap.Any("input", req), zap.Any("output", resp))
	return invoices, nil
}

func (azure *AzurePaymentHandlerImpl) GetThirdPartyName() string {
	return entity.PaymentType_AZURE
}

func (azure *AzurePaymentHandlerImpl) EnableReconciliation() bool {
	return true
}

func (azure *AzurePaymentHandlerImpl) ParseAzureReport(body []byte) ([]*paymenthandler.TenantRevenue, error) {
	//todo
	return nil, nil
}

func (azure *AzurePaymentHandlerImpl) getAzureReport(key string) ([]byte, error) {
	//todo

	return nil, nil
}

func (azure *AzurePaymentHandlerImpl) GetDailyRevenue(date time.Time) ([]*paymenthandler.TenantRevenue, error) {
	//todo

	return nil, nil
}

func (azure *AzurePaymentHandlerImpl) GetMonthlyRevenue(date time.Time) ([]*paymenthandler.TenantRevenue, error) {
	if !config.Get().GetBool(config.AZUREPaymentEnable) {
		return nil, nil
	}

	return nil, nil
}

func (azure *AzurePaymentHandlerImpl) EnableTaxCalculation() bool {
	return false
}

func (azure *AzurePaymentHandlerImpl) EnableHourlySending(tenantID, billingConfigID uint) bool {
	return slices.Contains(config.Get().GetStringSlice(config.HourlyInvoiceTenantBillingConfigIDList), fmt.Sprintf("%d-%d", tenantID, billingConfigID))
}

func (azure *AzurePaymentHandlerImpl) EnableDailySending(tenantID, billingConfigID uint) (bool, int) {
	return slices.Contains(config.Get().GetStringSlice(config.DailyInvoiceTenantBillingConfigIDList),
		fmt.Sprintf("%d-%d", tenantID, billingConfigID)), config.Get().GetInt(config.DailyInvoiceIssueDelayDays)
}

func (azure *AzurePaymentHandlerImpl) NeedInMonthInvoice() bool {
	return true
}

func (azure *AzurePaymentHandlerImpl) NeedSetInvoiceAutoConfirm() bool {
	return true
}
func (azure *AzurePaymentHandlerImpl) CheckInvoice() {

}
