package repo

import (
	"context"
	"time"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
)

// NodeInstanceGroupRepo defines the interface for NodeInstanceGroup persistence.
type NodeInstanceGroupRepo interface {
	// Save creates or updates a NodeInstanceGroup.
	Save(ctx context.Context, group *entity.NodeInstanceGroup) error

	// Load retrieves a NodeInstanceGroup by its ID.
	Load(ctx context.Context, id string) (*entity.NodeInstanceGroup, error)

	// LoadBySpec retrieves a NodeInstanceGroup by nodeSpec.
	// Returns nil (not error) if not found.
	LoadBySpec(ctx context.Context, nodeSpec string) (*entity.NodeInstanceGroup, error)

	// LoadBySpecAndPrimary retrieves a NodeInstanceGroup by nodeSpec and primary instance type.
	// Returns nil (not error) if not found.
	LoadBySpecAndPrimary(ctx context.Context, nodeSpec, primaryInstance string) (*entity.NodeInstanceGroup, error)

	// LoadActiveBySpec retrieves an ACTIVE NodeInstanceGroup by nodeSpec.
	// Returns nil (not error) if not found.
	LoadActiveBySpec(ctx context.Context, nodeSpec string) (*entity.NodeInstanceGroup, error)

	// LoadActiveBySpecAndPrimary retrieves an ACTIVE NodeInstanceGroup by nodeSpec and primary instance type.
	// Returns nil (not error) if not found.
	LoadActiveBySpecAndPrimary(ctx context.Context, nodeSpec, primaryInstance string) (*entity.NodeInstanceGroup, error)

	// ListBySpec retrieves all NodeInstanceGroups for a given nodeSpec.
	ListBySpec(ctx context.Context, nodeSpec string) ([]*entity.NodeInstanceGroup, error)

	// ListAll retrieves all NodeInstanceGroups in the region.
	ListAll(ctx context.Context) ([]*entity.NodeInstanceGroup, error)

	// ListByStatus retrieves all NodeInstanceGroups whose phase matches the given status
	// (status is the API term that maps to the underlying phase column).
	ListByStatus(ctx context.Context, status string) ([]*entity.NodeInstanceGroup, error)

	// Delete removes a NodeInstanceGroup by nodeSpec and primary instance type.
	Delete(ctx context.Context, nodeSpec, primaryInstance string) error
}

// DataplaneNodeInstanceGroupStateRepo defines the interface for DataplaneNodeInstanceGroupState persistence.
type DataplaneNodeInstanceGroupStateRepo interface {
	// Save creates or updates a DataplaneNodeInstanceGroupState.
	Save(ctx context.Context, state *entity.DataplaneNodeInstanceGroupState) error

	// Load retrieves a DataplaneNodeInstanceGroupState by its ID.
	Load(ctx context.Context, id string) (*entity.DataplaneNodeInstanceGroupState, error)

	// LoadByDataplaneAndSpec retrieves a DataplaneNodeInstanceGroupState by dataplane, nodeSpec, and primaryInstance.
	// Returns nil (not error) if not found.
	LoadByDataplaneAndSpec(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string) (*entity.DataplaneNodeInstanceGroupState, error)

	// LoadOrCreate loads an existing state or creates a new one in Normal state.
	// This provides lazy initialization for states.
	LoadOrCreate(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string) (*entity.DataplaneNodeInstanceGroupState, error)

	// ListByDataplane retrieves all DataplaneNodeInstanceGroupStates for a given dataplane.
	ListByDataplane(ctx context.Context, dataplaneName string) ([]*entity.DataplaneNodeInstanceGroupState, error)

	// ListByState retrieves all DataplaneNodeInstanceGroupStates with the given state.
	ListByState(ctx context.Context, state string) ([]*entity.DataplaneNodeInstanceGroupState, error)

	// Delete removes a DataplaneNodeInstanceGroupState by its ID.
	Delete(ctx context.Context, id string) error

	// DeleteByDataplaneAndSpec removes the DataplaneNodeInstanceGroupState for a specific
	// (dataplaneName, nodeSpec, primaryInstance). Used when a single dataplane is deprecated.
	DeleteByDataplaneAndSpec(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string) error

	// DeleteBySpecAndPrimary removes all DataplaneNodeInstanceGroupStates matching (nodeSpec, primaryInstance)
	// across all dataplanes. Used when the region-level group enters RETIRING.
	DeleteBySpecAndPrimary(ctx context.Context, nodeSpec, primaryInstance string) error

	// TransitionState atomically updates the state and increments the fencing token.
	// Returns error if the current state doesn't match expectedState (optimistic locking).
	// The stateEnteredAt will be set to current time.
	TransitionState(ctx context.Context, id, expectedState, newState string) error

	// TransitionStateWithCooldown atomically transitions state and sets cooldown in a single operation.
	// This ensures consistency between state transition and cooldown setting.
	// Pass nil for cooldownUntil to clear the cooldown.
	TransitionStateWithCooldown(ctx context.Context, id, expectedState, newState string, cooldownUntil *time.Time) error

	// SetCooldown sets a cooldown period for the state.
	SetCooldown(ctx context.Context, id string, until time.Time) error

	// ClearCooldown removes the cooldown for the state.
	ClearCooldown(ctx context.Context, id string) error
}

