package auditlog

import (
	"context"
	"errors"

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

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

type ConfigRepoImpl struct {
	provider db.Provider
}

func NewConfigRepo(provider db.Provider) auditlogrepo.ConfigRepo {
	return &ConfigRepoImpl{
		provider: provider,
	}
}

func (r *ConfigRepoImpl) Query(
	ctx context.Context,
	filter *auditlogrepo.ConfigFilter,
) ([]*globaldedicatedv1.AuditLogConfig, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	tx := rdb.Model(&schema.AuditLogConfig{})

	if len(filter.ClusterIDs) > 0 {
		tx = tx.Where("cluster_id IN (?)", filter.ClusterIDs)
	}

	var configModels []*schema.AuditLogConfig
	if err := tx.Find(&configModels).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find audit log configs")
	}

	var configs []*globaldedicatedv1.AuditLogConfig
	for _, model := range configModels {
		configs = append(configs, auditLogConfigModelToEn(model))
	}

	return configs, nil
}

func (r *ConfigRepoImpl) Get(ctx context.Context, id uint64) (*globaldedicatedv1.AuditLogConfig, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	var configModel schema.AuditLogConfig
	if err := rdb.Where("id = ?", id).First(&configModel).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "get audit log config by id")
	}
	return auditLogConfigModelToEn(&configModel), nil
}

func (r *ConfigRepoImpl) GetByClusterID(
	ctx context.Context,
	clusterID uint64,
) (*globaldedicatedv1.AuditLogConfig, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	var configModel schema.AuditLogConfig
	if err := rdb.Where("cluster_id = ?", clusterID).First(&configModel).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "get audit log config by cluster_id")
	}
	return auditLogConfigModelToEn(&configModel), nil
}

func (r *ConfigRepoImpl) Upsert(ctx context.Context, config *globaldedicatedv1.AuditLogConfig) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)
	configModel := auditLogConfigEnToModel(config)

	err := wdb.Transaction(func(tx *gorm.DB) error {
		var existing schema.AuditLogConfig
		// Use Unscoped to find both active and soft-deleted records.
		err := tx.Unscoped().Where("cluster_id = ?", config.GetClusterId()).First(&existing).Error

		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			// An unexpected error occurred.
			return err
		}

		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Record does not exist, create a new one.
			configModel.ID = 0 // Ensure ID is zero for creation.
			if createErr := tx.Create(configModel).Error; createErr != nil {
				return createErr
			}
		} else {
			// Record exists, update it.
			configModel.ID = existing.ID
			// Restore soft-deleted record by resetting DeletedAt.
			configModel.DeletedAt = gorm.DeletedAt{}
			// Use Select("*") to update all fields from the model, including zero-value fields.
			// Omit CreatedAt to preserve the original creation timestamp.
			if updateErr := tx.Model(&existing).
				Select("*").
				Omit("created_at").
				Updates(configModel).Error; updateErr != nil {
				return updateErr
			}
		}
		config.Id = uint64(configModel.ID)
		return nil
	})

	if err != nil {
		return bizerr.WrapGormError(err, "failed to upsert audit log config")
	}

	return nil
}

func (r *ConfigRepoImpl) DeleteByClusterID(ctx context.Context, clusterID uint64) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Where("cluster_id = ?", clusterID).Delete(&schema.AuditLogConfig{}).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete audit log config by cluster id")
	}
	return nil
}

func auditLogConfigEnToModel(config *globaldedicatedv1.AuditLogConfig) *schema.AuditLogConfig {
	model := &schema.AuditLogConfig{
		Model: gorm.Model{
			ID: uint(config.GetId()),
		},
		ClusterID:     config.GetClusterId(),
		Enabled:       config.GetEnabled(),
		BucketURI:     config.GetBucketUrl(),
		BucketRegion:  config.GetRegion(),
		AWSRoleARN:    config.GetRoleArn(),
		AzureSASToken: config.GetSasToken(),
		BucketManager: config.GetBucketManager(),
		Redacted:      !config.GetUnredacted(),
		Format:        config.GetFormat(),
		MaxSizeMib:    config.GetMaxSizeMib(),
		MaxAgeSecond:  config.GetMaxAgeSecond(),
	}
	return model
}

func auditLogConfigModelToEn(model *schema.AuditLogConfig) *globaldedicatedv1.AuditLogConfig {
	en := &globaldedicatedv1.AuditLogConfig{
		Id:            uint64(model.ID),
		ClusterId:     model.ClusterID,
		Enabled:       lo.ToPtr(model.Enabled),
		BucketUrl:     model.BucketURI,
		Region:        model.BucketRegion,
		RoleArn:       model.AWSRoleARN,
		SasToken:      model.AzureSASToken,
		BucketManager: model.BucketManager,
		Unredacted:    lo.ToPtr(!model.Redacted),
		Format:        model.Format,
		MaxSizeMib:    model.MaxSizeMib,
		MaxAgeSecond:  lo.ToPtr(model.MaxAgeSecond),
	}
	return en
}
