//go:build !integration

// Copyright 2025 Emiliano Spinella (eminwux)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

package controller_test

import (
	"context"
	"errors"
	"io"
	"log/slog"
	"testing"

	"github.com/eminwux/kukeon/internal/cni"
	"github.com/eminwux/kukeon/internal/consts"
	"github.com/eminwux/kukeon/internal/controller"
	"github.com/eminwux/kukeon/internal/controller/runner"
	"github.com/eminwux/kukeon/internal/ctr"
	intmodel "github.com/eminwux/kukeon/internal/modelhub"
)

// fakeRunner implements runner.Runner interface for testing.
type fakeRunner struct {
	// Realm methods
	GetRealmFn                       func(realm intmodel.Realm) (intmodel.Realm, error)
	ListRealmsFn                     func() ([]intmodel.Realm, error)
	CreateRealmFn                    func(realm intmodel.Realm) (intmodel.Realm, error)
	EnsureRealmFn                    func(realm intmodel.Realm) (intmodel.Realm, error)
	ExistsRealmContainerdNamespaceFn func(namespace string) (bool, error)
	ListContainerdNamespacesFn       func() ([]string, error)
	DeleteRealmFn                    func(realm intmodel.Realm) error

	// Space methods
	GetSpaceFn                  func(space intmodel.Space) (intmodel.Space, error)
	ListSpacesFn                func(realmName string) ([]intmodel.Space, error)
	CreateSpaceFn               func(space intmodel.Space) (intmodel.Space, error)
	EnsureSpaceFn               func(space intmodel.Space) (intmodel.Space, error)
	ExistsSpaceCNIConfigFn      func(space intmodel.Space) (bool, error)
	ResolveSpaceCNIConfigPathFn func(realmName, spaceName string) (string, error)
	DeleteSpaceFn               func(space intmodel.Space) error

	// Stack methods
	GetStackFn    func(stack intmodel.Stack) (intmodel.Stack, error)
	ListStacksFn  func(realmName, spaceName string) ([]intmodel.Stack, error)
	CreateStackFn func(stack intmodel.Stack) (intmodel.Stack, error)
	EnsureStackFn func(stack intmodel.Stack) (intmodel.Stack, error)
	DeleteStackFn func(stack intmodel.Stack) error

	// Secret methods
	WriteSecretFn  func(secret intmodel.Secret) (bool, error)
	GetSecretFn    func(secret intmodel.Secret) (intmodel.Secret, error)
	ListSecretsFn  func(realmName, spaceName, stackName, cellName string) ([]intmodel.Secret, error)
	DeleteSecretFn func(secret intmodel.Secret) error

	// Blueprint methods
	WriteBlueprintFn  func(bp intmodel.CellBlueprint) (bool, error)
	GetBlueprintFn    func(bp intmodel.CellBlueprint) (intmodel.CellBlueprint, error)
	ListBlueprintsFn  func(realmName, spaceName, stackName string) ([]intmodel.CellBlueprint, error)
	DeleteBlueprintFn func(bp intmodel.CellBlueprint) error

	// Config methods
	WriteConfigFn         func(config intmodel.CellConfig) (bool, error)
	WriteConfigIfAbsentFn func(config intmodel.CellConfig) error
	GetConfigFn           func(config intmodel.CellConfig) (intmodel.CellConfig, error)
	ListConfigsFn         func(realmName, spaceName, stackName string) ([]intmodel.CellConfig, error)
	DeleteConfigFn        func(config intmodel.CellConfig) error

	// Cell methods
	GetCellFn                 func(cell intmodel.Cell) (intmodel.Cell, error)
	ListCellsFn               func(realmName, spaceName, stackName string) ([]intmodel.Cell, error)
	CreateCellFn              func(cell intmodel.Cell) (intmodel.Cell, error)
	EnsureCellFn              func(cell intmodel.Cell) (intmodel.Cell, error)
	StartCellFn               func(cell intmodel.Cell) (intmodel.Cell, error)
	StopCellFn                func(cell intmodel.Cell) (intmodel.Cell, error)
	KillCellFn                func(cell intmodel.Cell) (intmodel.Cell, error)
	DeleteCellFn              func(cell intmodel.Cell) error
	ExistsCellRootContainerFn func(cell intmodel.Cell) (bool, error)
	UpdateCellMetadataFn      func(cell intmodel.Cell) error

	ReapplyAttachableSocketPermsFn func(spec intmodel.ContainerSpec)

	// Container methods
	ListContainersFn    func(realmName, spaceName, stackName, cellName string) ([]intmodel.ContainerSpec, error)
	CreateContainerFn   func(cell intmodel.Cell, container intmodel.ContainerSpec) (intmodel.Cell, error)
	EnsureContainerFn   func(cell intmodel.Cell, container intmodel.ContainerSpec) (intmodel.Cell, error)
	StartContainerFn    func(cell intmodel.Cell, containerID string) (intmodel.Cell, error)
	StopContainerFn     func(cell intmodel.Cell, containerID string) error
	KillContainerFn     func(cell intmodel.Cell, containerID string) error
	DeleteContainerFn   func(cell intmodel.Cell, containerID string) error
	GetContainerStateFn func(cell intmodel.Cell, containerID string) (intmodel.ContainerState, error)

	// Utility methods
	ExistsCgroupFn func(doc any) (bool, error)

	// Purge methods
	PurgeRealmFn     func(realm intmodel.Realm) (bool, error)
	PurgeSpaceFn     func(space intmodel.Space) error
	PurgeStackFn     func(stack intmodel.Stack) error
	PurgeCellFn      func(cell intmodel.Cell) error
	PurgeContainerFn func(realm intmodel.Realm, containerID string) error

	// Bootstrap
	BootstrapCNIFn           func(cfgDir, cacheDir, binDir string) (cni.BootstrapReport, error)
	EnsureKukeonRootCgroupFn func() (bool, bool, error)

	// Close
	CloseFn func() error

	// Apply methods
	UpdateRealmFn     func(realm intmodel.Realm) (intmodel.Realm, error)
	UpdateSpaceFn     func(space intmodel.Space) (intmodel.Space, error)
	UpdateStackFn     func(stack intmodel.Stack) (intmodel.Stack, error)
	UpdateCellFn      func(cell intmodel.Cell) (intmodel.Cell, error)
	RecreateCellFn    func(cell intmodel.Cell) (intmodel.Cell, error)
	UpdateContainerFn func(cell intmodel.Cell, container intmodel.ContainerSpec) (intmodel.Cell, error)

	// Refresh methods
	RefreshRealmFn  func(realm intmodel.Realm) (intmodel.Realm, bool, error)
	RefreshSpaceFn  func(space intmodel.Space) (intmodel.Space, bool, error)
	RefreshStackFn  func(stack intmodel.Stack) (intmodel.Stack, bool, error)
	RefreshCellFn   func(cell intmodel.Cell) (intmodel.Cell, int, error)
	ReconcileCellFn func(cell intmodel.Cell) (intmodel.Cell, runner.ReconcileOutcome, error)

	// Image methods
	LoadImageFn            func(namespace string, reader io.Reader) ([]string, error)
	ListImagesFn           func(namespace string) ([]ctr.ImageInfo, error)
	GetImageFn             func(namespace, ref string) (ctr.ImageInfo, error)
	ImageChainIDFn         func(namespace, ref string) (string, error)
	ContainerRootChainIDFn func(namespace, containerID string) (string, error)
	DeleteImageFn          func(namespace, ref string) error
	PruneImagesFn          func(namespace string) (ctr.PruneResult, error)
}

