package bss

import (
	"fmt"
	"net/http"
	"time"

	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/qa-PlatformAPIAuto/common"
	"github.com/tidbcloud/qa-PlatformAPIAuto/webScene/br/aws"
	"github.com/tidbcloud/qa-PlatformAPIAuto/webScene/cbp/iam"
	"github.com/tidbcloud/qa-gourd/ProductService"
	"github.com/tidbcloud/qa-gourd/handler"
	"github.com/tidbcloud/qa-gourd/util"
)

func RunBillingScene(w *ProductService.WebRequestInfo, UrlInfoMap map[string]ProductService.UrlInfo, reporter *util.AllureReporter) {
	bt := &BillingTest{}
	bt.Init(w, UrlInfoMap, reporter)
	bt.CheckBillingScene()
}

// This case can not run in parallel since we have to switch token a lot.
func (bt *BillingTest) CheckBillingScene() {
	// SignUp tenant
	bt.Reporter.StepReport("A-SignUpTenant", func() {
		iam.RunSignUpTest(bt.w, bt.URLInfoMap, bt.Reporter)
		bt.SetbtTestUserEnv(bt.w.User.TenantEmail, bt.w.User.OrgId, bt.w.User.ProjectId)
	})

	// newly registered - free trial tenant check
	bt.Reporter.StepReport("B-FreeTrialRestrict", func() {
		bt.CheckFreeTrialTenantRestrict()
	})

	// poc tenant check
	bt.Reporter.StepReport("C-PoCRestrictAndLifeCycle", func() {
		bt.Reporter.StepReport("C - 1 - EnablePoC - CheckPoCRestrict", func() {
			bt.CheckCreditsScene()
		})
		bt.Reporter.StepReport("C - 2 - PoCClosing - PoCLifeCycleCheck(see final backup cases)", func() {
			aws.ExecuteFinalBackupCase(bt.Reporter.T, bt.w)
		})
	})

	// ondemand stripe tenant check
	bt.Reporter.StepReport("D-OndemandRestrict", func() {
		// add bankcard
		bt.Reporter.StepReport("Add bankcard", func() {
			p := &PaymentTest{}
			p.Init(bt.w, bt.URLInfoMap, bt.Reporter)
			p.setUpIntent()
			cardId := p.CheckAddValidPaymentCard()
			p.CheckSetDefaultPaymentCard(cardId)
		})
		// check tenant plan is ondemand
		bt.Reporter.StepReport("Ondemand tenant plan check", func() {
			util.SecondTimeWait(30, 10, "Wait For Tenant Plan Sync")
			bt.CheckTenantPlan(bt.SetOrgTenantPlan(), PlanOnDemand)
		})
		// check create dedicated cluster
		bt.Reporter.StepReport("Ondemand tenant can create Dedicated Cluster", func() {
			bt.CheckCreateDedicatedInSpecEnv(true)
		})
	})

	// ondemand discounts & credits and consume check
	bt.Reporter.StepReport("E-DiscountsCreditsConsume", func() {
		// prepare test credits & discounts
		bt.prepareCreditsDiscounts()
		// add credits
		bt.Reporter.StepReport("Add credits", func() {
			bt.CheckOPSAddCredits(onDemandCreditBody1)
			bt.CheckOPSAddCredits(onDemandCreditBody2)
			bt.CheckOPSAddCredits(onDemandCreditBody3)
			bt.CheckOPSAddCredits(onDemandCreditBody4)
		})

		// add discounts
		bt.Reporter.StepReport("Add discounts", func() {
			bt.CheckOPSAddDiscount(onDemandDiscountBody1)
			bt.CheckOPSAddDiscount(onDemandDiscountBody2)
			bt.CheckOPSAddDiscount(onDemandDiscountBody3)
			bt.CheckOPSAddDiscount(onDemandDiscountBody4)
		})
		// create metering and check response
		bt.Reporter.StepReport("Add metering", func() {
			bt.CheckOPSCreateMetering(onDemandMeteringBody1)
			bt.CheckOPSCreateMetering(onDemandMeteringBody2)
			bt.CheckOPSCreateMetering(onDemandMeteringBody3)
			bt.CheckOPSCreateMetering(onDemandMeteringBody4)
			util.SecondTimeWait(36, 10, "Wait For Credits Discounts Expired Sync")
			bt.CheckOPSCreateMetering(onDemandMeteringBody5)
		})

	})

	// ondemand bills check & invoice check
	bt.Reporter.StepReport("F-BillsInvoicesCheck", func() {
		// check bills
		for index := range OndemandExpectBills.CheckItems {
			OndemandExpectBills.CheckItems[index].BilledDate = time.Now().AddDate(0, 0, 11).UTC().Format("2006-01-02")
		}
		bt.Reporter.StepReport("Check Bills", func() {
			bt.CheckBills(OndemandExpectBills)
		})
		// create invoice and check monthly invoice
		bt.Reporter.StepReport("Check Monthly Invoice", func() {
			bt.CheckMonthlyInvoice()
		})
	})

	// // delete org
	// bt.Reporter.StepReport("G-DeleteTenant", func() {
	// })
}

