package main

import (
	"context"
	"fmt"
	"path/filepath"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/glebarez/sqlite"
	"github.com/tidbcloud/metering/pkg/common/config"
	"github.com/tidbcloud/metering/pkg/common/constants"
	"github.com/tidbcloud/metering/pkg/common/utils"
	"github.com/tidbcloud/metering/pkg/common/utils/storage"
	awsobj "github.com/tidbcloud/metering/pkg/common/utils/storage/aws"
	azblob "github.com/tidbcloud/metering/pkg/common/utils/storage/azure"
	gcpobj "github.com/tidbcloud/metering/pkg/common/utils/storage/gcp"
	"gorm.io/gorm"
)

const (
	sqliteCachePath = "checkcache"
)

// CheckDBStore store the metering check result to sqlite and upload to object storage
type CheckDBStore struct {
	conf        *config.ServiceConfig
	objcli      storage.StorageProvider
	cachepath   string
	cachesqlite map[string]*gorm.DB
	perfix      string
}

func NewCheckDBStore(conf *config.ServiceConfig, perfix string) *CheckDBStore {
	return &CheckDBStore{
		conf:        conf,
		cachesqlite: make(map[string]*gorm.DB),
		perfix:      perfix,
	}
}

func (n *CheckDBStore) Init() (*CheckDBStore, error) {
	abspath, err := utils.EnsureCache(n.conf.TrafficMeteringConfig.CachePath, sqliteCachePath)
	if err != nil {
		return n, err
	}
	n.cachepath = abspath
	switch n.conf.Vendor {
	case constants.VendorAzure:
		{
			err := n.ensureAzureClient()
			if err != nil {
				return n, err
			}
		}
	case constants.VendorAWS:
		{
			err := n.ensureAWSClient()
			if err != nil {
				return n, err
			}
		}
	case constants.VendorGCP:
		{
			err := n.ensureGCPClient()
			if err != nil {
				return n, err
			}
		}
	}
	return n, nil
}

func (n *CheckDBStore) ensureAzureClient() error {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return err
	}
	n.objcli, err = azblob.NewAzureProviderBlob(cred, n.conf.TrafficMeteringConfig.Provider.Azure.StoreProvider.StorageAccountName, n.conf.TrafficMeteringConfig.Provider.Azure.StoreProvider.ContainerName)
	if err != nil {
		return err
	}
	return nil
}

func (n *CheckDBStore) ensureAWSClient() error {

	cfg, err := awsconfig.LoadDefaultConfig(context.TODO())
	if err != nil {
		return fmt.Errorf("unable to get aws config: %v", err)
	}
	cfg.Region = n.conf.TrafficMeteringConfig.Provider.AWS.StoreProvider.Region
	if n.conf.TrafficMeteringConfig.Provider.AWS.StoreProvider.RoleArn != "" {
		n.objcli = awsobj.NewAwsProviderS3(cfg, n.conf.TrafficMeteringConfig.Provider.AWS.StoreProvider.BucketName).AssumeRole(context.TODO(), n.conf.TrafficMeteringConfig.Provider.AWS.StoreProvider.RoleArn)
	} else {
		n.objcli = awsobj.NewAwsProviderS3(cfg, n.conf.TrafficMeteringConfig.Provider.AWS.StoreProvider.BucketName)
	}

	return nil
}
func (n *CheckDBStore) ensureGCPClient() error {
	n.objcli = gcpobj.NewGcpProviderGcs(n.conf.TrafficMeteringConfig.Provider.GCP.StoreProvider.ProjectId, n.conf.TrafficMeteringConfig.Provider.GCP.StoreProvider.BucketName)
	return nil
}

func (n *CheckDBStore) Load(ctx context.Context, timestamp int64) error {
	destFile, date := n.GetCacheFileInTime(timestamp)
	if _, ok := n.cachesqlite[date]; ok {
		return nil
	}

	objpath := filepath.Join(n.perfix, n.BuildObjName(date))
	objects, err := n.objcli.ListObjectByName(ctx, objpath)
	if err != nil {
		return err
	}
	var db *gorm.DB
	if len(objects) == 0 {
		// create a new db file
		db, err = n.ensureSQLite(destFile)
		if err != nil {
			return err
		}
		n.cachesqlite[date] = db
		return nil
	}
	//download blob to cache

	err = utils.EnsurePath(filepath.Dir(destFile))
	if err != nil {
		return err
	}
	err = n.objcli.DownloadObjects(ctx, objects, filepath.Dir(destFile))
	if err != nil {
		return err
	}
	// load db file
	db, err = n.ensureSQLite(destFile)
	if err != nil {
		return err
	}
	n.cachesqlite[date] = db
	return nil
}

func (n *CheckDBStore) Store(ctx context.Context, timestamp int64, res []*CompareResult) error {
	month := utils.GenerateCheckMonth(timestamp)
	db, ok := n.cachesqlite[month]
	if !ok {
		return fmt.Errorf("sqlite db not found for month %s", month)
	}
	for _, v := range res {
		err := db.Create(v).Error
		if err != nil {
			return err
		}
	}
	return nil
}

func (n *CheckDBStore) Upload(ctx context.Context, timestamp int64) (string, error) {
	destFile, date := n.GetCacheFileInTime(timestamp)
	//free db file and upload to remote storage
	delete(n.cachesqlite, date)
	objpath := filepath.Join(n.perfix, n.BuildObjName(date))
	err := n.objcli.UploadObject(ctx, objpath, destFile)
	if err != nil {
		return "", err
	}
	return objpath, nil
}

func (n *CheckDBStore) GetCacheFileInTime(timestamp int64) (string, string) {
	month := utils.GenerateCheckMonth(timestamp)
	name := fmt.Sprintf("%s.db", month)
	return filepath.Join(n.cachepath, name), month
}

// BuildObjName build the object name for the day . eg. 20240801/20240801.db
func (n *CheckDBStore) BuildObjName(day string) string {
	return fmt.Sprintf("%s/%s.db", day, day)
}

func (n *CheckDBStore) ensureSQLite(dbfile string) (*gorm.DB, error) {
	db, err := gorm.Open(sqlite.Open(dbfile), &gorm.Config{})
	if err != nil {
		return nil, err
	}
	err = db.AutoMigrate(&CompareResult{})
	if err != nil {
		return nil, err
	}
	return db, nil
}
