package networktask

import (
	"context"
	"fmt"
	"os"
	"testing"
	"time"

	"errors"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
	"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"
	apimodels "github.com/tidbcloud/metering/pkg/common/utils/apigateway/models"
	"github.com/tidbcloud/metering/pkg/networkmetering"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func TestNetWorkMeteringFromAzure(t *testing.T) {
	//12-03 00:00:00 UTC
	ts := int64(1733184000)
	conf := config.ServiceConfig{
		Vendor: constants.VendorAzure,
		Env:    constants.EnvProd,
		Secret: &config.Secret{
			ApiGatewayKey: "test",
		},
		TrafficMeteringConfig: &config.TrafficMeteringConfig{
			Provider: config.TrafficMeteringProvider{
				Azure: &config.TrafficMeteringProviderAzureConfig{
					BillingProvider: config.BillingProviderAzureConfig{
						StorageAccountName: "https://costmetetringstoragedev.blob.core.windows.net/",
						ContainerName:      "dataplane-cost-storage",
						Perfix:             []string{"dataplane-cost-storage/test-cost"},
					},
					StoreProvider: config.StoreProviderAzureConfig{
						StorageAccountName: "https://costmetetringstoragedev.blob.core.windows.net/",
						ContainerName:      "dataplane-cost-storage",
						Perfix:             "dataplane-cost-storage/test-cost/billingresult",
					},
				},
			},
			CachePath: "cachetest",
		},
	}
	cli := NewAzureBlobStorage(conf.TrafficMeteringConfig.Provider.Azure.StoreProvider.StorageAccountName, conf.TrafficMeteringConfig.Provider.Azure.StoreProvider.ContainerName)
	// clean test data
	cleanTestDataAzure(cli)
	defer cleanTestDataAzure(cli)
	//prepare test data
	prepareTestDataAzure(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 = compareResult(t, "./cachetest/resultcache/20241203.db", "../../../testdata/azurebillingfile/20241203_normal.db")
	assert.Nil(t, err)
	assert.True(t, ok)
}
func TestNetWorkMeteringFromAzureRerun(t *testing.T) {
	//12-03 00:00:00 UTC
	ts := int64(1733184000)
	conf := config.ServiceConfig{
		Vendor: constants.VendorAzure,
		Env:    constants.EnvProd,
		Secret: &config.Secret{
			ApiGatewayKey: "test",
		},
		TrafficMeteringConfig: &config.TrafficMeteringConfig{
			Provider: config.TrafficMeteringProvider{
				Azure: &config.TrafficMeteringProviderAzureConfig{
					BillingProvider: config.BillingProviderAzureConfig{
						StorageAccountName: "https://costmetetringstoragedev.blob.core.windows.net/",
						ContainerName:      "dataplane-cost-storage",
						Perfix:             []string{"dataplane-cost-storage/test-cost"},
					},
					StoreProvider: config.StoreProviderAzureConfig{
						StorageAccountName: "https://costmetetringstoragedev.blob.core.windows.net/",
						ContainerName:      "dataplane-cost-storage",
						Perfix:             "dataplane-cost-storage/test-cost/rerunbillingresult",
					},
				},
			},
			CachePath: "cachetest",
		},
	}
	cli := NewAzureBlobStorage(conf.TrafficMeteringConfig.Provider.Azure.StoreProvider.StorageAccountName, conf.TrafficMeteringConfig.Provider.Azure.StoreProvider.ContainerName)
	// clean test data
	cleanTestDataAzure(cli)
	defer cleanTestDataAzure(cli)
	//prepare test data
	prepareTestDataAzure(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 = compareResult(t, "./cachetest/resultcache/20241203.db", "../../../testdata/azurebillingfile/20241203_normal.db")
	assert.Nil(t, err)
	assert.True(t, ok)
}
func prepareTestDataAzure(cli *AzureBlobStorage) {
	//upload manifest_ft.json and ft_test_0_0001.csv.gz to blob storage
	cli.Upload(context.Background(), "dataplane-cost-storage/test-cost/20241201-20241231/e3ad27c2-9009-489b-8ff2-4990da9c5e30/manifest.json", "../../../testdata/azurebillingfile/manifest_ft.json")
	cli.Upload(context.Background(), "dataplane-cost-storage/test-cost/20241201-20241231/e3ad27c2-9009-489b-8ff2-4990da9c5e30/ft_test_0_0001.csv.gz", "../../../testdata/azurebillingfile/ft_test_0_0001.csv.gz")
	cli.Upload(context.Background(), "dataplane-cost-storage/test-cost/rerunbillingresult/20241203/20241203.db", "../../../testdata/azurebillingfile/20241203_rerun.db")
}

func cleanTestDataAzure(cli *AzureBlobStorage) {
	//delete manifest_ft.json and ft_test_0_0001.csv.gz from blob storage
	cli.Delete(context.Background(), "dataplane-cost-storage/test-cost/20241201-20241231/e3ad27c2-9009-489b-8ff2-4990da9c5e30/manifest.json")
	cli.Delete(context.Background(), "dataplane-cost-storage/test-cost/20241201-20241231/e3ad27c2-9009-489b-8ff2-4990da9c5e30/ft_test_0_0001.csv.gz")
	//delete metering result
	cli.Delete(context.Background(), "dataplane-cost-storage/test-cost/rerunbillingresult/20241203/20241203.db")
	cli.Delete(context.Background(), "dataplane-cost-storage/test-cost/billingresult/20241203/20241203.db")
}

type AzureBlobStorage struct {
	account       string
	containerName string
	cli           *azblob.Client
}

func NewAzureBlobStorage(account string, containerName string) *AzureBlobStorage {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}
	cli, err := azblob.NewClient(account, cred, nil)
	if err != nil {
		panic(err)
	}
	return &AzureBlobStorage{
		account:       account,
		containerName: containerName,
		cli:           cli,
	}
}

func (a *AzureBlobStorage) Upload(ctx context.Context, blobName 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()
	_, err = a.cli.UploadFile(ctx, a.containerName, blobName, file, nil)
	return err
}

func (a *AzureBlobStorage) Delete(ctx context.Context, blobName string) error {
	_, err := a.cli.DeleteBlob(ctx, a.containerName, blobName, nil)
	if err != nil {
		//ignore not found error "BlobNotFound"
		var responseError *azcore.ResponseError
		if errors.As(err, &responseError) {
			if responseError.ErrorCode == "BlobNotFound" {
				fmt.Printf("Blob '%s' not found. No action needed.\n", blobName)
				return nil
			}
		}
		return err
	}
	return nil
}

type mockAPIGateway struct {
}

func (m *mockAPIGateway) CountBillableClusters(ctx context.Context, organizationId string, projectid string, startTS time.Time, endTS time.Time) (int64, error) {
	//always return 1, means there is always billable
	return 1, nil
}

func (m *mockAPIGateway) ShowComponentNodeInfo(ctx context.Context, clusterId string) (*apimodels.V1ShowComponentNodeInfoResponse, error) {
	return nil, nil
}

func compareResult(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.Equal(t, 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.Equal(t, totalCost, totalCostExpected)
	return true, nil

}

func loadResult(dbfile string) (*gorm.DB, error) {
	db, err := gorm.Open(sqlite.Open(dbfile), &gorm.Config{})
	if err != nil {
		return nil, err
	}
	err = db.AutoMigrate(&models.BillingMetering{}, &models.BillingReport{})
	if err != nil {
		return nil, err
	}
	return db, nil
}
