package tests

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/cachehandler"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"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/shopspring/decimal"
	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/tiinf/sdk/auth"
)

const (
	AlicloudManagerOrgID  = uint(2)
	AlicloudCustomerOrgID = uint(3)
)

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

func (suite *CostExplorerOpenAPITestSuite) SetupTest() {
	assert := suite.Assert()
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	billingOpenAPIGroup := suite.router.Group("/")
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	billingOpenAPIGroup.Use(func(c *gin.Context) {
		accessOrgId := c.Query(auth.AccessOrgIdQueryKey)
		orgID, _ := strconv.ParseUint(accessOrgId, 10, 64)
		c.Set(auth.IdentityInfoKey, &auth.AuthnIdentityInfo{IdentityType: auth.AuthnIdentityTypeUserToken, OrgID: orgID})
	})
	wsOpenAPI := ginutils.MyRoute{Route: billingOpenAPIGroup}
	api.InitializeCostExplorerForPartnerPrivateAPIRouter(wsOpenAPI)

	// Setup mock handlers and initial data
	centralhandler.SetCentralHandler(&MockCentralHandlerOpenAPIImpl{})
	_, err := datainit.CreateBillingConfig(AlicloudManagerOrgID, entity.BillingConfigType_Alicloud)
	assert.NoError(err)
	_, err = datainit.CreateBillingConfig(AlicloudCustomerOrgID, entity.BillingConfigType_AlicloudCustomer)
	assert.NoError(err)
	config.Get().Set(config.AlicloudManagingOrgID, AlicloudManagerOrgID)
	datainit.CreateCategoryLabels()
	datainit.CreateDefaultUnits()
	cachehandler.InitWithLocal()
	config.Get().Set(config.EnableCache, false)
}

type MockCentralHandlerOpenAPIImpl struct {
	centralhandler.CentralHandler
	tenants []forms.Tenant
}

func (*MockCentralHandlerOpenAPIImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	return &forms.Tenant{
		TenantID: tenantID,
		Status:   centralhandler.TenantStatusActive,
	}, nil
}

func (*MockCentralHandlerOpenAPIImpl) GetTenantIdByOpenAPIAccess(apiKey string) (tenantID uint64, err error) {
	return 1, nil
}

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

func mockCentralUseOpenAPI() {
	central := &MockCentralHandlerOpenAPIImpl{}
	centralhandler.SetCentralHandler(central)
}
func TestCostExplorerOpenAPISuite(t *testing.T) {
	suite.Run(t, new(CostExplorerOpenAPITestSuite))
}

func (suite *CostExplorerOpenAPITestSuite) TestRouter() {
	assert := suite.Assert()
	suite.router.Use(ginutils.ErrorMiddleWare)

	billingGroup := suite.router.Group("/api/v1/billing")
	ws := ginutils.MyRoute{Route: billingGroup}
	api.InitializeCostExplorerRouter(ginutils.MyRoute{Route: ws})

	billingOpenAPIGroup := suite.router.Group("/openapi/v1")
	wsOpenAPI := ginutils.MyRoute{Route: billingOpenAPIGroup}

	api.InitializeOpenAPICostExplorerRouter(ginutils.MyRoute{Route: wsOpenAPI})

	_, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)

	w := tests.PerformRequest(suite.router, "GET", "/openapi/v1/tenants/1/bills/"+timeutils.GetNow().Format("2006-01"), nil)

	assert.Equal(http.StatusInternalServerError, w.Code)

	query := &forms.CostExplorerQuery{
		StartDate: datainit.BillingDate,
		EndDate:   datainit.BillingDate.Add(time.Hour),
	}
	w = tests.PerformRequest(suite.router, "POST", "/api/v1/billing/tenants/1/costexplorer", query)
	assert.Equal(http.StatusOK, w.Code)

}

// TestGetPartnerInvoicedBills tests the API for retrieving monthly bills based on invoice sent date.
func (suite *CostExplorerOpenAPITestSuite) TestGetPartnerInvoicedBills() {
	assert := suite.Assert()

	tenantID := AlicloudCustomerOrgID
	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	assert.NoError(err)
	billedDate := time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)
	sentDate := time.Date(2023, 3, 15, 0, 0, 0, 0, time.UTC)

	// Create an invoice with a specific sent date
	invoice := &entity.Invoice{
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		SentDate:              &sentDate,
		Status:                entity.InvoiceStatus_SENT,
		Type:                  entity.InvoiceType_MONTHLY,
		CostPrice:             decimal.Zero,
		RawPrice:              decimal.Zero,
		InvoiceDate:           billedDate,
	}
	err = db.GetDB().Create(invoice).Error
	assert.NoError(err)

	// Create unit costs associated with this invoice
	unitCost := entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		CostTimeStamp:         billedDate.Unix(),
		Unit:                  "tidb_compute",
		Cost:                  10,
		Labels:                entity.Labels{"project": "proj1"},
		InvoiceID:             int(invoice.ID),
		Status:                entity.UserUnitCostStatus_DONE,
		ConsumedSource:        entity.ConsumedSource_Invoice,
		CostPrice:             decimal.NewFromInt(10),
		RawPrice:              decimal.NewFromInt(10),
	}
	err = db.GetDB().Create(&unitCost).Error
	assert.NoError(err)

	path := fmt.Sprintf("/invoicedBills/%s?accessOrgId=%d", sentDate.Format("2006-01"), AlicloudManagerOrgID)
	w := tests.PerformRequest(suite.router, "GET", path, nil)

	assert.Equal(http.StatusOK, w.Code)
	var res forms.PartnerBillsExplorer
	err = json.Unmarshal(w.Body.Bytes(), &res)
	assert.NoError(err)

	assert.Equal(decimal.NewFromInt(10), res.BillsExplorer.BillsOverView.TotalCost)
	assert.Equal(decimal.NewFromInt(10), res.BillsExplorer.BillsOverView.RunningTotal)
	assert.Equal(sentDate.Format("2006-01"), res.BillsExplorer.BillsOverView.BilledMonth)
	assert.Len(res.SummaryByService, 1)
	assert.Len(res.SummaryByCustomer.Customers, 1)
}

