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

import (
	"errors"
	"fmt"
	"strings"

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

// DeleteContainer stops and deletes a specific container in a cell from containerd.
func (r *Exec) DeleteContainer(cell intmodel.Cell, containerID string) error {
	containerID = strings.TrimSpace(containerID)
	if containerID == "" {
		return errors.New("container ID is required")
	}

	cellName := strings.TrimSpace(cell.Metadata.Name)
	if cellName == "" {
		return errdefs.ErrCellNameRequired
	}

	cellID := strings.TrimSpace(cell.Spec.ID)
	if cellID == "" {
		return errdefs.ErrCellIDRequired
	}

	realmName := strings.TrimSpace(cell.Spec.RealmName)
	if realmName == "" {
		return errdefs.ErrRealmNameRequired
	}

	spaceName := strings.TrimSpace(cell.Spec.SpaceName)
	if spaceName == "" {
		return errdefs.ErrSpaceNameRequired
	}

	stackName := strings.TrimSpace(cell.Spec.StackName)
	if stackName == "" {
		return errdefs.ErrStackNameRequired
	}

	if err := r.ensureClientConnected(); err != nil {
		return fmt.Errorf("%w: %w", errdefs.ErrConnectContainerd, err)
	}

	// Get realm to access namespace
	lookupRealm := intmodel.Realm{
		Metadata: intmodel.RealmMetadata{
			Name: realmName,
		},
	}
	internalRealm, err := r.GetRealm(lookupRealm)
	if err != nil {
		return fmt.Errorf("failed to get realm: %w", err)
	}

	// Find container in cell spec by ID (base name)
	var foundContainerSpec *intmodel.ContainerSpec
	for i := range cell.Spec.Containers {
		if cell.Spec.Containers[i].ID == containerID {
			foundContainerSpec = &cell.Spec.Containers[i]
			break
		}
	}

	if foundContainerSpec == nil {
		return fmt.Errorf("container %q not found in cell %q", containerID, cellName)
	}

	// Root container cannot be deleted directly - it must be deleted by deleting the cell
	if foundContainerSpec.Root {
		return fmt.Errorf(
			"root container cannot be deleted directly, delete the cell instead using 'kuke delete cell %s'",
			cellName,
		)
	}

	// Use ContainerdID from spec
	containerdID := foundContainerSpec.ContainerdID
	if containerdID == "" {
		return fmt.Errorf("container %q has empty ContainerdID", containerID)
	}

	// Get space for network name
	lookupSpace := intmodel.Space{
		Metadata: intmodel.SpaceMetadata{
			Name: spaceName,
		},
		Spec: intmodel.SpaceSpec{
			RealmName: realmName,
		},
	}
	space, spaceErr := r.GetSpace(lookupSpace)
	var networkName string
	if spaceErr == nil {
		networkName, _ = r.getSpaceNetworkName(space)
	}

	// Comprehensive CNI cleanup before stopping/deleting
	netnsPath, _ := r.getContainerNetnsPath(internalRealm.Spec.Namespace, containerdID)
	_ = r.purgeCNIForContainer(containerdID, netnsPath, networkName)

	// Stop the container using containerd ID
	_, err = r.ctrClient.StopContainer(internalRealm.Spec.Namespace, containerdID, ctr.StopContainerOptions{})
	if err != nil {
		r.logger.WarnContext(
			r.ctx,
			"failed to stop container, continuing with deletion",
			"container",
			containerID,
			"containerdID",
			containerdID,
			"error",
			err,
		)
	}

	// Delete the container from containerd using containerd ID
	err = r.ctrClient.DeleteContainer(internalRealm.Spec.Namespace, containerdID, ctr.ContainerDeleteOptions{
		SnapshotCleanup: true,
	})
	if err != nil {
		// Check if container doesn't exist - this is idempotent (already deleted)
		if errors.Is(err, errdefs.ErrContainerNotFound) {
			// Container already deleted, treat as success
			fields := appendCellLogFields([]any{"id", containerdID}, cellID, cellName)
			fields = append(fields, "space", spaceName, "realm", realmName, "containerName", containerID)
			r.logger.DebugContext(
				r.ctx,
				"container already deleted",
				fields...,
			)
			// Still run cleanup in case there are orphaned CNI resources
			netnsPath = ""
			if networkName != "" {
				_ = r.purgeCNIForContainer(containerdID, netnsPath, networkName)
			}
			return nil
		}

		// Other errors are actual failures
		fields := appendCellLogFields([]any{"id", containerdID}, cellID, cellName)
		fields = append(
			fields,
			"space",
			spaceName,
			"realm",
			realmName,
			"containerName",
			containerID,
			"err",
			fmt.Sprintf("%v", err),
		)
		r.logger.ErrorContext(
			r.ctx,
			"failed to delete container",
			fields...,
		)
		return fmt.Errorf("failed to delete container %s: %w", containerID, err)
	}

	fields := appendCellLogFields([]any{"id", containerdID}, cellID, cellName)
	fields = append(fields, "space", spaceName, "realm", realmName, "containerName", containerID)
	r.logger.InfoContext(
		r.ctx,
		"deleted container",
		fields...,
	)

	// Always run comprehensive CNI cleanup after container deletion as a safety net
	// Note: Workload containers share the root container's network namespace, so they don't
	// need individual CNI cleanup, but we run this anyway to catch any edge cases
	netnsPath = ""
	if networkName != "" {
		_ = r.purgeCNIForContainer(containerdID, netnsPath, networkName)
	}

	// Unlink the SUN_PATH-safe socket symlink staged at provision time
	// (issue #521). The symlink lives outside the cell metadata tree, so
	// the CellMetadataDir RemoveAll path that delete_cell.go runs doesn't
	// reach it; a missing dirent is fine — the helper is idempotent.
	if symlinkErr := removeAttachableSocketSymlink(r.opts.RunPath, *foundContainerSpec); symlinkErr != nil {
		r.logger.WarnContext(
			r.ctx,
			"failed to remove socket symlink",
			"container", containerID,
			"error", symlinkErr,
		)
	}

	return nil
}