func (bt *BillingTest) CheckFreeTrialTenantRestrict() {
	// free trial tenant can not create dedicate cluster
	bt.Reporter.StepReport("Free trial tenant can not create Dedicated Cluster", func() {
		bt.CheckCreateDedicatedInSpecEnv(false)
	})
	// free trial tenant can not create project
	bt.Reporter.StepReport("Free trial tenant can not create Project", func() {
		bt.CheckCreateProjectInSpecEnv(false)
	})
	// free trial tenant can create serverless cluster
	bt.Reporter.StepReport("Free trial tenant can create Serverless Cluster", func() {
		bt.CheckCreateServerlessInSpecEnv(true)
	})
}

func (bt *BillingTest) CheckCreateDedicatedInSpecEnv(expectRes bool) {
	bt.w.Cluster.Region, _, _ = bt.w.GetRandomRegion()
	bt.w.Cluster.TiDBNodeCount = 1
	bt.w.Cluster.TiKVNodeCount = 3
	bt.w.Cluster.TiFlashNodeCount = 0
	bt.w.Cluster.IsServerless = false
	dedicatedClusterId, _, err := bt.w.CreateCluster()

	var message string
	if expectRes {
		message = "Expect create dedicated cluster success"
	} else {
		message = "Expect create dedicated cluster failed"
	}
	bt.Reporter.StepReport(fmt.Sprintf("%s, create id is : [%s], err is : [%v]",
		message,
		dedicatedClusterId,
		err,
	), func() {
		if expectRes && (dedicatedClusterId == "" || err != nil) {
			assert.Fail(bt.Reporter.T, message)
		}
		if !expectRes && (dedicatedClusterId != "" || err == nil) {
			assert.Fail(bt.Reporter.T, message)
		}
	})

	if expectRes {
		bt.Reporter.StepReport(fmt.Sprintf("Delete Cluster [%s]",
			dedicatedClusterId,
		), func() {
			bt.w.DeleteClusterById(dedicatedClusterId)
		})
	}

}
func (bt *BillingTest) CheckCreateServerlessInSpecEnv(expectRes bool) {
	serverlessClusterId, err := bt.w.CreateServerlessCluster()
	var message string
	if expectRes {
		message = "Expect create serverless cluster success"
	} else {
		message = "Expect create serverless cluster cluster failed"
	}
	bt.Reporter.StepReport(fmt.Sprintf("%s , create id is : [%s]",
		message,
		serverlessClusterId,
	), func() {
		if expectRes && serverlessClusterId == "" && err != nil {
			assert.Fail(bt.Reporter.T, message)
		}
		if !expectRes && (serverlessClusterId != "" || err == nil) {
			assert.Fail(bt.Reporter.T, message)
		}
	})

	if expectRes {
		bt.Reporter.StepReport(fmt.Sprintf("Delete Cluster [%s]",
			serverlessClusterId,
		), func() {
			bt.w.DeleteClusterById(serverlessClusterId)
		})
	}
}
func (bt *BillingTest) CheckCreateProjectInSpecEnv(expectRes bool) {
	createProjectResponse := bt.w.CreateProject(false)
	var message string
	if expectRes {
		message = "Expect create project success"
	} else {
		message = "Expect create project failed"
	}
	bt.Reporter.StepReport(fmt.Sprintf("%s , create response is : [%s]",
		message,
		createProjectResponse.Body().Raw(),
	), func() {
		if expectRes {
			createProjectResponse.Status(http.StatusOK)
		} else {
			createProjectResponse.Status(http.StatusBadRequest)
		}
	})
}

