package networktask

import (
	"context"
	"errors"
	"fmt"
	"io"
	"math"
	"os"
	"testing"

	"cloud.google.com/go/storage"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/metering/pkg/common/config"
	"github.com/tidbcloud/metering/pkg/common/constants"
	"github.com/tidbcloud/metering/pkg/common/models"
	"github.com/tidbcloud/metering/pkg/networkmetering"
	"go.uber.org/zap"
)

func TestNetWorkMeteringFromGCP(t *testing.T) {
	//12-03 00:00:00 UTC
	ts := int64(1733184000)
	conf := config.ServiceConfig{
		Vendor: constants.VendorGCP,
		Env:    constants.EnvProd,
		Secret: &config.Secret{
			ApiGatewayKey: "test",
		},
		TrafficMeteringConfig: &config.TrafficMeteringConfig{
			Provider: config.TrafficMeteringProvider{
				GCP: &config.TrafficMeteringProviderGCPConfig{
					BillingProvider: config.BillingProviderGCPConfig{
						ProjectId:        "pingcap-gardener",
						Location:         "US",
						MeteringProjects: []string{"tidbcloud-prod-000", "dbaas-prod"},
					},
					StoreProvider: config.StoreProviderGCPConfig{
						ProjectId:  "pingcap-gardener",
						BucketName: "costmetetringstoragedev",
						Perfix:     "dataplane-cost-storage/test-cost/billingresult",
					},
				},
			},
			CachePath: "cachetest",
		},
	}
	cli := NewGCPGCS("pingcap-gardener", "costmetetringstoragedev")
	cleanTestDataGCP(cli)
	defer cleanTestDataGCP(cli)
	logger, _ := zap.NewDevelopment()
	metering, err := networkmetering.NewNetworkMetering(&conf, logger)
	assert.Nil(t, err)
	//set mock gateway because we can't control the api gateway in this test
	metering.SetGateway(&mockAPIGateway{})
	path, ok, err := metering.Metering(context.Background(), ts)
	assert.Nil(t, err)
	assert.True(t, ok)
	assert.Equal(t, "dataplane-cost-storage/test-cost/billingresult/20241203/20241203.db", path)
	//compare the result with the expected result
	ok, err = compareResultGCP(t, "./cachetest/resultcache/20241203.db", "../../../testdata/gcpbillingfile/20241203_normal.db")
	assert.Nil(t, err)
	assert.True(t, ok)
}

func TestNetWorkMeteringFromGCPRerun(t *testing.T) {
	//12-03 00:00:00 UTC
	ts := int64(1733184000)
	conf := config.ServiceConfig{
		Vendor: constants.VendorGCP,
		Env:    constants.EnvProd,
		Secret: &config.Secret{
			ApiGatewayKey: "test",
		},
		TrafficMeteringConfig: &config.TrafficMeteringConfig{
			Provider: config.TrafficMeteringProvider{
				GCP: &config.TrafficMeteringProviderGCPConfig{
					BillingProvider: config.BillingProviderGCPConfig{
						ProjectId:        "pingcap-gardener",
						Location:         "US",
						MeteringProjects: []string{"tidbcloud-prod-000", "dbaas-prod"},
					},
					StoreProvider: config.StoreProviderGCPConfig{
						ProjectId:  "pingcap-gardener",
						BucketName: "costmetetringstoragedev",
						Perfix:     "dataplane-cost-storage/test-cost/rerunbillingresult",
					},
				},
			},
			CachePath: "cachetest",
		},
	}
	cli := NewGCPGCS("pingcap-gardener", "costmetetringstoragedev")
	cleanTestDataGCP(cli)
	prepareTestDataGCP(cli)
	defer cleanTestDataGCP(cli)
	logger, _ := zap.NewDevelopment()
	metering, err := networkmetering.NewNetworkMetering(&conf, logger)
	assert.Nil(t, err)
	//set mock gateway because we can't control the api gateway in this test
	metering.SetGateway(&mockAPIGateway{})
	path, ok, err := metering.Metering(context.Background(), ts)
	assert.Nil(t, err)
	assert.True(t, ok)
	assert.Equal(t, "dataplane-cost-storage/test-cost/rerunbillingresult/20241203/20241203.db", path)
	//compare the result with the expected result
	ok, err = compareResultGCP(t, "./cachetest/resultcache/20241203.db", "../../../testdata/gcpbillingfile/20241203_normal.db")
	assert.Nil(t, err)
	assert.True(t, ok)
}

