package auditlog

import (
	"context"
	"errors"

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

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

type SettingRepoImpl struct {
	provider db.Provider
}

func NewSettingRepo(provider db.Provider) auditlogrepo.SettingRepo {
	return &SettingRepoImpl{
		provider: provider,
	}
}

func (r *SettingRepoImpl) Query(
	ctx context.Context,
	filter *auditlogrepo.SettingFilter,
) ([]*globalngv1.AuditLogSetting, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	tx := rdb.Model(&schema.AuditLogSetting{})

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

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

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

	return configs, nil
}

func (r *SettingRepoImpl) Get(ctx context.Context, id uint64) (*globalngv1.AuditLogSetting, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	var configModel schema.AuditLogSetting
	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 *SettingRepoImpl) GetByClusterID(
	ctx context.Context,
	clusterID uint64,
) (*globalngv1.AuditLogSetting, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	var configModel schema.AuditLogSetting
	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 *SettingRepoImpl) Upsert(ctx context.Context, config *globalngv1.AuditLogSetting) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)
	configModel := auditLogConfigEnToModel(config)

	err := wdb.Transaction(func(tx *gorm.DB) error {
		var existing schema.AuditLogSetting
		// 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 *SettingRepoImpl) DeleteByClusterID(ctx context.Context, clusterID uint64) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Where("cluster_id = ?", clusterID).Delete(&schema.AuditLogSetting{}).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete audit log config by cluster id")
	}
	return nil
}

func auditLogConfigEnToModel(config *globalngv1.AuditLogSetting) *schema.AuditLogSetting {
	model := &schema.AuditLogSetting{
		Model: gorm.Model{
			ID: uint(config.GetId()),
		},
		ClusterID:         config.GetClusterId(),
		Enabled:           config.GetEnabled(),
		BucketURI:         config.GetBucketUrl(),
		BucketRegion:      config.GetRegion(),
		CloudRoleIdentity: config.GetCloudRoleIdentity(),
		ExternalID:        config.GetExternalId(),
		Redacted:          !config.GetUnredacted(),
		Format:            config.GetFormat(),
		MaxSizeMib:        config.GetMaxSizeMib(),
		MaxAgeSecond:      config.GetMaxAgeSecond(),
	}
	return model
}

func auditLogConfigModelToEn(model *schema.AuditLogSetting) *globalngv1.AuditLogSetting {
	en := &globalngv1.AuditLogSetting{
		Id:                uint64(model.ID),
		ClusterId:         model.ClusterID,
		Enabled:           lo.ToPtr(model.Enabled),
		BucketUrl:         model.BucketURI,
		Region:            model.BucketRegion,
		CloudRoleIdentity: lo.ToPtr(model.CloudRoleIdentity),
		ExternalId:        model.ExternalID,
		Unredacted:        lo.ToPtr(!model.Redacted),
		Format:            model.Format,
		MaxSizeMib:        model.MaxSizeMib,
		MaxAgeSecond:      lo.ToPtr(model.MaxAgeSecond),
	}
	return en
}