func (bt *BillingTest) CheckOPSAddDiscount(addDiscountBodys ...*common.AddDiscount) {
	pathParam := map[string]string{
		"org_id": bt.w.User.OrgId,
	}
	startTime := time.Now().UTC().AddDate(0, 0, -1)
	EndTime := time.Now().UTC().AddDate(0, 0, 1)
	addDiscountBody := &common.AddDiscount{
		StartedAt: startTime,
		ExpiredAt: EndTime,
		Value:     decimal.NewFromFloat(0.5),
		Usage:     "commitment_plan",
	}
	for _, Body := range addDiscountBodys {
		addDiscountBody = Body
	}
	bt.w.ResetRequestEmail(handler.OtherUserInfo.OpsAdminEmail)
	addDiscountResp := bt.w.DoWebAPIRequestByUrlInfo(bt.URLInfoMap["addDiscount"], addDiscountBody, pathParam, nil)
	bt.w.ResetRequestEmail(bt.btUserEmail)
	bt.Reporter.StepReport(fmt.Sprintf("addDiscount's status code is %s, response is : %v",
		addDiscountResp.Raw().Status,
		addDiscountResp.Body().Raw(),
	), func() {
		addDiscountResp.Status(http.StatusOK)
	})
}

func (bt *BillingTest) CheckOPSCreateMetering(addMeteringBody *common.UnitCosts) {
	pathParam := map[string]string{
		"org_id": bt.w.User.OrgId,
	}
	addMeteringBody.CostTimeStamp = time.Now().AddDate(0, 0, FutureCheckDate).Unix()
	bt.w.ResetRequestEmail(handler.OtherUserInfo.OpsAdminEmail)
	addMeteringResp := bt.w.DoWebAPIRequestByUrlInfo(bt.URLInfoMap["addMetering"], addMeteringBody, pathParam, nil)
	bt.w.ResetRequestEmail(bt.btUserEmail)
	bt.Reporter.StepReport(fmt.Sprintf("addMetering's status code is %s, response is : %v",
		addMeteringResp.Raw().Status,
		addMeteringResp.Body().Raw(),
	), func() {
		addMeteringResp.Status(http.StatusOK)
	})
}

