package instancemetering

import (
	"context"
	"fmt"

	log "github.com/sirupsen/logrus"
	"github.com/tidbcloud/metering/pkg/errors"

	"github.com/tidbcloud/metering/pkg/common/models"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/logger"
)

type Database struct {
	base *gorm.DB
}

func GetClusterMetaDB(localFile string) (*Database, error) {
	var base *gorm.DB
	// open database and migrate the tables, if any error occurs, break and return the error
	errOptional := errors.OfNullable(nil).BreakIf(func() error {
		var innerErr error
		base, innerErr = gorm.Open(sqlite.Open(localFile+"?_busy_timeout=5000&_txlock=immediate"), &gorm.Config{
			Logger: logger.Default.LogMode(logger.Error),
		})
		return innerErr
	}).BreakIf(func() error {
		return base.AutoMigrate(models.ClusterMeta{})
	}).BreakIf(func() error {
		return base.AutoMigrate(models.ComponentMeta{})
	})

	if err := errOptional.Present(); err != nil {
		err = errors.Wrap(errors.GormDBError, fmt.Sprintf("GetClusterMetaDB %s failed", localFile), err)
		log.Errorf("%s", err.Error())
		return nil, err
	}

	return &Database{base: base}, nil
}

func GetBillingReportDB(localFile string) (*Database, error) {
	var base *gorm.DB
	// open database and migrate the tables, if any error occurs, break and return the error
	errOptional := errors.OfNullable(nil).BreakIf(func() error {
		var innerErr error
		base, innerErr = gorm.Open(sqlite.Open(localFile+"?_busy_timeout=5000&_txlock=immediate"), &gorm.Config{
			Logger: logger.Default.LogMode(logger.Error),
		})
		return innerErr
	}).BreakIf(func() error {
		return base.AutoMigrate(models.BillingReport{}, models.BillingReportV2{})
	})

	if err := errOptional.Present(); err != nil {
		err = errors.Wrap(errors.GormDBError, fmt.Sprintf("GetBillingReportDB %s failed", localFile), err)
		log.Errorf("%s", err.Error())
		return nil, err
	}

	return &Database{base: base}, nil
}

func (d Database) SaveClusterMeta(ctx context.Context, clusterMetas []models.ClusterMeta) error {
	return d.base.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(clusterMetas, len(clusterMetas)).Error
}

func (d Database) ListClusterMeta(ctx context.Context) ([]*models.ClusterMeta, error) {
	list := make([]*models.ClusterMeta, 0)

	err := d.base.Model(&models.ClusterMeta{}).Preload("Components").Find(&list).Error

	return list, err
}

func (d Database) SaveBillingReports(ctx context.Context, reports []*models.BillingReport) error {
	return d.base.CreateInBatches(reports, len(reports)).Error
}

func (d Database) SaveBillingReportsV2(ctx context.Context, reports []*models.BillingReportV2) error {
	return d.base.CreateInBatches(reports, len(reports)).Error
}

func (d Database) ListBillingReports(ctx context.Context) ([]*models.BillingReport, error) {
	list := make([]*models.BillingReport, 0)

	err := d.base.Model(&models.BillingReport{}).Find(&list).Error

	return list, err
}

func (d Database) ListBillingReportsV2(ctx context.Context) ([]*models.BillingReportV2, error) {
	list := make([]*models.BillingReportV2, 0)
	err := d.base.Model(&models.BillingReportV2{}).Find(&list).Error

	return list, err
}

func (d Database) Close(ctx context.Context) {
	// close the database connection use sql.DB
	db, err := d.base.DB()
	if err != nil {
		log.Errorf("close db failed %s", err.Error())
	}
	db.Close()
}
