package privatelinkservicemigration

import (
	"context"

	"github.com/samber/lo"
	"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/privatelinkservicemigration"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func NewRepo(dbProvider db.Provider) privatelinkservicemigration.Repo {
	return &repo{
		dbProvider: dbProvider,
	}
}

type repo struct {
	dbProvider db.Provider
}

func (r *repo) Create(ctx context.Context, migration *entity.PrivateLinkServiceMigration) (*entity.PrivateLinkServiceMigration, error) {
	wdb := r.dbProvider.WriteDB(ctx)

	schemaMigration := privatelinkServiceMigrationEnToModel(migration)

	// First create the parent migration record without the child records
	if err := wdb.Omit("MigrationTidbGroups").Create(&schemaMigration).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to create private link service migration")
	}

	// Now create the child records with the proper migration_id
	for _, mtg := range schemaMigration.MigrationTidbGroups {
		mtg.MigrationID = uint64(schemaMigration.ID)
		if err := wdb.Create(mtg).Error; err != nil {
			return nil, bizerr.WrapGormError(err, "failed to create migration tidb group")
		}
	}

	return privatelinkServiceMigrationModelToEn(schemaMigration), nil
}

func (r *repo) Save(ctx context.Context, migration *entity.PrivateLinkServiceMigration) (*entity.PrivateLinkServiceMigration, error) {
	wdb := r.dbProvider.WriteDB(ctx)

	schemaMigration := privatelinkServiceMigrationEnToModel(migration)
	if err := wdb.Omit("MigrationTidbGroups").Omit("created_at").Save(&schemaMigration).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to save private link service migration")
	}
	for _, mtg := range schemaMigration.MigrationTidbGroups {
		mtg.MigrationID = uint64(schemaMigration.ID)
		if err := wdb.Save(mtg).Error; err != nil {
			return nil, bizerr.WrapGormError(err, "failed to save migration tidb group")
		}
	}

	return privatelinkServiceMigrationModelToEn(schemaMigration), nil
}

func (r *repo) UpdateState(
	ctx context.Context,
	id uint64,
	state globaldedicatedv1.PrivateLinkServiceMigration_State,
	message string,
) (*entity.PrivateLinkServiceMigration, error) {
	wdb := r.dbProvider.WriteDB(ctx)

	if err := wdb.Model(&schema.PrivateLinkServiceMigration{}).
		Where("id = ?", id).
		Update("state", state.String()).
		Update("message", message).
		Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to update private link service migration state")
	}

	return r.Get(ctx, id)
}

func (r *repo) Get(ctx context.Context, id uint64) (*entity.PrivateLinkServiceMigration, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	var schemaMigration schema.PrivateLinkServiceMigration
	if err := rdb.Preload("MigrationTidbGroups").Where("id = ?", id).First(&schemaMigration).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get private link service migration")
	}

	return privatelinkServiceMigrationModelToEn(&schemaMigration), nil
}

func (r *repo) List(ctx context.Context, filter *privatelinkservicemigration.Filter) ([]*entity.PrivateLinkServiceMigration, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	rdb = rdb.Model(&schema.PrivateLinkServiceMigration{}).Preload("MigrationTidbGroups")
	if filter.OrganizationID != 0 {
		rdb = rdb.Where("organization_id = ?", filter.OrganizationID)
	}
	if filter.ProjectID != 0 {
		rdb = rdb.Where("project_id = ?", filter.ProjectID)
	}
	if filter.SourceClusterID != 0 {
		rdb = rdb.Where("source_cluster_id = ?", filter.SourceClusterID)
	}
	if filter.TargetClusterID != 0 {
		rdb = rdb.Where("target_cluster_id = ?", filter.TargetClusterID)
	}

	var schemaMigrationList []*schema.PrivateLinkServiceMigration
	if err := rdb.Preload("MigrationTidbGroups").Find(&schemaMigrationList).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list private link service migration")
	}

	if filter.SourceTidbGroupID != 0 {
		schemaMigrationList = lo.Filter(schemaMigrationList, func(each *schema.PrivateLinkServiceMigration, _ int) bool {
			return lo.SomeBy(each.MigrationTidbGroups, func(mtg *schema.MigrationTidbGroup) bool {
				return mtg.SourceTidbGroupID == filter.SourceTidbGroupID
			})
		})
	}
	if filter.TargetTidbGroupID != 0 {
		schemaMigrationList = lo.Filter(schemaMigrationList, func(each *schema.PrivateLinkServiceMigration, _ int) bool {
			return lo.SomeBy(each.MigrationTidbGroups, func(mtg *schema.MigrationTidbGroup) bool {
				return mtg.TargetTidbGroupID == filter.TargetTidbGroupID
			})
		})
	}

	entities := make([]*entity.PrivateLinkServiceMigration, 0, len(schemaMigrationList))
	for _, schemaMigration := range schemaMigrationList {
		entities = append(entities, privatelinkServiceMigrationModelToEn(schemaMigration))
	}

	return entities, nil
}