func (bt *BillingTest) prepareCreditsDiscounts() {
	filterMap := bt.SearchFilterTypeId([]string{"Node Compute", "Node Storage", "Backup", "Changefeed"})
	computeFilter := common.BindType{TypeId: filterMap["Node Compute"]}
	StorageFilter := common.BindType{TypeId: filterMap["Node Storage"]}
	BackupFilter := common.BindType{TypeId: filterMap["Backup"]}
	ChangefeedFilter := common.BindType{TypeId: filterMap["Changefeed"]}
	StorageBackupChangefeedFilter := []common.BindType{StorageFilter, BackupFilter, ChangefeedFilter}
	// credits package 1 : start 5 mins later ; end 6 mins later ; credits 10 ; filter = compute ; discountEnable = true
	onDemandCreditBody1.BindTypes = []common.BindType{computeFilter}
	onDemandCreditBody1.StartDate = time.Now().UTC().Add(5*time.Minute).AddDate(0, 0, FutureCheckDate).Format("2006-01-02T15:04:05Z")
	onDemandCreditBody1.ExpiredDate = time.Now().UTC().Add(6*time.Minute).AddDate(0, 0, FutureCheckDate).Format("2006-01-02T15:04:05Z")
	// credits package 2 : start 5 mins before ; end 30 mins later ; credits 10 ; filter = storage / backup / changefeed ; discountEnable = false
	onDemandCreditBody2.BindTypes = StorageBackupChangefeedFilter
	onDemandCreditBody2.StartDate = time.Now().UTC().Add(-5*time.Minute).AddDate(0, 0, FutureCheckDate).Format("2006-01-02T15:04:05Z")
	onDemandCreditBody2.ExpiredDate = time.Now().UTC().Add(30*time.Minute).AddDate(0, 0, FutureCheckDate).Format("2006-01-02T15:04:05Z")
	// credits package 3 : start 5 mins before ; end 30 mins later ; credits 5 ; filter = storage ; discountEnable = true
	onDemandCreditBody3.BindTypes = []common.BindType{StorageFilter}
	onDemandCreditBody3.StartDate = onDemandCreditBody2.StartDate
	onDemandCreditBody3.ExpiredDate = onDemandCreditBody2.ExpiredDate
	// credits package 4 : start 5 mins before ; end 20 mins later ; credits 10 ; filter = backup ; discountEnable = true
	onDemandCreditBody4.BindTypes = []common.BindType{BackupFilter}
	onDemandCreditBody4.StartDate = onDemandCreditBody2.StartDate
	onDemandCreditBody4.ExpiredDate = time.Now().UTC().Add(20*time.Minute).AddDate(0, 0, FutureCheckDate).Format("2006-01-02T15:04:05Z")

	computeFilter2 := common.UnitFilterSlim{FilterID: filterMap["Node Compute"]}
	StorageFilter2 := common.UnitFilterSlim{FilterID: filterMap["Node Storage"]}
	BackupFilter2 := common.UnitFilterSlim{FilterID: filterMap["Backup"]}
	ChangefeedFilter2 := common.UnitFilterSlim{FilterID: filterMap["Changefeed"]}
	StorageBackupChangefeedFilter2 := []*common.UnitFilterSlim{&StorageFilter2, &BackupFilter2, &ChangefeedFilter2}
	// discounts package 1 : start 5 mins later ; end 6 mins later ; discount 0.5 ; filter = compute
	onDemandDiscountBody1.UnitFilters = []*common.UnitFilterSlim{&computeFilter2}
	onDemandDiscountBody1.StartedAt = time.Now().UTC().Add(5*time.Minute).AddDate(0, 0, FutureCheckDate)
	onDemandDiscountBody1.ExpiredAt = time.Now().UTC().Add(6*time.Minute).AddDate(0, 0, FutureCheckDate)
	// discounts package 2 : start 5 mins before ; end 30 mins later ; discount 0.5 ; filter = storage / backup
	onDemandDiscountBody2.UnitFilters = StorageBackupChangefeedFilter2
	onDemandDiscountBody2.StartedAt = time.Now().UTC().Add(-5*time.Minute).AddDate(0, 0, FutureCheckDate)
	onDemandDiscountBody2.ExpiredAt = time.Now().UTC().Add(30*time.Minute).AddDate(0, 0, FutureCheckDate)
	// discounts package 3 : start 5 mins before ; end 30 mins later ; discount 0.3 ; filter = storage
	onDemandDiscountBody3.UnitFilters = []*common.UnitFilterSlim{&StorageFilter2}
	onDemandDiscountBody3.StartedAt = onDemandDiscountBody2.StartedAt
	onDemandDiscountBody3.ExpiredAt = onDemandDiscountBody2.ExpiredAt
	// discounts package 4 : start 5 mins before ; end 20 mins later ; discount 0.5 ; filter = backup
	onDemandDiscountBody4.UnitFilters = []*common.UnitFilterSlim{&BackupFilter2}
	onDemandDiscountBody4.StartedAt = onDemandDiscountBody2.StartedAt
	onDemandDiscountBody4.ExpiredAt = time.Now().UTC().Add(20*time.Minute).AddDate(0, 0, FutureCheckDate)
}

// check monthly invoice
func (bt *BillingTest) CheckMonthlyInvoice() {
	// generate invoice in ops
	invoiceParam := map[string]string{
		"org_id": bt.w.User.OrgId,
	}
	invoiceBody := map[string]string{
		"start_date": time.Now().AddDate(0, 0, FutureCheckDate).Format("2006-01-02T15:04:05Z"),
		"type":       "monthly",
	}
	bt.w.ResetRequestEmail(handler.OtherUserInfo.OpsAdminEmail)
	generateInvoiceResp := bt.w.DoWebAPIRequestByUrlInfo(bt.URLInfoMap["generateInvoice"], invoiceBody, invoiceParam, nil)
	bt.Reporter.StepReport(fmt.Sprintf("GenerateInvoice's status code is %s, response is : %v",
		generateInvoiceResp.Raw().Status,
		generateInvoiceResp.Body().Raw(),
	), func() {
		generateInvoiceResp.Status(http.StatusOK)
	})
	// get invoice
	getInvoiceResp := bt.w.DoWebAPIRequestByUrlInfo(bt.URLInfoMap["getAdminInvoice"], nil, invoiceParam, nil)
	bt.Reporter.StepReport(fmt.Sprintf("GetInvoice's status code is %s, number of actual invoice items is [%v] (expect 1) ,actual invoice cost price is [%v], expect invoice cost is [%v]",
		getInvoiceResp.Raw().Status,
		getInvoiceResp.JSON().Array().Length().Raw(),
		getInvoiceResp.JSON().Array().Value(0).Object().Value("cost_price").Raw(),
		onDemandExpectInvoices,
	), func() {
		getInvoiceResp.Status(http.StatusOK)
		getInvoiceResp.JSON().Array().Length().IsEqual(1)
		getInvoiceResp.JSON().Array().Value(0).Object().Value("cost_price").IsEqual(onDemandExpectInvoices)
	})

	bt.w.ResetRequestEmail(bt.btUserEmail)
}