// Realm methods

func (f *fakeRunner) GetRealm(realm intmodel.Realm) (intmodel.Realm, error) {
	if f.GetRealmFn != nil {
		return f.GetRealmFn(realm)
	}
	return intmodel.Realm{}, errors.New("unexpected call to GetRealm")
}

func (f *fakeRunner) ListRealms() ([]intmodel.Realm, error) {
	if f.ListRealmsFn != nil {
		return f.ListRealmsFn()
	}
	return nil, errors.New("unexpected call to ListRealms")
}

func (f *fakeRunner) CreateRealm(realm intmodel.Realm) (intmodel.Realm, error) {
	if f.CreateRealmFn != nil {
		return f.CreateRealmFn(realm)
	}
	return intmodel.Realm{}, errors.New("unexpected call to CreateRealm")
}

func (f *fakeRunner) EnsureRealm(realm intmodel.Realm) (intmodel.Realm, error) {
	if f.EnsureRealmFn != nil {
		return f.EnsureRealmFn(realm)
	}
	return intmodel.Realm{}, errors.New("unexpected call to EnsureRealm")
}

func (f *fakeRunner) ExistsRealmContainerdNamespace(namespace string) (bool, error) {
	if f.ExistsRealmContainerdNamespaceFn != nil {
		return f.ExistsRealmContainerdNamespaceFn(namespace)
	}
	return false, errors.New("unexpected call to ExistsRealmContainerdNamespace")
}

