package cmd

import (
	"bufio"
	"fmt"
	"strings"

	"nest/src/config"
	"nest/src/detect"
	"nest/src/output"
	nestruntime "nest/src/runtime"
)

const cleanupUsage = `Usage: nest cleanup [--running] [--images] [--dry-run] [--yes]
                    [--runtime podman|docker] [--catalog path] [--json]

Remove nest-managed container resources. Only resources carrying the
io.parrot.nest.managed=true label (and, with --images, images referenced by
the catalog) are touched. Default: remove stopped containers. Volumes are
never pruned (AGENTS.md §4.5).
`

func (a *App) cmdCleanup(cfg *config.Config, args []string) int {
	fs := a.newFlagSet("cleanup", cleanupUsage)
	running := fs.Bool("running", false, "also stop and remove running containers (asks confirmation)")
	images := fs.Bool("images", false, "also remove catalog-referenced images present locally")
	dryRun := fs.Bool("dry-run", false, "print what would be removed; change nothing")
	yes := fs.Bool("yes", false, "do not ask for confirmation")
	runtimeFlag := fs.String("runtime", "", "container runtime: podman or docker")
	jsonOut := fs.Bool("json", false, "JSON output (stable schema, see AGENTS.md §4.8)")
	catalogFlag := fs.String("catalog", "", "use the catalog at path (development/testing)")
	if ok, code := a.parseFlags(fs, args); !ok {
		return code
	}

	if detect.InContainer(a.Getenv) {
		fmt.Fprintf(a.Stderr, "nest: cleanup is not available inside a container\n")
		return exitRuntimeFail
	}
	rt, code := a.resolveRuntime(cfg, *runtimeFlag)
	if code != exitOK {
		return code
	}

	containers, err := rt.ListManaged()
	if err != nil {
		fmt.Fprintf(a.Stderr, "nest: %v\n", err)
		return exitRuntimeFail
	}
	var stopped, active []nestruntime.Container
	for _, c := range containers {
		if c.State == "running" {
			active = append(active, c)
		} else {
			stopped = append(stopped, c)
		}
	}

	if *running && len(active) > 0 && !*dryRun && !*yes {
		if !a.StdinTTY {
			fmt.Fprintf(a.Stderr, "nest: refusing to stop %d running container(s) without confirmation; pass --yes\n", len(active))
			return exitUsage
		}
		fmt.Fprintf(a.Stderr, "Stop and remove %d running nest-managed container(s)? [y/N] ", len(active))
		if !confirm(a.Stdin) {
			fmt.Fprintln(a.Stderr, "nest: aborted")
			return exitOK
		}
	}

	result := output.Cleanup{
		DryRun:            *dryRun,
		ContainersRemoved: []string{},
		ImagesRemoved:     []string{},
	}
	targets := stopped
	if *running {
		targets = append(targets, active...)
	}
	for _, c := range targets {
		if !*dryRun {
			if err := removeContainer(rt, c); err != nil {
				fmt.Fprintf(a.Stderr, "nest: %v\n", err)
				continue
			}
		}
		result.ContainersRemoved = append(result.ContainersRemoved, c.ID)
	}

	if *images {
		a.cleanupImages(rt, *catalogFlag, *dryRun, &result)
	}

	if *jsonOut {
		if err := output.JSON(a.Stdout, result); err != nil {
			fmt.Fprintf(a.Stderr, "nest: cannot render JSON: %v\n", err)
			return exitError
		}
		return exitOK
	}
	a.printCleanup(&result)
	return exitOK
}

func removeContainer(rt *nestruntime.Runtime, c nestruntime.Container) error {
	if c.State == "running" {
		if err := rt.Stop(c.ID); err != nil {
			return fmt.Errorf("cannot stop container %s: %w", c.ID, err)
		}
	}
	if err := rt.Remove(c.ID); err != nil {
		return fmt.Errorf("cannot remove container %s: %w", c.ID, err)
	}
	return nil
}

func (a *App) cleanupImages(rt *nestruntime.Runtime, catalogFlag string, dryRun bool, result *output.Cleanup) {
	cat, _, code := a.loadCatalog(catalogFlag)
	if code != exitOK {
		fmt.Fprintf(a.Stderr, "nest: cannot load catalog; skipping image removal\n")
		return
	}
	// TODO: ask for interactive/--yes confirmation before removing images
	// (AGENTS.md §4.5); image deletion is irreversible without a re-pull.
	seen := map[string]bool{}
	for i := range cat.Tools {
		for _, image := range cat.Tools[i].Images {
			if seen[image] {
				continue
			}
			seen[image] = true
			if dryRun {
				result.ImagesRemoved = append(result.ImagesRemoved, image)
				continue
			}
			if err := rt.RemoveImage(image); err != nil {
				fmt.Fprintf(a.Stderr, "nest: cannot remove image %s: %v\n", image, err)
				continue
			}
			result.ImagesRemoved = append(result.ImagesRemoved, image)
		}
	}
}

func (a *App) printCleanup(result *output.Cleanup) {
	verb := "removed"
	if result.DryRun {
		verb = "would remove"
	}
	if len(result.ContainersRemoved) == 0 && len(result.ImagesRemoved) == 0 {
		fmt.Fprintln(a.Stdout, "nothing to clean up")
		return
	}
	for _, id := range result.ContainersRemoved {
		fmt.Fprintf(a.Stdout, "%s container %s\n", verb, id)
	}
	for _, image := range result.ImagesRemoved {
		fmt.Fprintf(a.Stdout, "%s image %s\n", verb, image)
	}
}

// confirm reads one line and accepts only an explicit y/yes.
func confirm(r interface{ Read([]byte) (int, error) }) bool {
	line, _ := bufio.NewReader(r).ReadString('\n')
	line = strings.TrimSpace(strings.ToLower(line))
	return line == "y" || line == "yes"
}
