// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.

package migration

import (
	"context"
	"fmt"

	"github.com/cosi-project/runtime/pkg/resource"
	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/cosi-project/runtime/pkg/state"
	"go.uber.org/zap"

	"github.com/siderolabs/omni/client/pkg/omni/resources"
	authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
	"github.com/siderolabs/omni/client/pkg/omni/resources/infra"
	"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
	"github.com/siderolabs/omni/client/pkg/omni/resources/siderolink"
	omnictrl "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni"
	"github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni/cluster"
	"github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni/clustermachine"
)

func moveClusterTaintFromResourceToLabel(ctx context.Context, st state.State, _ *zap.Logger, _ migrationContext) error {
	clusterTaints, err := safe.ReaderListAll[*omni.ClusterTaint](ctx, st)
	if err != nil {
		return err
	}

	for taint := range clusterTaints.All() {
		_, err = safe.StateUpdateWithConflicts(ctx, st, omni.NewClusterStatus(taint.Metadata().ID()).Metadata(), func(res *omni.ClusterStatus) error {
			res.Metadata().Labels().Set(omni.LabelClusterTaintedByBreakGlass, "")

			return nil
		}, state.WithExpectedPhaseAny(), state.WithUpdateOwner(omnictrl.ClusterStatusControllerName))
		if err != nil && !state.IsNotFoundError(err) {
			return err
		}
		// cluster status does not exist, taint is dangling, just remove it

		if err = st.TeardownAndDestroy(ctx, taint.Metadata()); err != nil {
			return err
		}
	}

	return err
}

func dropExtraInputFinalizers(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	logger.Info("dropping extra finalizers from MachineSetStatus resources")

	if err := dropFinalizers[*omni.MachineSetStatus](ctx, st, "ClusterDestroyStatusController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from MachineSetStatus resources: %w", err)
	}

	logger.Info("dropping extra finalizers from ClusterMachineStatus resources")

	if err := dropFinalizers[*omni.ClusterMachineStatus](ctx, st, "ClusterDestroyStatusController", "MachineSetDestroyStatusController", "MachineStatusController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from ClusterMachineStatus resources: %w", err)
	}

	logger.Info("dropping extra finalizers from Link resources")

	if err := dropFinalizers[*siderolink.Link](ctx, st, "BMCConfigController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from Link resources: %w", err)
	}

	logger.Info("dropping extra finalizers from MachineSetNode resources")

	if err := dropFinalizers[*omni.MachineSetNode](ctx, st, "ClusterMachineStatusController", "MachineStatusController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from MachineSetNode resources: %w", err)
	}

	logger.Info("dropping extra finalizers from ClusterMachine resources")

	if err := dropFinalizers[*omni.ClusterMachine](ctx, st, "MachineStatusSnapshotController", "InfraMachineController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from ClusterMachine resources: %w", err)
	}

	logger.Info("dropping extra finalizers from InfraMachineConfig resources")

	if err := dropFinalizers[*omni.InfraMachineConfig](ctx, st, "InfraMachineController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from InfraMachineConfig resources: %w", err)
	}

	logger.Info("dropping extra finalizers from MachineExtensions resources")

	if err := dropFinalizers[*omni.MachineExtensions](ctx, st, "InfraMachineController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from MachineExtensions resources: %w", err)
	}

	logger.Info("dropping extra finalizers from MachineStatus resources")

	if err := dropFinalizers[*omni.MachineStatus](ctx, st, "InfraMachineController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from MachineStatus resources: %w", err)
	}

	logger.Info("dropping extra finalizers from NodeUniqueToken resources")

	if err := dropFinalizers[*siderolink.NodeUniqueToken](ctx, st, "InfraMachineController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from NodeUniqueToken resources: %w", err)
	}

	logger.Info("dropping extra finalizers from MachineStatusSnapshot resources")

	if err := dropFinalizers[*omni.MachineStatusSnapshot](ctx, st, "MachineStatusController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from MachineStatusSnapshot resources: %w", err)
	}

	logger.Info("dropping extra finalizers from MachineLabels resources")

	if err := dropFinalizers[*omni.MachineLabels](ctx, st, "MachineStatusController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from MachineLabels resources: %w", err)
	}

	logger.Info("dropping extra finalizers from infra.MachineStatus resources")

	if err := dropFinalizers[*infra.MachineStatus](ctx, st, "MachineStatusController"); err != nil {
		return fmt.Errorf("failed to remove finalizers from infra.MachineStatus resources: %w", err)
	}

	return nil
}

