/*
 * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 *
 * This work is published and licensed under a Creative Commons
 * Attribution-NonCommercial-NoDerivatives 4.0 International License.
 *
 * Distribution of this work is permitted, but commercial use and
 * modifications are strictly prohibited.
 */
package main

import (
	"context"
	"net/url"
	"os"
	"path"
	"strings"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"mstore/pkg/client"
)

func packUserinfo(resurseuri, username, password string) (string, error) {
	var err error
	var res string
	if !strings.Contains(resurseuri, "://") {
		resurseuri = "https://" + resurseuri
	}
	uri, err := url.Parse(resurseuri)
	if err != nil {
		return res, err
	}
	uri.Path = path.Clean(uri.Path)
	if username != "" && password != "" {
		uri.User = url.UserPassword(username, password)
	}
	res = uri.String()
	return res, err
}

func (util *ImageUtil) CreateImageCmds() *cobra.Command {
	const defaultTimeout uint64 = 30 // Second

	var subCmd = &cobra.Command{
		Use:     "images",
		Short:   "Image operations",
		Aliases: []string{"image"},
	}
	subCmd.PersistentFlags().StringVarP(&util.commonImageParams.Username, "user", "u", "", "Username")
	subCmd.PersistentFlags().StringVarP(&util.commonImageParams.Password, "pass", "p", "", "Password")
	subCmd.PersistentFlags().Uint64VarP(&util.commonImageParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout")
	subCmd.PersistentFlags().BoolVarP(&util.commonImageParams.SkipTLSVerify, "skipVerify", "S", true, "Skip server certificate verify")
	subCmd.MarkFlagsRequiredTogether("user", "pass")

	vi := viper.New()
	vi.SetEnvPrefix("mstore")
	vi.BindEnv("user")
	vi.BindEnv("pass")
	util.commonImageParams.Username = vi.GetString("user")
	util.commonImageParams.Password = vi.GetString("pass")

	// PushImage
	var pushImageCmd = &cobra.Command{
		Use:   "push filename [user:pass@]hostname[:port]/path:tag",
		Short: "Push container image from local tar file into registry",
		Args:  cobra.ExactArgs(2),
		Run:   util.PushImage,
	}
	subCmd.AddCommand(pushImageCmd)

	// ImageInfo
	var imageInfoCmd = &cobra.Command{
		Use:   "info [user:pass@]hostname[:port]/path:tag",
		Short: "Show container image info",
		Args:  cobra.ExactArgs(1),
		Run:   util.ImageInfo,
	}
	subCmd.AddCommand(imageInfoCmd)

	// PullImage
	var pullImageCmd = &cobra.Command{
		Use:   "pull [user:pass@]hostname[:port]/path:tag filename",
		Short: "Pull container image from registry into local file",
		Args:  cobra.ExactArgs(2),
		Run:   util.PullImage,
	}
	subCmd.AddCommand(pullImageCmd)

	// DeleteImage
	var deleteImageCmd = &cobra.Command{
		Use:   "delete [user:pass@]hostname[:port]/path:tag",
		Short: "Delete container image from registry",
		Args:  cobra.ExactArgs(1),
		Run:   util.DeleteImage,
	}
	subCmd.AddCommand(deleteImageCmd)

	return subCmd
}

type ImageUtil struct {
	imageInfoParams   ImageInfoParams
	pullImageParams   PullImageParams
	pushImageParams   PushImageParams
	deleteImageParams DeleteImageParams
	commonImageParams CommonImageParams
}

type CommonImageParams struct {
	Timeout       uint64
	Username      string
	Password      string
	SkipTLSVerify bool
}

// PushImage
type PushImageParams struct {
	Imagepath string
	Filepath  string
}

type PushImageResult struct{}

func (util *ImageUtil) PushImage(cmd *cobra.Command, args []string) {
	util.pushImageParams.Filepath = args[0]
	util.pushImageParams.Imagepath = args[1]

	res, err := util.pushImage(&util.commonImageParams, &util.pushImageParams)
	printResponse(res, err)
}

func (util *ImageUtil) pushImage(common *CommonImageParams, params *PushImageParams) (*PushImageResult, error) {
	var err error
	ctx := context.Background()
	res := &PushImageResult{}

	cli := client.NewClient(common.SkipTLSVerify)
	timeout := time.Duration(common.Timeout) * time.Second
	params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	ctx, _ = context.WithTimeout(ctx, timeout)
	err = cli.PushImage(ctx, params.Filepath, params.Imagepath)
	if err != nil {
		return res, err
	}
	return res, err
}

// ImageInfo
type ImageInfoParams struct {
	Imagepath string
}

type ImageInfoResult struct {
	ImageInfo *client.ImageDescr `yaml:"imageInfo"`
}

func (util *ImageUtil) ImageInfo(cmd *cobra.Command, args []string) {
	util.imageInfoParams.Imagepath = args[0]
	res, err := util.imageInfo(&util.commonImageParams, &util.imageInfoParams)
	printResponse(res, err)
}

func (util *ImageUtil) imageInfo(common *CommonImageParams, params *ImageInfoParams) (*ImageInfoResult, error) {
	var err error
	res := &ImageInfoResult{}
	ctx := context.Background()

	cli := client.NewClient(common.SkipTLSVerify)
	timeout := time.Duration(common.Timeout) * time.Second

	params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	ctx, _ = context.WithTimeout(ctx, timeout)
	opres, err := cli.ImageInfo(ctx, params.Imagepath)
	if err != nil {
		return res, err
	}
	res.ImageInfo = opres
	return res, err
}

// PullImage
type PullImageParams struct {
	Imagepath string
	Filepath  string
}

type PullImageResult struct {
	Filepath string `yaml:"filepath"`
	Size     int64  `yaml:"size"`
}

func (util *ImageUtil) PullImage(cmd *cobra.Command, args []string) {
	util.pullImageParams.Imagepath = args[0]
	util.pullImageParams.Filepath = args[1]
	res, err := util.pullImage(&util.commonImageParams, &util.pullImageParams)
	printResponse(res, err)
}

func (util *ImageUtil) pullImage(common *CommonImageParams, params *PullImageParams) (*PullImageResult, error) {
	var err error

	ctx := context.Background()
	res := &PullImageResult{}

	cli := client.NewClient(common.SkipTLSVerify)
	timeout := time.Duration(common.Timeout) * time.Second
	params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	ctx, _ = context.WithTimeout(ctx, timeout)
	err = cli.PullImage(ctx, params.Imagepath, params.Filepath)
	if err != nil {
		return res, err
	}
	filestat, err := os.Stat(params.Filepath)
	if err != nil {
		return res, err
	}
	res.Size = filestat.Size()
	res.Filepath = params.Filepath

	return res, err
}

// DeleteImage
type DeleteImageParams struct {
	Imagepath string
}

type DeleteImageResult struct {
}

func (util *ImageUtil) DeleteImage(cmd *cobra.Command, args []string) {
	util.deleteImageParams.Imagepath = args[0]
	res, err := util.deleteImage(&util.commonImageParams, &util.deleteImageParams)
	printResponse(res, err)
}

func (util *ImageUtil) deleteImage(common *CommonImageParams, params *DeleteImageParams) (*DeleteImageResult, error) {
	var err error
	res := &DeleteImageResult{}
	ctx := context.Background()

	cli := client.NewClient(common.SkipTLSVerify)
	timeout := time.Duration(common.Timeout) * time.Second

	params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	ctx, _ = context.WithTimeout(ctx, timeout)
	err = cli.DeleteImage(ctx, params.Imagepath)
	if err != nil {
		return res, err
	}
	return res, err
}
