package cmd

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/openeuler/Conch/internal/cli/client"
	conchimage "github.com/openeuler/Conch/internal/image"
	"github.com/openeuler/Conch/internal/runtimeapi"
)

type stringSliceFlag []string

func (s *stringSliceFlag) String() string {
	return strings.Join(*s, ",")
}

func (s *stringSliceFlag) Set(value string) error {
	*s = append(*s, value)
	return nil
}

func printImageHelp(out io.Writer) {
	fmt.Fprintln(out, "Usage:")
	fmt.Fprintln(out, "  conch image <command> [options]")
	fmt.Fprintln(out, "")
	fmt.Fprintln(out, "Commands:")
	fmt.Fprintln(out, "  pull    Pull an image into the local content store.")
	fmt.Fprintln(out, "  push    Push an image to a registry.")
	fmt.Fprintln(out, "  ls      List images from conchd/containerd.")
	fmt.Fprintln(out, "  rm      Remove an image from conchd/containerd.")
	fmt.Fprintln(out, "")
	fmt.Fprintln(out, "Run 'conch image <command> --help' for command-specific usage.")
}

func RunImage(ctx context.Context, args []string) error {
	if len(args) == 0 || args[0] == "-h" || args[0] == "--help" {
		printImageHelp(os.Stdout)
		return nil
	}
	switch args[0] {
	case "pull":
		if len(args) >= 2 && (args[1] == "-h" || args[1] == "--help") {
			PrintImagePullHelp(os.Stdout)
			return nil
		}
		return RunImagePull(ctx, args[1:])
	case "push":
		if len(args) >= 2 && (args[1] == "-h" || args[1] == "--help") {
			PrintImagePushHelp(os.Stdout)
			return nil
		}
		return RunImagePush(ctx, args[1:])
	case "ls":
		return runImageList(ctx, args[1:])
	case "rm":
		return runImageRemove(ctx, args[1:])
	default:
		printImageHelp(os.Stderr)
		return fmt.Errorf("unknown image command %q", args[0])
	}
}

func runImageList(ctx context.Context, args []string) error {
	fs := flag.NewFlagSet("image ls", flag.ContinueOnError)
	fs.SetOutput(os.Stderr)
	configPath := fs.String("config", "", "config file path")
	showAll := fs.Bool("all", false, "show internal containerd image records")
	var filters stringSliceFlag
	fs.Var(&filters, "filter", "containerd image filter")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if fs.NArg() != 0 {
		return fmt.Errorf("conch image ls: unexpected positional arguments: %v", fs.Args())
	}
	conchClient, err := client.New(client.Options{ConfigPath: *configPath})
	if err != nil {
		return fmt.Errorf("conch image ls: create API client: %w", err)
	}
	images, err := conchClient.ListImages(ctx, client.ListImagesRequest{
		Filters: filters,
	})
	if err != nil {
		return fmt.Errorf("conch image ls: %w", err)
	}
	return printImageList(os.Stdout, images, *showAll)
}

func printImageList(out io.Writer, images []client.ImageRecord, showAll bool) error {
	tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
	fmt.Fprintln(tw, "NAME\tKIND\tDIGEST\tSIZE")
	for _, image := range images {
		if !showAll && isInternalImageRecord(image) {
			continue
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%d\n", image.Name, displayImageKind(image.Kind), image.TargetDigest, image.Size)
	}
	return tw.Flush()
}

func isInternalImageRecord(image client.ImageRecord) bool {
	switch strings.TrimSpace(image.Kind) {
	case runtimeapi.ImageKindBootComponentRootfs,
		runtimeapi.ImageKindBootComponentSandbox,
		runtimeapi.ImageKindBootComponentMemory:
		return true
	}
	name := strings.TrimSpace(image.Name)
	return strings.HasPrefix(name, conchimage.TemplateRecordNamePrefix) ||
		strings.HasPrefix(name, "conch-erofs-rootfs:")
}

func displayImageKind(kind string) string {
	if strings.TrimSpace(kind) == "" {
		return runtimeapi.ImageKindOCIImage
	}
	return kind
}

func runImageRemove(ctx context.Context, args []string) error {
	fs := flag.NewFlagSet("image rm", flag.ContinueOnError)
	fs.SetOutput(os.Stderr)
	configPath := fs.String("config", "", "config file path")
	synchronous := fs.Bool("sync", true, "delete the containerd image record synchronously")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if fs.NArg() != 1 {
		return fmt.Errorf("conch image rm: exactly one image name is required")
	}
	imageName := fs.Arg(0)
	conchClient, err := client.New(client.Options{ConfigPath: *configPath})
	if err != nil {
		return fmt.Errorf("conch image rm: create API client: %w", err)
	}
	if err := conchClient.RemoveImage(ctx, client.RemoveImageRequest{
		ImageName:   imageName,
		Synchronous: *synchronous,
	}); err != nil {
		return fmt.Errorf("conch image rm: %w", err)
	}
	fmt.Fprintf(os.Stdout, "Removed image: %s\n", imageName)
	return nil
}