func (f *fakeRunner) ListContainerdNamespaces() ([]string, error) {
	if f.ListContainerdNamespacesFn != nil {
		return f.ListContainerdNamespacesFn()
	}
	// Default to an empty list so tests that don't care about the suffix
	// enumerator (the dominant case) don't have to install a stub.
	return nil, nil
}

func (f *fakeRunner) DeleteRealm(realm intmodel.Realm) error {
	if f.DeleteRealmFn != nil {
		return f.DeleteRealmFn(realm)
	}
	return errors.New("unexpected call to DeleteRealm")
}

// Space methods

func (f *fakeRunner) GetSpace(space intmodel.Space) (intmodel.Space, error) {
	if f.GetSpaceFn != nil {
		return f.GetSpaceFn(space)
	}
	return intmodel.Space{}, errors.New("unexpected call to GetSpace")
}

func (f *fakeRunner) ResolveSpaceCNIConfigPath(realmName, spaceName string) (string, error) {
	if f.ResolveSpaceCNIConfigPathFn != nil {
		return f.ResolveSpaceCNIConfigPathFn(realmName, spaceName)
	}
	return "", errors.New("unexpected call to ResolveSpaceCNIConfigPath")
}

func (f *fakeRunner) ListSpaces(realmName string) ([]intmodel.Space, error) {
	if f.ListSpacesFn != nil {
		return f.ListSpacesFn(realmName)
	}
	return nil, errors.New("unexpected call to ListSpaces")
}

func (f *fakeRunner) CreateSpace(space intmodel.Space) (intmodel.Space, error) {
	if f.CreateSpaceFn != nil {
		return f.CreateSpaceFn(space)
	}
	return intmodel.Space{}, errors.New("unexpected call to CreateSpace")
}

func (f *fakeRunner) EnsureSpace(space intmodel.Space) (intmodel.Space, error) {
	if f.EnsureSpaceFn != nil {
		return f.EnsureSpaceFn(space)
	}
	return intmodel.Space{}, errors.New("unexpected call to EnsureSpace")
}

func (f *fakeRunner) ExistsSpaceCNIConfig(space intmodel.Space) (bool, error) {
	if f.ExistsSpaceCNIConfigFn != nil {
		return f.ExistsSpaceCNIConfigFn(space)
	}
	return false, errors.New("unexpected call to ExistsSpaceCNIConfig")
}

func (f *fakeRunner) DeleteSpace(space intmodel.Space) error {
	if f.DeleteSpaceFn != nil {
		return f.DeleteSpaceFn(space)
	}
	return errors.New("unexpected call to DeleteSpace")
}

// Stack methods

func (f *fakeRunner) GetStack(stack intmodel.Stack) (intmodel.Stack, error) {
	if f.GetStackFn != nil {
		return f.GetStackFn(stack)
	}
	return intmodel.Stack{}, errors.New("unexpected call to GetStack")
}

func (f *fakeRunner) ListStacks(realmName, spaceName string) ([]intmodel.Stack, error) {
	if f.ListStacksFn != nil {
		return f.ListStacksFn(realmName, spaceName)
	}
	return nil, errors.New("unexpected call to ListStacks")
}

func (f *fakeRunner) CreateStack(stack intmodel.Stack) (intmodel.Stack, error) {
	if f.CreateStackFn != nil {
		return f.CreateStackFn(stack)
	}
	return intmodel.Stack{}, errors.New("unexpected call to CreateStack")
}