func moveInfraProviderAnnotationsToLabels(ctx context.Context, st state.State, _ *zap.Logger, _ migrationContext) error {
	for _, resType := range []resource.Type{siderolink.LinkType, omni.MachineType} {
		kind := resource.NewMetadata(resources.DefaultNamespace, resType, "", resource.VersionUndefined)

		list, err := st.List(ctx, kind)
		if err != nil {
			return err
		}

		for _, item := range list.Items {
			id, ok := item.Metadata().Annotations().Get(omni.LabelInfraProviderID)
			if !ok {
				continue
			}

			if _, err = st.UpdateWithConflicts(ctx, item.Metadata(), func(res resource.Resource) error {
				res.Metadata().Labels().Set(omni.LabelInfraProviderID, id)
				res.Metadata().Annotations().Delete(omni.LabelInfraProviderID)

				return nil
			}, state.WithUpdateOwner(item.Metadata().Owner()), state.WithExpectedPhaseAny()); err != nil {
				return err
			}
		}
	}

	return nil
}

func dropSchematicConfigFinalizerFromClusterMachines(ctx context.Context, s state.State, _ *zap.Logger, _ migrationContext) error {
	list, err := safe.ReaderListAll[*omni.ClusterMachine](ctx, s)
	if err != nil {
		return err
	}

	for cm := range list.All() {
		if cm.Metadata().Finalizers().Has(omnictrl.SchematicConfigurationControllerName) {
			if _, err = safe.StateUpdateWithConflicts(ctx, s, cm.Metadata(), func(res *omni.ClusterMachine) error {
				res.Metadata().Finalizers().Remove(omnictrl.SchematicConfigurationControllerName)

				return nil
			}, state.WithUpdateOwner(cm.Metadata().Owner()), state.WithExpectedPhaseAny()); err != nil {
				return err
			}
		}
	}

	return nil
}

func dropTalosUpgradeStatusFinalizersFromSchematicConfigs(ctx context.Context, s state.State, _ *zap.Logger, _ migrationContext) error {
	list, err := safe.ReaderListAll[*omni.SchematicConfiguration](ctx, s)
	if err != nil {
		return err
	}

	for sc := range list.All() {
		if sc.Metadata().Finalizers().Has("TalosUpgradeStatusController") {
			if _, err = safe.StateUpdateWithConflicts(ctx, s, sc.Metadata(), func(res *omni.SchematicConfiguration) error {
				res.Metadata().Finalizers().Remove("TalosUpgradeStatusController")

				return nil
			}, state.WithUpdateOwner(sc.Metadata().Owner()), state.WithExpectedPhaseAny()); err != nil {
				return err
			}
		}
	}

	return nil
}

func makeMachineSetNodesOwnerEmpty(ctx context.Context, st state.State, _ *zap.Logger, _ migrationContext) error {
	machineSetNodes, err := safe.ReaderListAll[*omni.MachineSetNode](ctx, st)
	if err != nil {
		return err
	}

	for machineSetNode := range machineSetNodes.All() {
		if machineSetNode.Metadata().Owner() != omnictrl.NewMachineSetNodeController().ControllerName {
			continue
		}

		machineSetNode.Metadata().Labels().Set(omni.LabelManagedByMachineSetNodeController, "")

		if err = changeOwner(ctx, st, machineSetNode, ""); err != nil {
			return err
		}
	}

	return nil
}

func changeClusterMachineConfigPatchesOwner(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	clusterMachineConfigPatches, err := safe.ReaderListAll[*omni.ClusterMachineConfigPatches](ctx, st)
	if err != nil {
		return err
	}

	controllerName := clustermachine.NewConfigPatchesController().ControllerName

	for cmcp := range clusterMachineConfigPatches.All() {
		if err = changeOwner(ctx, st, cmcp, controllerName); err != nil {
			return err
		}
	}

	return nil
}

func createIdentityLastActiveForExistingIdentities(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	identities, err := safe.ReaderListAll[*authres.Identity](ctx, st)
	if err != nil {
		return err
	}

	var created int

	for identity := range identities.All() {
		identityLastActive := authres.NewIdentityLastActive(identity.Metadata().ID())

		if err = st.Create(ctx, identityLastActive); err != nil {
			if state.IsConflictError(err) {
				continue
			}

			return fmt.Errorf("failed to create IdentityLastActive for %q: %w", identity.Metadata().ID(), err)
		}

		created++
	}

	logger.Info("created IdentityLastActive resources for existing identities", zap.Int("created", created))

	return nil
}