// check bills detail
func (bt *BillingTest) CheckBills(expect ExpectBillsResult) {
	// get bills
	billsDetailsParam := map[string]string{
		"org_id": bt.w.User.OrgId,
		"year":   time.Now().AddDate(0, 0, FutureCheckDate).Format("2006"),
		"month":  time.Now().AddDate(0, 0, FutureCheckDate).Format("01"),
	}
	getBillsDetailsResp := bt.w.DoWebAPIRequestByUrlInfo(bt.URLInfoMap["getBillsDetails"], nil, billsDetailsParam, nil)
	bt.Reporter.StepReport(fmt.Sprintf("GetBillsDetails's status code is %d",
		getBillsDetailsResp.Raw().StatusCode,
	), func() {
		getBillsDetailsResp.Status(http.StatusOK)
	})
	// check each
	var time time.Time
	for _, billCheck := range expect.CheckItems {
		actualBills, _ := ProductService.GetBillsDetailsOfLabel(getBillsDetailsResp, []string{billCheck.Service}, time, billCheck.ProjectName, billCheck.ClusterName)
		if len(*actualBills) != 1 {
			bt.Reporter.StepReport(fmt.Sprintf("Bills Check failed, expect items are [%s,%s,%s],expect 1 bill item, but got %d bills (%v)",
				billCheck.Service, billCheck.ProjectName, billCheck.ClusterName,
				len(*actualBills),
				*actualBills,
			), func() {
				assert.Fail(bt.Reporter.T, fmt.Sprintf("Bills Check failed, expect 1 bills, but got %d bills", len(*actualBills)))
			})
		} else {
			bt.Reporter.StepReport(fmt.Sprintf("Bills Check , BilledDate : %s (expect %s), ClusterName : %s (expect %s), ProjectName : %s (expect %s), Service : %s (expect %s), Amount : %s (expect %s), BillableCost : %s (expect %s), CreditUsage : %s (expect %s), DiscountCost : %s (expect %s)",
				(*actualBills)[0].Billed_date, billCheck.BilledDate,
				(*actualBills)[0].Cluster_name, billCheck.ClusterName,
				(*actualBills)[0].Project_name, billCheck.ProjectName,
				(*actualBills)[0].Service, billCheck.Service,
				(*actualBills)[0].Amount, billCheck.Amount,
				(*actualBills)[0].Billable_cost, billCheck.BillableCost,
				(*actualBills)[0].Credit_usage, billCheck.CreditUsage,
				(*actualBills)[0].Discount_cost, billCheck.DiscountCost,
			), func() {
				assert.Equal(bt.Reporter.T, billCheck.BilledDate, (*actualBills)[0].Billed_date)
				assert.Equal(bt.Reporter.T, billCheck.ClusterName, (*actualBills)[0].Cluster_name)
				assert.Equal(bt.Reporter.T, billCheck.ProjectName, (*actualBills)[0].Project_name)
				assert.Equal(bt.Reporter.T, billCheck.Service, (*actualBills)[0].Service)
				assert.Equal(bt.Reporter.T, billCheck.Amount, (*actualBills)[0].Amount)
				assert.Equal(bt.Reporter.T, billCheck.BillableCost, (*actualBills)[0].Billable_cost)
				assert.Equal(bt.Reporter.T, billCheck.CreditUsage, (*actualBills)[0].Credit_usage)
				assert.Equal(bt.Reporter.T, billCheck.DiscountCost, (*actualBills)[0].Discount_cost)
			})
		}

	}
}