func (f *fakeRunner) EnsureStack(stack intmodel.Stack) (intmodel.Stack, error) {
	if f.EnsureStackFn != nil {
		return f.EnsureStackFn(stack)
	}
	return intmodel.Stack{}, errors.New("unexpected call to EnsureStack")
}

func (f *fakeRunner) DeleteStack(stack intmodel.Stack) error {
	if f.DeleteStackFn != nil {
		return f.DeleteStackFn(stack)
	}
	return errors.New("unexpected call to DeleteStack")
}

// Secret methods

func (f *fakeRunner) WriteSecret(secret intmodel.Secret) (bool, error) {
	if f.WriteSecretFn != nil {
		return f.WriteSecretFn(secret)
	}
	return false, errors.New("unexpected call to WriteSecret")
}

func (f *fakeRunner) GetSecret(secret intmodel.Secret) (intmodel.Secret, error) {
	if f.GetSecretFn != nil {
		return f.GetSecretFn(secret)
	}
	return intmodel.Secret{}, errors.New("unexpected call to GetSecret")
}

func (f *fakeRunner) ListSecrets(realmName, spaceName, stackName, cellName string) ([]intmodel.Secret, error) {
	if f.ListSecretsFn != nil {
		return f.ListSecretsFn(realmName, spaceName, stackName, cellName)
	}
	return nil, errors.New("unexpected call to ListSecrets")
}

func (f *fakeRunner) DeleteSecret(secret intmodel.Secret) error {
	if f.DeleteSecretFn != nil {
		return f.DeleteSecretFn(secret)
	}
	return errors.New("unexpected call to DeleteSecret")
}

// Blueprint methods

func (f *fakeRunner) WriteBlueprint(bp intmodel.CellBlueprint) (bool, error) {
	if f.WriteBlueprintFn != nil {
		return f.WriteBlueprintFn(bp)
	}
	return false, errors.New("unexpected call to WriteBlueprint")
}

func (f *fakeRunner) GetBlueprint(bp intmodel.CellBlueprint) (intmodel.CellBlueprint, error) {
	if f.GetBlueprintFn != nil {
		return f.GetBlueprintFn(bp)
	}
	return intmodel.CellBlueprint{}, errors.New("unexpected call to GetBlueprint")
}

func (f *fakeRunner) ListBlueprints(realmName, spaceName, stackName string) ([]intmodel.CellBlueprint, error) {
	if f.ListBlueprintsFn != nil {
		return f.ListBlueprintsFn(realmName, spaceName, stackName)
	}
	return nil, errors.New("unexpected call to ListBlueprints")
}

func (f *fakeRunner) DeleteBlueprint(bp intmodel.CellBlueprint) error {
	if f.DeleteBlueprintFn != nil {
		return f.DeleteBlueprintFn(bp)
	}
	return errors.New("unexpected call to DeleteBlueprint")
}

// Config methods

func (f *fakeRunner) WriteConfig(config intmodel.CellConfig) (bool, error) {
	if f.WriteConfigFn != nil {
		return f.WriteConfigFn(config)
	}
	return false, errors.New("unexpected call to WriteConfig")
}

func (f *fakeRunner) WriteConfigIfAbsent(config intmodel.CellConfig) error {
	if f.WriteConfigIfAbsentFn != nil {
		return f.WriteConfigIfAbsentFn(config)
	}
	return errors.New("unexpected call to WriteConfigIfAbsent")
}

func (f *fakeRunner) GetConfig(config intmodel.CellConfig) (intmodel.CellConfig, error) {
	if f.GetConfigFn != nil {
		return f.GetConfigFn(config)
	}
	return intmodel.CellConfig{}, errors.New("unexpected call to GetConfig")
}

func (f *fakeRunner) ListConfigs(realmName, spaceName, stackName string) ([]intmodel.CellConfig, error) {
	if f.ListConfigsFn != nil {
		return f.ListConfigsFn(realmName, spaceName, stackName)
	}
	return nil, errors.New("unexpected call to ListConfigs")
}

func (f *fakeRunner) DeleteConfig(config intmodel.CellConfig) error {
	if f.DeleteConfigFn != nil {
		return f.DeleteConfigFn(config)
	}
	return errors.New("unexpected call to DeleteConfig")
}

// Cell methods

func (f *fakeRunner) GetCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.GetCellFn != nil {
		return f.GetCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to GetCell")
}

