package tests

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/pingcap/dbaas/billing2/handler/tidbmgmtsvchandler"
	"net/http"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"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/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/infrahandler"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/stretchr/testify/suite"
)

type CostExplorerTrendsTestSuite struct {
	tests.BillingTestSuite
	router *gin.Engine
}

func (suite *CostExplorerTrendsTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	infrahandler.Set(&MockInfraHandlerImpl{})
	centralhandler.SetCentralHandler(&MockCentralHandlerImpl2{})
	tidbmgmtsvchandler.Set(&MockMgmtHandlerImpl3{})
	billingGroup := suite.router.Group("/api/v1/billing")
	ws := ginutils.MyRoute{Route: billingGroup}
	api.InitializeCostExplorerRouter(ginutils.MyRoute{Route: ws})

	billingOpenAPIGroup := suite.router.Group("/v1beta1")
	wsOpenAPI := ginutils.MyRoute{Route: billingOpenAPIGroup}
	api.InitializeOpenAPICostExplorerRouter(ginutils.MyRoute{Route: wsOpenAPI})

	datainit.CreateCategoryLabels()
	datainit.CreateDefaultUnits()
}

type MockMgmtHandlerImpl3 struct {
	tidbmgmtsvchandler.MgmtService
}

func (m *MockMgmtHandlerImpl3) GetAllClustersByTenantID(ctx context.Context, tenantID uint64) (ret []*forms.Cluster, err error) {
	return []*forms.Cluster{
		{
			ID:       datainit.DefaultUnitCostsDedicatedAWSClusterID,
			Name:     fmt.Sprint(constant.UnknownClusterName, 1),
			TenantID: uint(tenantID),
		},
		{
			ID:       datainit.DefaultUnitCostsDedicatedGCPClusterID,
			Name:     fmt.Sprint(constant.UnknownClusterName, 1),
			TenantID: uint(tenantID),
		},
		{
			ID:       10,
			Name:     fmt.Sprint(constant.UnknownClusterName, 10),
			TenantID: uint(tenantID),
		},
	}, nil

}

type MockInfraHandlerImpl struct {
	infrahandler.InfraHandler
}

func (m *MockInfraHandlerImpl) ListClusters(tenantID uint) (ret []*forms.Cluster, err error) {
	return []*forms.Cluster{
		{
			ID:       datainit.DefaultUnitCostsDedicatedAWSClusterID,
			Name:     fmt.Sprint(constant.UnknownClusterName, 1),
			TenantID: tenantID,
		},
		{
			ID:       datainit.DefaultUnitCostsDedicatedGCPClusterID,
			Name:     fmt.Sprint(constant.UnknownClusterName, 1),
			TenantID: tenantID,
		},
		{
			ID:       10,
			Name:     fmt.Sprint(constant.UnknownClusterName, 10),
			TenantID: tenantID,
		},
	}, nil
}

type MockCentralHandlerImpl2 struct {
	centralhandler.CentralHandler
}

func (*MockCentralHandlerImpl2) ListProjects(tenantID uint, containsDeleted bool) (ret []*forms.Project, err error) {
	return []*forms.Project{
		{
			ID:       1,
			TenantID: tenantID,
			Name:     "Project 1",
		},
		{
			ID:       2,
			TenantID: tenantID,
			Name:     "Project 2",
		},
	}, nil
}

func (*MockCentralHandlerImpl2) GetProjectInfo(projectID uint) (*forms.Project, error) {
	return &forms.Project{
		ID:   projectID,
		Name: "Project",
	}, nil
}

func TestCostExplorerTrendsTestSuite(t *testing.T) {
	suite.Run(t, new(CostExplorerTrendsTestSuite))
}

