package tidbgroup

import (
	"context"
	"database/sql"
	"errors"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/dataplane"
	"github.com/tidbcloud/cluster-service-ng/pkg/dataplanemgr"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"gorm.io/gorm"
)

type tidbGroupRepoImp struct {
	provider         db.Provider
	dataplaneManager dataplane.Manager
	dataplaneClient  dataplanemgr.Client
}

func NewTiDBGroupRepo(provider db.Provider, dataplaneManager dataplane.Manager, dataplaneClient dataplanemgr.Client) repo.TiDBGroupRepo {
	return &tidbGroupRepoImp{
		provider:         provider,
		dataplaneManager: dataplaneManager,
		dataplaneClient:  dataplaneClient,
	}
}

func (r *tidbGroupRepoImp) Save(ctx context.Context, tg *entity.TiDBGroup) error {

	if tg == nil {
		return status.Errorf(codes.InvalidArgument, "TiDBGroup cannot be nil")
	}
	wdb := r.provider.WriteDB(ctx)

	tgModel, endPointModel, err := r.cvtTidbGroupToModel(tg)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to convert TiDBGroup to model: %v", err)
	}

	// not found, then set the max template version
	// if found, then set the existing template version
	var existingTiDBGroup schema.TidbGroup
	// Check if the TiDBGroup already exists
	if err := wdb.Where("id = ?", tgModel.ID).First(&existingTiDBGroup).Error; err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return status.Errorf(codes.Internal, "failed to check existing TiDBGroup: %v", err)
		}
		// If not found, use the max template version from existing groups
		var maxTemplateVersion sql.NullInt64
		if err := wdb.Model(&schema.TidbGroup{}).Select("MAX(template_version)").Scan(&maxTemplateVersion).Error; err != nil {
			return status.Errorf(codes.Internal, "failed to get max template version: %v", err)
		}
		if maxTemplateVersion.Valid {
			tgModel.TemplateVersion = maxTemplateVersion.Int64
		} else {
			// If no existing groups, set to 1
			tgModel.TemplateVersion = 1
		}

	} else {
		// If found, update the existing TiDBGroup
		tgModel.TemplateVersion = existingTiDBGroup.TemplateVersion
		// Preserve the original created_at value to avoid zero value issues
		tgModel.CreatedAt = existingTiDBGroup.CreatedAt
		tilog.Warn(ctx, "TiDBGroup already exists, updating existing record",
			zap.Any("tgModel", tgModel),
			zap.Any("existingTiDBGroup", existingTiDBGroup),
		)
	}

	if err := wdb.Model(&schema.TidbGroup{}).Where("id = ?", tgModel.ID).Save(tgModel).Error; err != nil {
		return status.Errorf(codes.Internal, "failed to save TiDBGroup: %v", err)
	}

	result := wdb.Model(&schema.TidbGroupEndpoint{}).Where("tidb_group_id = ?", tgModel.ID).Updates(endPointModel)
	if result.Error != nil {
		return status.Errorf(codes.Internal, "failed to save TiDBGroup endpoint: %v", result.Error)
	}
	if result.RowsAffected == 0 {
		// If no rows were updated, it means the endpoint does not exist, so create it
		if err := wdb.Create(endPointModel).Error; err != nil {
			return status.Errorf(codes.Internal, "failed to create TiDBGroup endpoint: %v", err)
		}
	}

	return nil
}

func (r *tidbGroupRepoImp) Load(ctx context.Context, id string, deleted bool) (*entity.TiDBGroup, error) {

	if id == "" {
		return nil, status.Errorf(codes.InvalidArgument, "TiDBGroup id cannot be empty")
	}

	rdb := r.provider.ReadDB(ctx)

	var tcModel schema.TidbGroup
	if deleted {
		rdb = rdb.Unscoped() // Use Unscoped to include soft-deleted records
		// When loading deleted records, also need to preload deleted Endpoint
		rdb = rdb.Preload("Endpoint", func(db *gorm.DB) *gorm.DB {
			return db.Unscoped()
		})
	} else {
		rdb = rdb.Preload("Endpoint")
	}

	if err := rdb.Where("id = ?", id).First(&tcModel).Error; err != nil {
		// If record not found, return NotFound error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Errorf(codes.NotFound, "TiDBGroup with id %s not found", id)
		}
		return nil, status.Errorf(codes.Internal, "failed to load TiDBGroup: %v", err)
	}

	tgEntity, err := r.cvtModelToTiDBGroup(ctx, &tcModel)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to convert TiDBGroup model to entity: %v", err)
	}

	return tgEntity, nil
}

func (r *tidbGroupRepoImp) Delete(ctx context.Context, id string) error {

	if id == "" {
		return status.Errorf(codes.InvalidArgument, "TiDBGroup id cannot be empty")
	}

	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("id = ?", id).Delete(&schema.TidbGroup{}).Error; err != nil {
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return status.Errorf(codes.Internal, "failed to delete TiDBGroup: %v", err)
			}
		}
		if err := tx.Where("tidb_group_id = ?", id).Delete(&schema.TidbGroupEndpoint{}).Error; err != nil {
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return status.Errorf(codes.Internal, "failed to delete TidbGroup endpoint: %v", err)
			}
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}

func (r *tidbGroupRepoImp) List(ctx context.Context, sharedPoolIds, tenantIds, clusterIds, keyspaces []string, deleted bool) ([]*entity.TiDBGroup, error) {

	rdb := r.provider.ReadDB(ctx)

	if len(sharedPoolIds) == 0 && len(tenantIds) == 0 && len(clusterIds) == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "at least one filter must be provided")
	}

	if deleted {
		rdb = rdb.Unscoped() // Use Unscoped to include soft-deleted records
	}

	query := rdb.Model(&schema.TidbGroup{})
	// When loading deleted records, also need to preload deleted Endpoint
	if deleted {
		query = query.Preload("Endpoint", func(db *gorm.DB) *gorm.DB {
			return db.Unscoped()
		})
	} else {
		query = query.Preload("Endpoint")
	}

	if len(sharedPoolIds) > 0 {
		query = query.Where("shared_pool_id IN ?", sharedPoolIds)
	}
	if len(tenantIds) > 0 {
		query = query.Where("tenant_id IN ?", tenantIds)
	}

	clusterIds = append(clusterIds, keyspaces...)
	if len(clusterIds) > 0 {
		query = query.Where("cluster_id IN ?", clusterIds)
	}

	var tgModels []*schema.TidbGroup
	if err := query.Find(&tgModels).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Errorf(codes.NotFound, "no TiDBGroups found with the provided filters")
		}
		return nil, status.Errorf(codes.Internal, "failed to list TiDBGroups: %v", err)
	}
	var tgEntities []*entity.TiDBGroup
	for _, tgModel := range tgModels {

		tcEntity, err := r.cvtModelToTiDBGroup(ctx, tgModel)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to convert TiDBGroup model to entity: %v", err)
		}
		tgEntities = append(tgEntities, tcEntity)
	}

	return tgEntities, nil
}