func (f *fakeRunner) ListCells(realmName, spaceName, stackName string) ([]intmodel.Cell, error) {
	if f.ListCellsFn != nil {
		return f.ListCellsFn(realmName, spaceName, stackName)
	}
	return nil, errors.New("unexpected call to ListCells")
}

func (f *fakeRunner) CreateCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.CreateCellFn != nil {
		return f.CreateCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to CreateCell")
}

func (f *fakeRunner) EnsureCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.EnsureCellFn != nil {
		return f.EnsureCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to EnsureCell")
}

func (f *fakeRunner) StartCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.StartCellFn != nil {
		return f.StartCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to StartCell")
}

func (f *fakeRunner) StopCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.StopCellFn != nil {
		return f.StopCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to StopCell")
}

func (f *fakeRunner) KillCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.KillCellFn != nil {
		return f.KillCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to KillCell")
}

func (f *fakeRunner) DeleteCell(cell intmodel.Cell) error {
	if f.DeleteCellFn != nil {
		return f.DeleteCellFn(cell)
	}
	return errors.New("unexpected call to DeleteCell")
}

func (f *fakeRunner) ExistsCellRootContainer(cell intmodel.Cell) (bool, error) {
	if f.ExistsCellRootContainerFn != nil {
		return f.ExistsCellRootContainerFn(cell)
	}
	return false, errors.New("unexpected call to ExistsCellRootContainer")
}

func (f *fakeRunner) UpdateCellMetadata(cell intmodel.Cell) error {
	if f.UpdateCellMetadataFn != nil {
		return f.UpdateCellMetadataFn(cell)
	}
	return errors.New("unexpected call to UpdateCellMetadata")
}

func (f *fakeRunner) ReapplyAttachableSocketPerms(spec intmodel.ContainerSpec) {
	if f.ReapplyAttachableSocketPermsFn != nil {
		f.ReapplyAttachableSocketPermsFn(spec)
	}
}

// Container methods

func (f *fakeRunner) ListContainers(
	realmName, spaceName, stackName, cellName string,
) ([]intmodel.ContainerSpec, error) {
	if f.ListContainersFn != nil {
		return f.ListContainersFn(realmName, spaceName, stackName, cellName)
	}
	return nil, errors.New("unexpected call to ListContainers")
}

func (f *fakeRunner) CreateContainer(cell intmodel.Cell, container intmodel.ContainerSpec) (intmodel.Cell, error) {
	if f.CreateContainerFn != nil {
		return f.CreateContainerFn(cell, container)
	}
	return intmodel.Cell{}, errors.New("unexpected call to CreateContainer")
}

func (f *fakeRunner) EnsureContainer(cell intmodel.Cell, container intmodel.ContainerSpec) (intmodel.Cell, error) {
	if f.EnsureContainerFn != nil {
		return f.EnsureContainerFn(cell, container)
	}
	return intmodel.Cell{}, errors.New("unexpected call to EnsureContainer")
}

func (f *fakeRunner) StartContainer(cell intmodel.Cell, containerID string) (intmodel.Cell, error) {
	if f.StartContainerFn != nil {
		return f.StartContainerFn(cell, containerID)
	}
	return intmodel.Cell{}, errors.New("unexpected call to StartContainer")
}

func (f *fakeRunner) StopContainer(cell intmodel.Cell, containerID string) error {
	if f.StopContainerFn != nil {
		return f.StopContainerFn(cell, containerID)
	}
	return errors.New("unexpected call to StopContainer")
}

func (f *fakeRunner) KillContainer(cell intmodel.Cell, containerID string) error {
	if f.KillContainerFn != nil {
		return f.KillContainerFn(cell, containerID)
	}
	return errors.New("unexpected call to KillContainer")
}

func (f *fakeRunner) DeleteContainer(cell intmodel.Cell, containerID string) error {
	if f.DeleteContainerFn != nil {
		return f.DeleteContainerFn(cell, containerID)
	}
	return errors.New("unexpected call to DeleteContainer")
}

func (f *fakeRunner) GetContainerState(cell intmodel.Cell, containerID string) (intmodel.ContainerState, error) {
	if f.GetContainerStateFn != nil {
		return f.GetContainerStateFn(cell, containerID)
	}
	return intmodel.ContainerStateUnknown, errors.New("unexpected call to GetContainerState")
}