func moveSchematicCacheToEphemeral(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	kind := resource.NewMetadata(
		resources.DefaultNamespace,
		omni.SchematicType,
		"",
		resource.VersionUndefined,
	)

	list, err := st.List(ctx, kind)
	if err != nil {
		return err
	}

	logger.Info("cleaning up schematics from default namespace", zap.Int("count", len(list.Items)))

	for _, item := range list.Items {
		err = st.Destroy(ctx, item.Metadata(), state.WithDestroyOwner(item.Metadata().Owner()))
		if err != nil && !state.IsNotFoundError(err) {
			return err
		}
	}

	return nil
}

func dropTalosUpgradeStatusFinalizers(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	logger.Info("dropping TalosUpgradeStatusController finalizers from ClusterMachine resources")

	return dropFinalizers[*omni.ClusterMachine](ctx, st, "TalosUpgradeStatusController")
}

func dropRedactedClusterMachineConfigFinalizers(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	logger.Info("dropping RedactedClusterMachineConfigController finalizers from ClusterMachineConfig resources")

	return dropFinalizers[*omni.ClusterMachineConfig](ctx, st, "RedactedClusterMachineConfigController")
}

func removeStaleIdentityLastActiveResources(ctx context.Context, st state.State, logger *zap.Logger, _ migrationContext) error {
	identityLastActives, err := safe.ReaderListAll[*authres.IdentityLastActive](ctx, st)
	if err != nil {
		return err
	}

	identities, err := safe.ReaderListAll[*authres.Identity](ctx, st)
	if err != nil {
		return err
	}

	identitySet := make(map[resource.ID]struct{}, identities.Len())

	for identity := range identities.All() {
		identitySet[identity.Metadata().ID()] = struct{}{}
	}

	var removed int

	for ila := range identityLastActives.All() {
		if _, identityOk := identitySet[ila.Metadata().ID()]; identityOk { // exists, continue
			continue
		}

		// Strip any finalizers defensively before destroying.
		if !ila.Metadata().Finalizers().Empty() {
			if _, err = safe.StateUpdateWithConflicts(ctx, st, ila.Metadata(), func(res *authres.IdentityLastActive) error {
				for _, f := range *res.Metadata().Finalizers() {
					res.Metadata().Finalizers().Remove(f)
				}

				return nil
			}, state.WithUpdateOwner(ila.Metadata().Owner()), state.WithExpectedPhaseAny()); err != nil {
				return fmt.Errorf("failed to strip finalizers from IdentityLastActive %q: %w", ila.Metadata().ID(), err)
			}
		}

		if err = st.TeardownAndDestroy(ctx, ila.Metadata(), state.WithTeardownAndDestroyOwner(ila.Metadata().Owner())); err != nil {
			if state.IsNotFoundError(err) {
				continue
			}

			return fmt.Errorf("failed to destroy IdentityLastActive %q: %w", ila.Metadata().ID(), err)
		}

		removed++
	}

	logger.Info("removed stale IdentityLastActive resources", zap.Int("removed", removed))

	return nil
}

func dropWorkloadProxyConfigPatches(ctx context.Context, st state.State, _ *zap.Logger, _ migrationContext) error {
	configPatches, err := safe.ReaderListAll[*omni.ConfigPatch](ctx, st)
	if err != nil {
		return err
	}

	ctrl, err := cluster.NewClusterWorkloadProxyController(true)
	if err != nil {
		return err
	}

	controllerName := ctrl.ControllerName

	for patch := range configPatches.All() {
		if patch.Metadata().Owner() != controllerName {
			continue
		}

		if _, err = safe.StateUpdateWithConflicts(ctx, st, patch.Metadata(), func(r *omni.ConfigPatch) error {
			for _, f := range *r.Metadata().Finalizers() {
				r.Metadata().Finalizers().Remove(f)
			}

			return nil
		}, state.WithUpdateOwner(controllerName)); err != nil {
			return err
		}

		if err = st.Destroy(ctx, patch.Metadata(), state.WithDestroyOwner(controllerName)); err != nil {
			return err
		}
	}

	return nil
}

func makeMachineRequestsOwnerEmpty(ctx context.Context, st state.State, _ *zap.Logger, _ migrationContext) error {
	machineRequests, err := safe.ReaderListAll[*infra.MachineRequest](ctx, st)
	if err != nil {
		return err
	}

	for machineRequest := range machineRequests.All() {
		if machineRequest.Metadata().Owner() == "" {
			continue
		}

		if err = changeOwner(ctx, st, machineRequest, ""); err != nil {
			return err
		}
	}

	return nil
}