// DataplaneNodeInstanceGroupLockRepo defines the interface for distributed locking on per-(dataplane, nodeSpec, primaryInstance) operations.
type DataplaneNodeInstanceGroupLockRepo interface {
	// TryAcquire attempts to acquire the lock for the given (dataplane, nodeSpec, primaryInstance).
	// Returns the lock with the new fencing token if successful, nil if lock is held by another owner.
	// The lock will expire after the given duration.
	TryAcquire(ctx context.Context, dataplaneName, nodeSpec, primaryInstance, ownerID string, leaseDuration time.Duration) (*entity.DataplaneNodeInstanceGroupLock, error)

	// Release releases the lock if held by the given owner.
	// Returns nil if successful, error if lock is not held by this owner.
	Release(ctx context.Context, dataplaneName, nodeSpec, primaryInstance, ownerID string) error

	// Load retrieves the current lock status for a (dataplane, nodeSpec, primaryInstance).
	// Returns nil (not error) if no lock exists.
	Load(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string) (*entity.DataplaneNodeInstanceGroupLock, error)

	// ForceRelease releases all locks for a dataplane regardless of owner (admin operation).
	// Deletes all rows whose dataplane_name matches, covering every nodeSpec and primaryInstance.
	ForceRelease(ctx context.Context, dataplaneName string) error

	// IsLockValid checks if a lock with the given fencing token is still valid.
	// Used for fencing token validation before performing write operations.
	IsLockValid(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string, fencingToken int64) (bool, error)
}

// DataplaneNodeInstanceGroupRepo defines the interface for per-dataplane overrides.
type DataplaneNodeInstanceGroupRepo interface {
	// Save creates or updates a DataplaneNodeInstanceGroup.
	Save(ctx context.Context, override *entity.DataplaneNodeInstanceGroup) error

	// LoadByDataplaneAndSpec retrieves an override by dataplane, nodeSpec, and primaryInstance.
	// Returns nil (not error) if not found.
	LoadByDataplaneAndSpec(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string) (*entity.DataplaneNodeInstanceGroup, error)

	// ListBySpec retrieves all overrides for a given nodeSpec.
	ListBySpec(ctx context.Context, nodeSpec string) ([]*entity.DataplaneNodeInstanceGroup, error)

	// ListByDataplane retrieves all overrides for a given dataplane.
	ListByDataplane(ctx context.Context, dataplaneName string) ([]*entity.DataplaneNodeInstanceGroup, error)

	// Delete removes an override by dataplane, nodeSpec, and primary instance type.
	Delete(ctx context.Context, dataplaneName, nodeSpec, primaryInstance string) error

	// DeleteByDataplane removes all overrides for a given dataplane.
	DeleteByDataplane(ctx context.Context, dataplaneName string) error
}