// Utility methods

func (f *fakeRunner) ExistsCgroup(doc any) (bool, error) {
	if f.ExistsCgroupFn != nil {
		return f.ExistsCgroupFn(doc)
	}
	return false, errors.New("unexpected call to ExistsCgroup")
}

// Purge methods

func (f *fakeRunner) PurgeRealm(realm intmodel.Realm) (bool, error) {
	if f.PurgeRealmFn != nil {
		return f.PurgeRealmFn(realm)
	}
	return false, errors.New("unexpected call to PurgeRealm")
}

func (f *fakeRunner) PurgeSpace(space intmodel.Space) error {
	if f.PurgeSpaceFn != nil {
		return f.PurgeSpaceFn(space)
	}
	return errors.New("unexpected call to PurgeSpace")
}

func (f *fakeRunner) PurgeStack(stack intmodel.Stack) error {
	if f.PurgeStackFn != nil {
		return f.PurgeStackFn(stack)
	}
	return errors.New("unexpected call to PurgeStack")
}

func (f *fakeRunner) PurgeCell(cell intmodel.Cell) error {
	if f.PurgeCellFn != nil {
		return f.PurgeCellFn(cell)
	}
	return errors.New("unexpected call to PurgeCell")
}

func (f *fakeRunner) PurgeContainer(realm intmodel.Realm, containerID string) error {
	if f.PurgeContainerFn != nil {
		return f.PurgeContainerFn(realm, containerID)
	}
	return errors.New("unexpected call to PurgeContainer")
}

// Bootstrap

func (f *fakeRunner) BootstrapCNI(cfgDir, cacheDir, binDir string) (cni.BootstrapReport, error) {
	if f.BootstrapCNIFn != nil {
		return f.BootstrapCNIFn(cfgDir, cacheDir, binDir)
	}
	return cni.BootstrapReport{}, errors.New("unexpected call to BootstrapCNI")
}

func (f *fakeRunner) EnsureKukeonRootCgroup() (bool, bool, error) {
	if f.EnsureKukeonRootCgroupFn != nil {
		return f.EnsureKukeonRootCgroupFn()
	}
	return false, false, errors.New("unexpected call to EnsureKukeonRootCgroup")
}

// Close

func (f *fakeRunner) Close() error {
	if f.CloseFn != nil {
		return f.CloseFn()
	}
	return nil
}

// Apply

func (f *fakeRunner) UpdateRealm(realm intmodel.Realm) (intmodel.Realm, error) {
	if f.UpdateRealmFn != nil {
		return f.UpdateRealmFn(realm)
	}
	return intmodel.Realm{}, errors.New("unexpected call to UpdateRealm")
}

func (f *fakeRunner) UpdateSpace(space intmodel.Space) (intmodel.Space, error) {
	if f.UpdateSpaceFn != nil {
		return f.UpdateSpaceFn(space)
	}
	return intmodel.Space{}, errors.New("unexpected call to UpdateSpace")
}

func (f *fakeRunner) UpdateStack(stack intmodel.Stack) (intmodel.Stack, error) {
	if f.UpdateStackFn != nil {
		return f.UpdateStackFn(stack)
	}
	return intmodel.Stack{}, errors.New("unexpected call to UpdateStack")
}

func (f *fakeRunner) UpdateCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.UpdateCellFn != nil {
		return f.UpdateCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to UpdateCell")
}

func (f *fakeRunner) RecreateCell(cell intmodel.Cell) (intmodel.Cell, error) {
	if f.RecreateCellFn != nil {
		return f.RecreateCellFn(cell)
	}
	return intmodel.Cell{}, errors.New("unexpected call to RecreateCell")
}

func (f *fakeRunner) UpdateContainer(cell intmodel.Cell, container intmodel.ContainerSpec) (intmodel.Cell, error) {
	if f.UpdateContainerFn != nil {
		return f.UpdateContainerFn(cell, container)
	}
	return intmodel.Cell{}, errors.New("unexpected call to UpdateContainer")
}

// Refresh methods