func (suite *CostExplorerTrendsTestSuite) TestGenerateCostTrends() {
	assert := suite.Assert()
	var err error
	tenantID := uint(30008)
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	timeutils.SetMockNow(time.Date(2022, 1, 1, 12, 0, 0, 0, time.UTC))
	datainit.CreateDefaultUserUnitCosts(tenantID, datainit.BillingDate, 60)
	start := time.Date(2022, 10, 17, 0, 0, 0, 0, time.UTC)
	end := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
	timeGroup := forms.CostExploreTrendsTimeGroupMonth
	internal := false
	query := &forms.CostExploreTrendsInput{
		CostExplorerTrendsFilter: forms.CostExplorerTrendsFilter{
			StartDate: &start,
			EndDate:   &end,
			Internal:  &internal,
		},
		TimeGroup:   &timeGroup,
		BreakdownBy: []forms.CostExplorerTrendsBreakdownKey{forms.CostExploreTrendsBreakdownKeyService},
	}
	w := tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends", query)
	assert.Equal(http.StatusBadRequest, w.Code)
	start = time.Date(2022, 12, 30, 0, 0, 0, 0, time.UTC)
	end = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	query.StartDate = &start
	query.EndDate = &end
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends", query)
	assert.Equal(http.StatusBadRequest, w.Code)
	query.StartDate = nil
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends", query)
	assert.Equal(http.StatusBadRequest, w.Code)
	start = time.Date(2021, 10, 17, 0, 0, 0, 0, time.UTC)
	end = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
	query.StartDate = &start
	query.EndDate = &end
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends", query)
	assert.Equal(http.StatusOK, w.Code)
	var r forms.CostExploreTrendsOutput
	err = json.Unmarshal(w.Body.Bytes(), &r)
	assert.NoError(err)
	assert.Len(r.Elements, 3)
	for i, billingDate := range []string{"2021-10-01", "2021-11-01", "2021-12-01"} {
		assert.Equal(billingDate, r.Elements[i].BillingDate)
	}
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends/csv", query)
	assert.Equal(http.StatusOK, w.Code)
	target := `service,total_cost,2021-10-01,2021-11-01,2021-12-01
TiDB Dedicated - Data Transfer - Same Region,80,0,40,40
TiDB Dedicated - Node Compute - TiDB,80,0,40,40
TiDB Dedicated - Node Compute - TiKV,80,0,40,40
TiDB Serverless - Request Units,40,0,20,20
TiDB Serverless - Row-based Storage,40,0,20,20
Total,320,0,160,160
`
	assert.Equal(target, w.Body.String())
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends/csv?with_discounts_and_credits=true", query)
	assert.Equal(http.StatusOK, w.Code)
	withDiscountAndCreditstarget := `service,running_total,discounts,credits,total_cost,2021-10-01,2021-11-01,2021-12-01
TiDB Dedicated - Data Transfer - Same Region,120,0,-40,80,0,40,40
TiDB Dedicated - Node Compute - TiDB,240,-120,-40,80,0,40,40
TiDB Dedicated - Node Compute - TiKV,240,-120,-40,80,0,40,40
TiDB Serverless - Request Units,50,0,-10,40,0,20,20
TiDB Serverless - Row-based Storage,50,0,-10,40,0,20,20
Total,700,-240,-140,320,0,160,160
`
	assert.Equal(withDiscountAndCreditstarget, w.Body.String())
	query.EndDate = nil
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends", query)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &r)
	assert.NoError(err)
	assert.Len(r.Elements, 4)
	for i, billingDate := range []string{"2021-10-01", "2021-11-01", "2021-12-01", "2022-01-01"} {
		assert.Equal(billingDate, r.Elements[i].BillingDate)
	}
	start = time.Date(2021, 11, 17, 0, 0, 0, 0, time.UTC)
	end = time.Date(2021, 11, 18, 0, 0, 0, 0, time.UTC)
	query.StartDate = &start
	query.EndDate = &end
	query.Projects = []string{fmt.Sprint(datainit.DefaultUnitCostsProjectID)}
	query.BreakdownBy = []forms.CostExplorerTrendsBreakdownKey{forms.CostExploreTrendsBreakdownKeyCluster, forms.CostExploreTrendsBreakdownKeyProject}
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends", query)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &r)
	assert.NoError(err)
	for _, elem := range r.Elements {
		for _, b := range elem.Breakdown {
			assert.Equal(datainit.DefaultUnitCostsProjectID, *b.ProjectID)
		}
	}
}

func (suite *CostExplorerTrendsTestSuite) TestGenerateCostExplorerArg() {
	mgmt := config.Get().GetBool(config.EnableUseMgmtGetClusterInfo)
	defer func() {
		config.Get().Set(config.EnableUseMgmtGetClusterInfo, mgmt)
	}()
	config.Get().Set(config.EnableUseMgmtGetClusterInfo, false)
	assert := suite.Assert()
	var err error
	tenantID := uint(30008)
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	w := tests.PerformGetRequest(suite.router, "/api/v1/billing/tenants/30008/costexplorer/args")
	assert.Equal(http.StatusOK, w.Code)
	var r forms.CostExplorerArgs
	err = json.Unmarshal(w.Body.Bytes(), &r)
	assert.Nil(err)
	assert.Len(r.Projects, 2)
	assert.Len(r.Clusters, 3)
}

