package cluster

import (
	"context"

	"github.com/tidbcloud/pkgv2/db"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type clusterSnapshotRepoImp struct {
	dbProvider db.Provider
}

func NewClusterSnapshotRepo(dbProvider db.Provider) cluster.ComponentSnapshotRepo {
	return clusterSnapshotRepoImp{dbProvider: dbProvider}
}

func (c clusterSnapshotRepoImp) Load(ctx context.Context, id uint64) (*entity.ClusterSnapshot, bizerr.BizError) {
	rdb := c.dbProvider.WriteDB(ctx).Unscoped() // Include deleted records by default.

	snapshot := &schema.ClusterSnapshot{}
	if err := rdb.Model(&schema.ClusterSnapshot{}).
		Joins("Cluster").
		Joins("Cluster.Region").
		Take(snapshot, id).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load cluster snapshot,id=%d", id)
	}

	snapshotEn, err := clusterSnapshotModelToEn(snapshot)
	if err != nil {
		return nil, bizerr.WrapError(err, "convert to entity")
	}

	return snapshotEn, nil
}

func (c clusterSnapshotRepoImp) LoadMany(
	ctx context.Context,
	ids []uint64,
) ([]*entity.ClusterSnapshot, bizerr.BizError) {
	if len(ids) == 0 {
		return nil, nil
	}

	rdb := c.dbProvider.WriteDB(ctx).Unscoped() // Include deleted records by default.

	var snapshots []*schema.ClusterSnapshot
	if err := rdb.Model(&schema.ClusterSnapshot{}).
		Joins("Cluster").
		Joins("Cluster.Region").
		Where("cluster_snapshots.id in (?)", ids).Find(&snapshots).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load cluster snapshots,ids=%v", ids)
	}

	res := make([]*entity.ClusterSnapshot, len(snapshots))
	for _, s := range snapshots {
		snapshotEn, err := clusterSnapshotModelToEn(s)
		if err != nil {
			return nil, bizerr.WrapError(err, "convert to entity")
		}
		res = append(res, snapshotEn)
	}

	return res, nil
}

func (c clusterSnapshotRepoImp) Save(ctx context.Context, en *entity.ClusterSnapshot) bizerr.BizError {
	wdb := c.dbProvider.WriteDB(ctx)

	snapshot := clusterSnapshotEnToModel(en)

	if err := wdb.Omit("Cluster").Save(&snapshot).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to save cluster snapshot")
	}

	en.Id = uint64(snapshot.ID)

	return nil
}

func (c clusterSnapshotRepoImp) Query(
	ctx context.Context,
	filter *cluster.ComponentSnapshotFilter,
	_ *globaldedicatedv1.Pager,
	_ []*globaldedicatedv1.Sorter,
) ([]*entity.ClusterSnapshot, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	var snapshots []*schema.ClusterSnapshot

	sql := rdb.Model(&schema.ClusterSnapshot{}).
		Joins("Cluster").
		Joins("Cluster.Region").
		Joins("Cluster.Provider").
		Unscoped() // Include deleted records by default.

	if len(filter.TenantIDs) != 0 {
		sql = sql.Where("cluster_snapshots.tenant_id in (?)", filter.TenantIDs)
	}
	if len(filter.ProjectIDs) != 0 {
		sql = sql.Where("cluster_snapshots.project_id in (?)", filter.ProjectIDs)
	}
	if len(filter.ClusterIDs) != 0 {
		sql = sql.Where("cluster_snapshots.cluster_id in (?)", filter.ClusterIDs)
	}
	if len(filter.SnapshotsIDs) != 0 {
		sql = sql.Where("cluster_snapshots.id in (?)", filter.SnapshotsIDs)
	}

	if err := sql.Find(&snapshots).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find cluster snapshot")
	}

	res := make([]*entity.ClusterSnapshot, 0, len(snapshots))
	for _, s := range snapshots {
		snapshotEn, err := clusterSnapshotModelToEn(s)
		if err != nil {
			return nil, bizerr.WrapError(err, "convert to entity")
		}
		res = append(res, snapshotEn)
	}

	return res, nil
}