func (f *fakeRunner) RefreshRealm(realm intmodel.Realm) (intmodel.Realm, bool, error) {
	if f.RefreshRealmFn != nil {
		return f.RefreshRealmFn(realm)
	}
	return intmodel.Realm{}, false, errors.New("unexpected call to RefreshRealm")
}

func (f *fakeRunner) RefreshSpace(space intmodel.Space) (intmodel.Space, bool, error) {
	if f.RefreshSpaceFn != nil {
		return f.RefreshSpaceFn(space)
	}
	return intmodel.Space{}, false, errors.New("unexpected call to RefreshSpace")
}

func (f *fakeRunner) RefreshStack(stack intmodel.Stack) (intmodel.Stack, bool, error) {
	if f.RefreshStackFn != nil {
		return f.RefreshStackFn(stack)
	}
	return intmodel.Stack{}, false, errors.New("unexpected call to RefreshStack")
}

func (f *fakeRunner) RefreshCell(cell intmodel.Cell) (intmodel.Cell, int, error) {
	if f.RefreshCellFn != nil {
		return f.RefreshCellFn(cell)
	}
	return intmodel.Cell{}, 0, errors.New("unexpected call to RefreshCell")
}

func (f *fakeRunner) ReconcileCell(cell intmodel.Cell) (intmodel.Cell, runner.ReconcileOutcome, error) {
	if f.ReconcileCellFn != nil {
		return f.ReconcileCellFn(cell)
	}
	return intmodel.Cell{}, runner.ReconcileOutcome{}, errors.New("unexpected call to ReconcileCell")
}

func (f *fakeRunner) LoadImage(namespace string, reader io.Reader) ([]string, error) {
	if f.LoadImageFn != nil {
		return f.LoadImageFn(namespace, reader)
	}
	return nil, errors.New("unexpected call to LoadImage")
}

func (f *fakeRunner) ListImages(namespace string) ([]ctr.ImageInfo, error) {
	if f.ListImagesFn != nil {
		return f.ListImagesFn(namespace)
	}
	return nil, errors.New("unexpected call to ListImages")
}

func (f *fakeRunner) GetImage(namespace, ref string) (ctr.ImageInfo, error) {
	if f.GetImageFn != nil {
		return f.GetImageFn(namespace, ref)
	}
	return ctr.ImageInfo{}, errors.New("unexpected call to GetImage")
}

func (f *fakeRunner) ImageChainID(namespace, ref string) (string, error) {
	if f.ImageChainIDFn != nil {
		return f.ImageChainIDFn(namespace, ref)
	}
	// Default to empty chainID + nil so bootstrapCell tests that don't
	// exercise the digest-drift branch keep working with no extra stub.
	return "", nil
}

func (f *fakeRunner) ContainerRootChainID(namespace, containerID string) (string, error) {
	if f.ContainerRootChainIDFn != nil {
		return f.ContainerRootChainIDFn(namespace, containerID)
	}
	// Default to empty chainID + nil for the same reason as ImageChainID:
	// the drift comparison treats "both empty" as "no drift".
	return "", nil
}

func (f *fakeRunner) DeleteImage(namespace, ref string) error {
	if f.DeleteImageFn != nil {
		return f.DeleteImageFn(namespace, ref)
	}
	return errors.New("unexpected call to DeleteImage")
}

func (f *fakeRunner) PruneImages(namespace string) (ctr.PruneResult, error) {
	if f.PruneImagesFn != nil {
		return f.PruneImagesFn(namespace)
	}
	return ctr.PruneResult{}, errors.New("unexpected call to PruneImages")
}

// Test helper functions

// setupTestLogger creates a test logger that discards output.
func setupTestLogger(t *testing.T) *slog.Logger {
	t.Helper()
	return slog.New(slog.NewTextHandler(io.Discard, nil))
}

// setupTestContext creates a context with test logger.
func setupTestContext(t *testing.T) context.Context {
	t.Helper()
	logger := setupTestLogger(t)
	return context.WithValue(context.Background(), "logger", logger)
}

// setupTestController creates a test controller instance with injected mock runner.
func setupTestController(t *testing.T, mockRunner runner.Runner) *controller.Exec {
	t.Helper()
	ctx := context.Background()
	logger := setupTestLogger(t)
	opts := controller.Options{
		RunPath:          "/test/run/path",
		ContainerdSocket: "/test/containerd.sock",
	}
	return controller.NewControllerExecForTesting(ctx, logger, opts, mockRunner)
}

