package configbackup

import (
	"context"
	"encoding/json"
	"errors"

	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	repo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/configbackup"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type BackupRepoImpl struct {
	provider db.Provider
}

func NewBackupRepo(provider db.Provider) repo.BackupRepo {
	return &BackupRepoImpl{provider: provider}
}

func (r *BackupRepoImpl) GetByClusterID(ctx context.Context, clusterID uint64) (*entity.ClusterConfigBackup, bizerr.BizError) {
	var model schema.ClusterConfigBackup
	if err := r.provider.ReadDB(ctx).Where("cluster_id = ?", clusterID).First(&model).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "get cluster config backup")
	}
	return backupModelToEntity(&model)
}

func (r *BackupRepoImpl) Upsert(ctx context.Context, backup *entity.ClusterConfigBackup) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)

	model, err := backupEntityToModel(backup)
	if err != nil {
		return err
	}

	return bizerr.WrapGormError(
		wdb.Transaction(func(tx *gorm.DB) error {
			var existing schema.ClusterConfigBackup
			err := tx.Where("cluster_id = ?", backup.ClusterID).First(&existing).Error
			if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}

			if errors.Is(err, gorm.ErrRecordNotFound) {
				return tx.Create(model).Error
			}

			model.ID = existing.ID
			return tx.Model(&existing).Select("*").Omit("created_at").Updates(model).Error
		}),
		"upsert cluster config backup",
	)
}

func (r *BackupRepoImpl) DeleteByClusterID(ctx context.Context, clusterID uint64) bizerr.BizError {
	return bizerr.WrapGormError(
		r.provider.WriteDB(ctx).Unscoped().Where("cluster_id = ?", clusterID).Delete(&schema.ClusterConfigBackup{}).Error,
		"delete cluster config backup",
	)
}

func backupEntityToModel(backup *entity.ClusterConfigBackup) (*schema.ClusterConfigBackup, bizerr.BizError) {
	systemVariables, err := json.Marshal(backup.SystemVariables)
	if err != nil {
		return nil, bizerr.WrapError(err, "marshal system variables")
	}
	pdConfig, err := json.Marshal(backup.PdConfig)
	if err != nil {
		return nil, bizerr.WrapError(err, "marshal pd config")
	}
	pdServerConfig, err := json.Marshal(backup.PdServerConfig)
	if err != nil {
		return nil, bizerr.WrapError(err, "marshal pd server config")
	}
	placementRules, err := json.Marshal(backup.PlacementRules)
	if err != nil {
		return nil, bizerr.WrapError(err, "marshal placement rules")
	}
	storeLimits, err := json.Marshal(backup.StoreLimits)
	if err != nil {
		return nil, bizerr.WrapError(err, "marshal store limits")
	}

	return &schema.ClusterConfigBackup{
		Model: gorm.Model{
			ID: uint(backup.ID),
		},
		ClusterID:           backup.ClusterID,
		SystemVariables:     string(systemVariables),
		PdConfig:            string(pdConfig),
		PdServerConfig:      string(pdServerConfig),
		PlacementRules:      string(placementRules),
		StoreLimits:         string(storeLimits),
		NewCollationEnabled: backup.NewCollationEnabled,
		CapturedAt:          backup.CapturedAt,
	}, nil
}

func backupModelToEntity(model *schema.ClusterConfigBackup) (*entity.ClusterConfigBackup, bizerr.BizError) {
	out := &entity.ClusterConfigBackup{
		ID:                  uint64(model.ID),
		ClusterID:           model.ClusterID,
		NewCollationEnabled: model.NewCollationEnabled,
		CapturedAt:          model.CapturedAt,
	}

	if err := json.Unmarshal([]byte(model.SystemVariables), &out.SystemVariables); err != nil {
		return nil, bizerr.WrapError(err, "unmarshal system variables")
	}
	if err := json.Unmarshal([]byte(model.PdConfig), &out.PdConfig); err != nil {
		return nil, bizerr.WrapError(err, "unmarshal pd config")
	}
	if err := json.Unmarshal([]byte(model.PdServerConfig), &out.PdServerConfig); err != nil {
		return nil, bizerr.WrapError(err, "unmarshal pd server config")
	}
	if err := json.Unmarshal([]byte(model.PlacementRules), &out.PlacementRules); err != nil {
		return nil, bizerr.WrapError(err, "unmarshal placement rules")
	}
	if err := json.Unmarshal([]byte(model.StoreLimits), &out.StoreLimits); err != nil {
		return nil, bizerr.WrapError(err, "unmarshal store limits")
	}

	return out, nil
}