// TestGetPartnerInvoicedBillsDetails tests the API for retrieving monthly bill details based on invoice sent date.
func (suite *CostExplorerOpenAPITestSuite) TestGetPartnerInvoicedBillsDetails() {
	assert := suite.Assert()

	tenantID := AlicloudCustomerOrgID
	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	assert.NoError(err)
	billedDate := time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)
	sentDate := time.Date(2023, 3, 15, 0, 0, 0, 0, time.UTC)

	// Create an invoice with a specific sent date
	invoice := &entity.Invoice{
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		SentDate:              &sentDate,
		Status:                entity.InvoiceStatus_SENT,
		Type:                  entity.InvoiceType_MONTHLY,
		CostPrice:             decimal.Zero,
		RawPrice:              decimal.Zero,
		InvoiceDate:           billedDate,
	}
	err = db.GetDB().Create(invoice).Error
	assert.NoError(err)

	// Create unit costs associated with this invoice
	unitCost := entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		CostTimeStamp:         billedDate.Unix(),
		Unit:                  "tidb_compute",
		Cost:                  10,
		Labels:                entity.Labels{"project": "proj1"},
		InvoiceID:             int(invoice.ID),
		Status:                entity.UserUnitCostStatus_DONE,
		ConsumedSource:        entity.ConsumedSource_Invoice,
		CostPrice:             decimal.NewFromInt(10),
		RawPrice:              decimal.NewFromInt(10),
	}
	err = db.GetDB().Create(&unitCost).Error
	assert.NoError(err)

	config.Get().Set(config.EnableCache, false)
	defer config.Get().Set(config.EnableCache, true)
	path := fmt.Sprintf("/invoicedBillsDetails/%s?accessOrgId=%d", sentDate.Format("2006-01"), AlicloudManagerOrgID)
	w := tests.PerformRequest(suite.router, "GET", path, nil)

	assert.Equal(http.StatusOK, w.Code)
	var res forms.PartnerInvoicedBillsDetails
	err = json.Unmarshal(w.Body.Bytes(), &res)
	assert.NoError(err)

	assert.Len(res.BillsDetails, 1)
	detail := res.BillsDetails[0]
	assert.Equal(billedDate.Format("2006-01-02"), detail.BilledDate)
	assert.Equal(sentDate.Format("2006-01-02"), detail.InvoicedDate)
	assert.Equal(decimal.NewFromInt(10), detail.RunningTotal)
	assert.Equal("Default", detail.ServicePathName)
	config.Get().Set(config.AlicloudInvisibleCustomers, []string{fmt.Sprintf("%d-%d", tenantID, billingConfigID)})
	defer config.Get().Set(config.AlicloudInvisibleCustomers, []string{})
	w = tests.PerformRequest(suite.router, "GET", path, nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &res)
	assert.NoError(err)
	assert.Len(res.BillsDetails, 0)

}

// TestGetPartnerInvoicedBillsDetailsCSV tests the API for exporting monthly bill details to CSV based on invoice sent date.
func (suite *CostExplorerOpenAPITestSuite) TestGetPartnerInvoicedBillsDetailsCSV() {
	assert := suite.Assert()

	tenantID := AlicloudCustomerOrgID
	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	assert.NoError(err)
	billedDate := time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)
	sentDate := time.Date(2023, 3, 15, 0, 0, 0, 0, time.UTC)

	// Create an invoice with a specific sent date
	pc := "1223"
	invoice := &entity.Invoice{
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		SentDate:              &sentDate,
		Status:                entity.InvoiceStatus_SENT,
		Type:                  entity.InvoiceType_MONTHLY,
		CostPrice:             decimal.Zero,
		RawPrice:              decimal.Zero,
		InvoiceDate:           billedDate,
		PaymentCode:           &pc,
	}
	err = db.GetDB().Create(invoice).Error
	assert.NoError(err)

	// Create unit costs associated with this invoice
	unitCost := entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		CostTimeStamp:         billedDate.Unix(),
		Unit:                  "tidb_compute",
		Cost:                  10,
		Labels:                entity.Labels{"project": "proj1"},
		InvoiceID:             int(invoice.ID),
		Status:                entity.UserUnitCostStatus_DONE,
		ConsumedSource:        entity.ConsumedSource_Invoice,
		CostPrice:             decimal.NewFromInt(10),
		RawPrice:              decimal.NewFromInt(10),
	}
	err = db.GetDB().Create(&unitCost).Error
	assert.NoError(err)

	path := fmt.Sprintf("/invoicedBillsDetails/%s/export?accessOrgId=%d", sentDate.Format("2006-01"), AlicloudManagerOrgID)
	w := tests.PerformRequest(suite.router, "GET", path, nil)

	assert.Equal(http.StatusOK, w.Code)

	// Read and validate CSV content
	assert.Equal(`Billing Period,03/01/23-03/31/23
Organization Name,
Organization ID,2

Billed Date,Invoiced Date,Payment Code,Customer ID,Customer Name,Service,Project ID,Project Name,Cluster ID,Cluster Name,Running Total,Discounts,Credits,Total Cost
2023-02-01,2023-03-15,1223,3,,Default,,proj1,,,10,0,0,10
`, string(w.Body.Bytes()))
}