// Resource builder helpers

// buildTestRealm creates a test realm with the specified name and namespace.
// An empty namespace argument is filled with consts.RealmNamespace(name) so
// fixtures stay in sync with the controller-layer default in CreateRealm/PurgeRealm.
func buildTestRealm(name, namespace string) intmodel.Realm {
	if namespace == "" {
		namespace = consts.RealmNamespace(name)
	}
	return intmodel.Realm{
		Metadata: intmodel.RealmMetadata{
			Name: name,
			Labels: map[string]string{
				consts.KukeonRealmLabelKey: namespace,
			},
		},
		Spec: intmodel.RealmSpec{
			Namespace: namespace,
		},
		Status: intmodel.RealmStatus{
			State: intmodel.RealmStateReady,
		},
	}
}

// buildTestSpace creates a test space with the specified name and realm name.
func buildTestSpace(name, realmName string) intmodel.Space {
	return intmodel.Space{
		Metadata: intmodel.SpaceMetadata{
			Name: name,
			Labels: map[string]string{
				consts.KukeonRealmLabelKey: realmName,
				consts.KukeonSpaceLabelKey: name,
			},
		},
		Spec: intmodel.SpaceSpec{
			RealmName: realmName,
		},
		Status: intmodel.SpaceStatus{
			State: intmodel.SpaceStateReady,
		},
	}
}

// buildTestStack creates a test stack with the specified name, realm name, and space name.
func buildTestStack(name, realmName, spaceName string) intmodel.Stack {
	return intmodel.Stack{
		Metadata: intmodel.StackMetadata{
			Name: name,
			Labels: map[string]string{
				consts.KukeonRealmLabelKey: realmName,
				consts.KukeonSpaceLabelKey: spaceName,
				consts.KukeonStackLabelKey: name,
			},
		},
		Spec: intmodel.StackSpec{
			ID:        name,
			RealmName: realmName,
			SpaceName: spaceName,
		},
		Status: intmodel.StackStatus{
			State: intmodel.StackStateReady,
		},
	}
}

// buildTestCell creates a test cell with the specified name, realm name, space name, and stack name.
func buildTestCell(name, realmName, spaceName, stackName string) intmodel.Cell {
	return intmodel.Cell{
		Metadata: intmodel.CellMetadata{
			Name: name,
			Labels: map[string]string{
				consts.KukeonRealmLabelKey: realmName,
				consts.KukeonSpaceLabelKey: spaceName,
				consts.KukeonStackLabelKey: stackName,
				consts.KukeonCellLabelKey:  name,
			},
		},
		Spec: intmodel.CellSpec{
			ID:        name,
			RealmName: realmName,
			SpaceName: spaceName,
			StackName: stackName,
		},
		Status: intmodel.CellStatus{
			State: intmodel.CellStateReady,
		},
	}
}

// buildTestContainer creates a test container with the specified parameters.
func buildTestContainer(name, realmName, spaceName, stackName, cellName, image string) intmodel.Container {
	return intmodel.Container{
		Metadata: intmodel.ContainerMetadata{
			Name: name,
		},
		Spec: intmodel.ContainerSpec{
			ID:        name,
			RealmName: realmName,
			SpaceName: spaceName,
			StackName: stackName,
			CellName:  cellName,
			Image:     image,
		},
		Status: intmodel.ContainerStatus{
			State: intmodel.ContainerStateReady,
		},
	}
}

// Basic test to verify mock runner infrastructure works

func TestFakeRunner_ImplementsInterface(t *testing.T) {
	f := &fakeRunner{}
	var _ runner.Runner = f
}

func TestSetupTestController_CreatesControllerWithMockRunner(t *testing.T) {
	mockRunner := &fakeRunner{
		GetRealmFn: func(realm intmodel.Realm) (intmodel.Realm, error) {
			return buildTestRealm("test-realm", "test-ns"), nil
		},
	}

	ctrl := setupTestController(t, mockRunner)
	if ctrl == nil {
		t.Fatal("expected controller to be created, got nil")
	}
}