func (r *repo) UpdateMigrationTidbGroup(
	ctx context.Context,
	migrationId uint64,
	migrationTidbGroup *entity.MigrationTidbGroup,
) (*entity.MigrationTidbGroup, error) {
	wdb := r.dbProvider.WriteDB(ctx)

	schemaMigrationTidbGroup := migrationTidbGroupEnToModel(migrationId, migrationTidbGroup)
	if err := wdb.Model(&schema.MigrationTidbGroup{}).Where("migration_id = ? AND source_tidb_group_id = ? AND target_tidb_group_id = ?",
		schemaMigrationTidbGroup.MigrationID,
		schemaMigrationTidbGroup.SourceTidbGroupID,
		schemaMigrationTidbGroup.TargetTidbGroupID).Omit("created_at").Updates(schemaMigrationTidbGroup).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to update migration tidb group")
	}

	return migrationTidbGroupModelToEn(schemaMigrationTidbGroup), nil
}

func (r *repo) GetMigrationTidbGroup(
	ctx context.Context,
	migrationId uint64,
	sourceTidbGroupId, targetTidbGroupId uint64,
) (*entity.MigrationTidbGroup, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	var schemaMigrationTidbGroup schema.MigrationTidbGroup
	if err := rdb.Where("migration_id = ? AND source_tidb_group_id = ? AND target_tidb_group_id = ?", migrationId, sourceTidbGroupId, targetTidbGroupId).
		First(&schemaMigrationTidbGroup).
		Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get migration tidb group")
	}
	return migrationTidbGroupModelToEn(&schemaMigrationTidbGroup), nil
}

func (r *repo) ListMigrationTidbGroups(
	ctx context.Context,
	filter *privatelinkservicemigration.MigrationTidbGroupFilter,
) ([]*entity.MigrationTidbGroup, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	var schemaMigrationTidbGroupList []*schema.MigrationTidbGroup
	rdb = rdb.Model(&schema.MigrationTidbGroup{})

	if len(filter.SourceTidbGroupIDs) > 0 {
		rdb = rdb.Where("source_tidb_group_id IN ?", filter.SourceTidbGroupIDs)
	}
	if len(filter.TargetTidbGroupIDs) > 0 {
		rdb = rdb.Where("target_tidb_group_id IN ?", filter.TargetTidbGroupIDs)
	}
	if len(filter.States) > 0 {
		rdb = rdb.Where("state IN ?", lo.Map(filter.States, func(each globaldedicatedv1.PrivateLinkServiceMigration_State, _ int) string {
			return each.String()
		}))
	}
	if err := rdb.Find(&schemaMigrationTidbGroupList).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list migration tidb groups")
	}

	entities := make([]*entity.MigrationTidbGroup, 0, len(schemaMigrationTidbGroupList))
	for _, schemaMigrationTidbGroup := range schemaMigrationTidbGroupList {
		entities = append(entities, migrationTidbGroupModelToEn(schemaMigrationTidbGroup))
	}

	return entities, nil
}