func prepareTestDataGCP(cli *GCPGCS) {
	//upload the rerun result to gcs
	cli.Upload(context.Background(), "dataplane-cost-storage/test-cost/rerunbillingresult/20241203/20241203.db", "../../../testdata/gcpbillingfile/20241203_rerun.db")
}

func cleanTestDataGCP(cli *GCPGCS) {
	//delete metering result
	cli.Delete(context.Background(), "dataplane-cost-storage/test-cost/billingresult/20241203/20241203.db")
	cli.Delete(context.Background(), "dataplane-cost-storage/test-cost/rerunbillingresult/20241203/20241203.db")
}

func compareResultGCP(t *testing.T, Resultpath string, expectedResultPath string) (bool, error) {
	db, err := loadResult(Resultpath)
	if err != nil {
		panic(err)
	}
	expectedDb, err := loadResult(expectedResultPath)
	if err != nil {
		panic(err)
	}
	var billingres []*models.BillingMetering
	var billingresExpected []*models.BillingMetering
	var reportres []*models.BillingReport
	var reportresExpected []*models.BillingReport
	err = db.Find(&billingres).Error
	if err != nil {
		return false, err
	}
	err = expectedDb.Find(&billingresExpected).Error
	if err != nil {
		return false, err
	}
	err = db.Find(&reportres).Error
	if err != nil {
		return false, err
	}
	err = expectedDb.Find(&reportresExpected).Error
	if err != nil {
		return false, err
	}
	//compare billingres and billingresExpected by tenantid total cost
	tenantCostMap := make(map[string]int64)
	for _, billing := range billingres {
		tenantCostMap[billing.TenantID] += billing.Cost
	}
	tenantCostMapExpected := make(map[string]int64)
	for _, billingExpected := range billingresExpected {
		tenantCostMapExpected[billingExpected.TenantID] += billingExpected.Cost
	}
	for tenant, cost := range tenantCostMap {
		assert.True(t, math.Abs(float64(cost-tenantCostMapExpected[tenant])) <= 100,
			"tenant %s cost %d expected %d", tenant, cost, tenantCostMapExpected[tenant])
	}

	//compare reportres and reportresExpected by total cost
	var totalCost int64
	var totalCostExpected int64
	for _, report := range reportres {
		totalCost += report.Cost
	}
	for _, reportExpected := range reportresExpected {
		totalCostExpected += reportExpected.Cost
	}
	assert.True(t, math.Abs(float64(totalCost-totalCostExpected)) <= 100, "total cost %d expected %d", totalCost, totalCostExpected)
	return true, nil

}

type GCPGCS struct {
	projectId  string
	bucketName string
	cli        *storage.Client
}

func NewGCPGCS(projectId string, bucketName string) *GCPGCS {
	cli, err := storage.NewClient(context.Background())
	if err != nil {
		panic(err)
	}
	return &GCPGCS{
		projectId:  projectId,
		bucketName: bucketName,
		cli:        cli,
	}
}

func (g *GCPGCS) Delete(ctx context.Context, objname string) error {
	obj := g.cli.Bucket(g.bucketName).Object(objname)
	if err := obj.Delete(ctx); err != nil {
		if errors.Is(err, storage.ErrObjectNotExist) {
			fmt.Printf("object '%s' not found. No action needed.\n", objname)
			return nil
		}
		return err
	}
	return nil
}

func (g *GCPGCS) Upload(ctx context.Context, objname string, fpath string) error {
	file, err := os.Open(fpath)
	if err != nil {
		return fmt.Errorf("failed to open file %s: %w", fpath, err)
	}
	defer file.Close()
	p, err := io.ReadAll(file)
	if err != nil {
		return fmt.Errorf("failed to read file %s: %w", fpath, err)
	}
	wc := g.cli.Bucket(g.bucketName).Object(objname).NewWriter(ctx)
	_, err = wc.Write(p)
	if err != nil {
		fmt.Printf("failed to upload file %s to gcs: %s\n", fpath, err.Error())
		return err
	}
	err = wc.Close()
	if err != nil {
		fmt.Printf("failed to close writer: %s\n", err.Error())
		return err
	}

	return nil
}
