// 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

import (
	"errors"
	"fmt"
	"strings"

	"github.com/eminwux/kukeon/internal/consts"
	"github.com/eminwux/kukeon/internal/errdefs"
	intmodel "github.com/eminwux/kukeon/internal/modelhub"
)

// PurgeRealmResult reports what was purged during realm purging.
type PurgeRealmResult struct {
	Realm            intmodel.Realm
	RealmDeleted     bool     // Whether realm deletion succeeded
	PurgeSucceeded   bool     // Whether comprehensive purge succeeded
	NamespaceRemoved bool     // Whether the containerd namespace was actually removed
	Force            bool     // Force flag that was used
	Cascade          bool     // Cascade flag that was used
	Deleted          []string // Resources that were deleted (standard cleanup)
	Purged           []string // Additional resources purged (CNI, orphaned containers, etc.)
}

// PurgeRealm purges a realm with comprehensive cleanup. If cascade is true, purges all spaces first.
// If force is true, skips validation of child resources.
func (b *Exec) PurgeRealm(realm intmodel.Realm, force, cascade bool) (PurgeRealmResult, error) {
	var result PurgeRealmResult

	name := strings.TrimSpace(realm.Metadata.Name)
	if name == "" {
		return result, errdefs.ErrRealmNameRequired
	}

	// Default namespace to <name>.kukeon.io if not set (matching CreateRealm behavior)
	namespace := strings.TrimSpace(realm.Spec.Namespace)
	if namespace == "" {
		namespace = consts.RealmNamespace(name)
		realm.Spec.Namespace = namespace
	}

	// Determine which realm to use: from metadata if available, otherwise use provided realm
	var internalRealm intmodel.Realm
	var metadataExists bool
	internalRealm, err := b.runner.GetRealm(realm)
	if err != nil {
		if errors.Is(err, errdefs.ErrRealmNotFound) {
			// Metadata doesn't exist - construct realm from input with default namespace
			metadataExists = false
			internalRealm = intmodel.Realm{
				Metadata: intmodel.RealmMetadata{
					Name:   name,
					Labels: realm.Metadata.Labels,
				},
				Spec: intmodel.RealmSpec{
					Namespace: namespace,
				},
				Status: intmodel.RealmStatus{
					State: intmodel.RealmStateUnknown,
				},
			}
		} else {
			return result, err
		}
	} else {
		metadataExists = true
		// Ensure namespace is set (use from metadata if available, otherwise use default)
		if internalRealm.Spec.Namespace == "" {
			internalRealm.Spec.Namespace = namespace
		}
	}

	// Initialize result with realm and flags
	result = PurgeRealmResult{
		Realm:   internalRealm,
		Force:   force,
		Cascade: cascade,
		Deleted: []string{},
		Purged:  []string{},
	}

	// Track deleted spaces for result building (list before deletion to know what will be deleted)
	if cascade && metadataExists {
		spaces, listErr := b.runner.ListSpaces(name)
		if listErr != nil {
			return result, fmt.Errorf("failed to list spaces: %w", listErr)
		}
		// Track spaces that will be deleted
		for _, space := range spaces {
			result.Deleted = append(result.Deleted, fmt.Sprintf("space:%s", space.Metadata.Name))
		}
	}

	// Call private cascade method (handles cascade deletion, standard delete, and comprehensive purge)
	namespaceRemoved, err := b.purgeRealmCascade(internalRealm, force, cascade, metadataExists)
	result.NamespaceRemoved = namespaceRemoved
	if err != nil {
		result.Purged = append(result.Purged, fmt.Sprintf("purge-error:%v", err))
		result.PurgeSucceeded = false
		// If metadata exists and delete failed, mark as not deleted
		if metadataExists {
			result.RealmDeleted = false
		}
		return result, err
	}

	// Since private method succeeded, assume all operations succeeded
	if metadataExists {
		result.RealmDeleted = true
		result.Deleted = append(result.Deleted, "metadata", "cgroup", "namespace")
	} else {
		result.RealmDeleted = false
	}
	result.Purged = append(result.Purged, "orphaned-containers", "cni-resources", "all-metadata")
	result.PurgeSucceeded = true

	return result, nil
}

// purgeRealmCascade handles cascade deletion and purging logic using runner methods directly.
// It returns whether the containerd namespace was actually removed and an
// error if deletion/purging failed. metadataExists indicates whether realm
// metadata exists (affects cascade and delete operations).
func (b *Exec) purgeRealmCascade(realm intmodel.Realm, force, cascade, metadataExists bool) (bool, error) {
	realmName := strings.TrimSpace(realm.Metadata.Name)

	// If cascade is true, list and purge child resources (spaces) recursively (only if metadata exists)
	if cascade && metadataExists {
		spaces, err := b.runner.ListSpaces(realmName)
		if err != nil {
			return false, fmt.Errorf("failed to list spaces: %w", err)
		}
		for _, space := range spaces {
			if err = b.purgeSpaceCascade(space, force, cascade); err != nil {
				return false, fmt.Errorf("failed to purge space %q: %w", space.Metadata.Name, err)
			}
		}
	} else if !force && metadataExists {
		// Validate no child resources exist (only if metadata exists)
		spaces, err := b.runner.ListSpaces(realmName)
		if err != nil {
			return false, fmt.Errorf("failed to list spaces: %w", err)
		}
		if len(spaces) > 0 {
			return false, fmt.Errorf("%w: realm %q has %d space(s). Use --cascade to purge them or --force to skip validation",
				errdefs.ErrResourceHasDependencies, realmName, len(spaces))
		}
	}

	// Perform standard delete first (only if metadata exists, as deleteRealmCascade requires metadata).
	// If it fails (e.g. DeleteNamespace can't remove a namespace whose snapshots
	// are still pinned by a live task), still fall through to runner.PurgeRealm
	// so the comprehensive cleanup runs in a single pass — runner.PurgeRealm
	// is idempotent and goes further (orphaned containers, CNI tear-down, etc.),
	// so a single failed deleteRealmCascade should not strand the user with a
	// half-cleaned realm that requires re-running the command. If runner.PurgeRealm
	// later succeeds, the realm is fully gone and the original delete error is
	// no longer load-bearing — log it and treat the operation as a success.
	var deleteErr error
	if metadataExists {
		if err := b.deleteRealmCascade(realm, force, cascade); err != nil {
			deleteErr = fmt.Errorf("failed to delete realm: %w", err)
			b.logger.WarnContext(
				b.ctx,
				"deleteRealmCascade failed; falling through to runner.PurgeRealm",
				"realm",
				realmName,
				"error",
				err,
			)
		}
	}

	// Perform comprehensive purge via runner (works even without metadata).
	// Note: namespaceRemoved is the load-bearing signal — even when purgeErr
	// is nil, a residual namespace surfaces here so callers can render
	// "namespace not empty" instead of a misleading "purged" outcome.
	namespaceRemoved, purgeErr := b.runner.PurgeRealm(realm)
	if purgeErr != nil {
		if deleteErr != nil {
			return namespaceRemoved, fmt.Errorf("%w; runner purge also failed: %w", deleteErr, purgeErr)
		}
		return namespaceRemoved, purgeErr
	}
	return namespaceRemoved, nil
}