func (suite *CostExplorerTrendsTestSuite) TestGenerateCostTrendsOpenAPI() {
	assert := suite.Assert()
	var err error
	tenantID := uint(30008)
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	timeutils.SetMockNow(time.Date(2022, 1, 1, 12, 0, 0, 0, time.UTC))
	datainit.CreateDefaultUserUnitCosts(tenantID, datainit.BillingDate, 60)
	start := time.Date(2022, 10, 17, 0, 0, 0, 0, time.UTC)
	end := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
	timeGroup := forms.CostExploreTrendsTimeGroupMonth
	query := &forms.TrendsInput{
		Filter: forms.TrendsFilter{
			TimeRange: forms.TimeRange{
				StartDate: &start,
				EndDate:   &end,
			},
		},
		Groups: forms.Groups{
			TimeGranularity: &timeGroup,
			GroupBy:         []forms.CostExplorerTrendsBreakdownKey{forms.CostExploreTrendsBreakdownKeyService},
		},
	}
	path := "/v1beta1/tenants/30008/billsCostExplorer"
	w := tests.PerformRequest(suite.router, "POST", path, query)
	assert.Equal(http.StatusInternalServerError, w.Code)
	start = time.Date(2022, 12, 30, 0, 0, 0, 0, time.UTC)
	end = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	query.Filter.TimeRange.StartDate = &start
	query.Filter.TimeRange.EndDate = &end
	w = tests.PerformRequest(suite.router, "POST", path, query)
	assert.Equal(http.StatusInternalServerError, w.Code)
	query.Filter.TimeRange.StartDate = nil
	w = tests.PerformRequest(suite.router, "POST", path, query)
	assert.Equal(http.StatusInternalServerError, w.Code)
	start = time.Date(2021, 10, 17, 0, 0, 0, 0, time.UTC)
	end = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
	query.Filter.TimeRange.StartDate = &start
	query.Filter.TimeRange.EndDate = &end
	w = tests.PerformRequest(suite.router, "POST", path, query)
	assert.Equal(http.StatusInternalServerError, w.Code)
	//var r forms.BillsCostExplorer
	//err = json.Unmarshal(w.Body.Bytes(), &r)
	//assert.NoError(err)
	//assert.Len(r.Elements, 3)
	//for i, billingDate := range []string{"2021-10-01", "2021-11-01", "2021-12-01"} {
	//	assert.Equal(billingDate, r.Elements[i].BilledDate)
	//}
	////	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/30008/costexplorer/trends/csv", query)
	////	assert.Equal(http.StatusOK, w.Code)
	////	target := `service,total_cost,2021-10-01,2021-11-01,2021-12-01
	////TiDB Dedicated - Data Transfer - Same Region,80,0,40,40
	////TiDB Dedicated - Node Compute - TiDB,80,0,40,40
	////TiDB Dedicated - Node Compute - TiKV,80,0,40,40
	////TiDB Serverless - Request Units,40,0,20,20
	////TiDB Serverless - Row-based Storage,40,0,20,20
	////Total,320,0,160,160
	////`
	////	assert.Equal(target, w.Body.String())
	//
	//query.Filter.TimeRange.EndDate = nil
	//w = tests.PerformRequest(suite.router, "POST", path, query)
	//assert.Equal(http.StatusOK, w.Code)
	//err = json.Unmarshal(w.Body.Bytes(), &r)
	//assert.NoError(err)
	//assert.Len(r.Elements, 4)
	//for i, billingDate := range []string{"2021-10-01", "2021-11-01", "2021-12-01", "2022-01-01"} {
	//	assert.Equal(billingDate, r.Elements[i].BilledDate)
	//}
	//start = time.Date(2021, 11, 17, 0, 0, 0, 0, time.UTC)
	//end = time.Date(2021, 11, 18, 0, 0, 0, 0, time.UTC)
	//query.Filter.TimeRange.StartDate = &start
	//query.Filter.TimeRange.EndDate = &end
	//query.Filter.ProjectIDs = []string{fmt.Sprint(datainit.DefaultUnitCostsProjectID)}
	//query.Groups.GroupBy = []forms.CostExplorerTrendsBreakdownKey{forms.CostExploreTrendsBreakdownKeyCluster, forms.CostExploreTrendsBreakdownKeyProject}
	//w = tests.PerformRequest(suite.router, "POST", path, query)
	//assert.Equal(http.StatusOK, w.Code)
	//err = json.Unmarshal(w.Body.Bytes(), &r)
	//assert.NoError(err)
	//for _, elem := range r.Elements {
	//	for _, b := range elem.Breakdown {
	//		//assert.Equal(datainit.DefaultUnitCostsProjectID, *b.ProjectID)
	//		fmt.Print(b.DetailKey)
	//		fmt.Print(b.DetailName)
	//
	//	}
	//}
}

func (suite *CostExplorerTrendsTestSuite) TestGenerateCostExplorerArgOpenAPI() {
	assert := suite.Assert()
	var err error
	tenantID := uint(30008)
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	w := tests.PerformGetRequest(suite.router, "/v1beta1/tenants/30008/billsCostExplorer/args")
	assert.Equal(http.StatusInternalServerError, w.Code)

}
