package autoscale_runtime_guard

import (
	"context"
	"fmt"
	"time"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/pkgv2/db"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type RepoImpl struct {
	provider db.Provider
}

func NewAutoscaleRuntimeGuardRepo(provider db.Provider) repo.AutoscaleRuntimeGuardRepo {
	return &RepoImpl{provider: provider}
}

func (r *RepoImpl) UpsertGuard(ctx context.Context, guard *entity.AutoscaleRuntimeGuard) (*entity.AutoscaleRuntimeGuard, error) {
	if guard == nil {
		return nil, fmt.Errorf("autoscale runtime guard is nil")
	}
	row := entityToSchema(guard)

	wdb := r.provider.WriteDB(ctx)
	target := &schema.AutoscaleRuntimeGuard{}
	if err := wdb.Where(
		"scope_type = ? AND scope_id = ? AND guard_type = ? AND component_type = ?",
		row.ScopeType,
		row.ScopeID,
		row.GuardType,
		row.ComponentType,
	).Assign(*row).FirstOrCreate(target).Error; err != nil {
		return nil, fmt.Errorf("upsert autoscale runtime guard (%s/%s): %w", row.ScopeType, row.ScopeID, err)
	}
	return schemaToEntity(target), nil
}

func (r *RepoImpl) DeleteGuard(
	ctx context.Context,
	scopeType entity.AutoscaleRuntimeGuardScopeType,
	scopeID string,
	guardType entity.AutoscaleRuntimeGuardType,
	componentType entity.ComponentType,
) error {
	wdb := r.provider.WriteDB(ctx)
	// Hard delete (Unscoped): the schema embeds gorm.Model, so a plain
	// Delete only sets deleted_at while the row stays. The unique index
	// runtime_guard_key does NOT include deleted_at, so a later
	// UpsertGuard.FirstOrCreate (which is default-scoped and can't see the
	// soft-deleted row) would attempt an INSERT and collide with the unique
	// key. Guards are operational toggles with no need for soft-delete
	// history, so we hard-delete to keep delete-then-recreate working.
	if err := wdb.Unscoped().Where(
		"scope_type = ? AND scope_id = ? AND guard_type = ? AND component_type = ?",
		scopeTypeToDB(scopeType),
		scopeID,
		guardTypeToDB(guardType),
		componentTypeToDB(componentType),
	).Delete(&schema.AutoscaleRuntimeGuard{}).Error; err != nil {
		return fmt.Errorf("delete autoscale runtime guard (%s/%s): %w", scopeType.String(), scopeID, err)
	}
	return nil
}

func (r *RepoImpl) ListGuards(ctx context.Context, filter repo.AutoscaleRuntimeGuardFilter) ([]*entity.AutoscaleRuntimeGuard, error) {
	rdb := r.provider.ReadDB(ctx)
	query := rdb.Model(&schema.AutoscaleRuntimeGuard{})
	if filter.ScopeType != apiv1.AutoscaleRuntimeGuard_SCOPE_TYPE_UNSPECIFIED {
		query = query.Where("scope_type = ?", scopeTypeToDB(filter.ScopeType))
	}
	if filter.ScopeID != "" {
		query = query.Where("scope_id = ?", filter.ScopeID)
	}
	if filter.GuardType != apiv1.AutoscaleRuntimeGuard_GUARD_TYPE_UNSPECIFIED {
		query = query.Where("guard_type = ?", guardTypeToDB(filter.GuardType))
	}
	if filter.ComponentType != apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		query = query.Where("component_type = ?", componentTypeToDB(filter.ComponentType))
	}
	if filter.ActiveOnly {
		now := time.Now()
		query = query.Where("expires_at IS NULL OR expires_at > ?", now)
	}

	var rows []*schema.AutoscaleRuntimeGuard
	if err := query.Order("updated_at desc").Find(&rows).Error; err != nil {
		return nil, fmt.Errorf("list autoscale runtime guards: %w", err)
	}

	guards := make([]*entity.AutoscaleRuntimeGuard, 0, len(rows))
	for _, row := range rows {
		guards = append(guards, schemaToEntity(row))
	}
	return guards, nil
}

func schemaToEntity(row *schema.AutoscaleRuntimeGuard) *entity.AutoscaleRuntimeGuard {
	if row == nil {
		return nil
	}
	guard := &entity.AutoscaleRuntimeGuard{
		ScopeType:     scopeTypeFromDB(row.ScopeType),
		ScopeId:       row.ScopeID,
		GuardType:     guardTypeFromDB(row.GuardType),
		ComponentType: componentTypeFromDB(row.ComponentType),
		Reason:        row.Reason,
		Source:        row.Source,
		CreatedAt:     timestamppb.New(row.CreatedAt),
		UpdatedAt:     timestamppb.New(row.UpdatedAt),
	}
	if row.ExpiresAt != nil {
		guard.ExpiresAt = timestamppb.New(*row.ExpiresAt)
	}
	return guard
}

func entityToSchema(guard *entity.AutoscaleRuntimeGuard) *schema.AutoscaleRuntimeGuard {
	row := &schema.AutoscaleRuntimeGuard{
		ScopeType:     scopeTypeToDB(guard.GetScopeType()),
		ScopeID:       guard.GetScopeId(),
		GuardType:     guardTypeToDB(guard.GetGuardType()),
		ComponentType: componentTypeToDB(guard.GetComponentType()),
		Reason:        guard.GetReason(),
		Source:        guard.GetSource(),
	}
	if guard.GetExpiresAt() != nil {
		expiresAt := guard.GetExpiresAt().AsTime()
		row.ExpiresAt = &expiresAt
	}
	return row
}

func scopeTypeToDB(v entity.AutoscaleRuntimeGuardScopeType) string {
	switch v {
	case apiv1.AutoscaleRuntimeGuard_REGION:
		return "region"
	default:
		return ""
	}
}

func scopeTypeFromDB(v string) entity.AutoscaleRuntimeGuardScopeType {
	switch v {
	case "region":
		return apiv1.AutoscaleRuntimeGuard_REGION
	default:
		return apiv1.AutoscaleRuntimeGuard_SCOPE_TYPE_UNSPECIFIED
	}
}

func guardTypeToDB(v entity.AutoscaleRuntimeGuardType) string {
	switch v {
	case apiv1.AutoscaleRuntimeGuard_FORBID_SCALE_IN:
		return "forbid_scale_in"
	default:
		return ""
	}
}

func guardTypeFromDB(v string) entity.AutoscaleRuntimeGuardType {
	switch v {
	case "forbid_scale_in":
		return apiv1.AutoscaleRuntimeGuard_FORBID_SCALE_IN
	default:
		return apiv1.AutoscaleRuntimeGuard_GUARD_TYPE_UNSPECIFIED
	}
}

func componentTypeToDB(v entity.ComponentType) string {
	if v == apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		return ""
	}
	return v.String()
}

func componentTypeFromDB(v string) entity.ComponentType {
	if v == "" {
		return apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED
	}
	if enum, ok := apiv1.ComponentType_value[v]; ok {
		return apiv1.ComponentType(enum)
	}
	return apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED
}
