package cmd

import (
	_ "embed"
	"fmt"
	"net/http"
	"net/url"
	"os"

	"github.com/hetznercloud/hcloud-go/v2/hcloud"
	"github.com/spf13/cobra"

	"github.com/apricote/hcloud-upload-image/hcloudimages"
	"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
)

const (
	uploadFlagImageURL     = "image-url"
	uploadFlagImagePath    = "image-path"
	uploadFlagCompression  = "compression"
	uploadFlagFormat       = "format"
	uploadFlagArchitecture = "architecture"
	uploadFlagServerType   = "server-type"
	uploadFlagDescription  = "description"
	uploadFlagLabels       = "labels"
)

//go:embed upload.md
var longDescription string

// uploadCmd represents the upload command
var uploadCmd = &cobra.Command{
	Use:   "upload (--image-path=<local-path> | --image-url=<url>) --architecture=<x86|arm>",
	Short: "Upload the specified disk image into your Hetzner Cloud project.",
	Long:  longDescription,
	Example: `  hcloud-upload-image upload --image-path /home/you/images/custom-linux-image-x86.bz2 --architecture x86 --compression bz2 --description "My super duper custom linux"
  hcloud-upload-image upload --image-url https://examples.com/image-arm.raw --architecture arm --labels foo=bar,version=latest
  hcloud-upload-image upload --image-url https://examples.com/image-x86.qcow2 --architecture x86 --format qcow2`,
	DisableAutoGenTag: true,

	GroupID: "primary",

	PreRun: initClient,

	RunE: func(cmd *cobra.Command, args []string) error {
		ctx := cmd.Context()
		logger := contextlogger.From(ctx)

		imageURLString, _ := cmd.Flags().GetString(uploadFlagImageURL)
		imagePathString, _ := cmd.Flags().GetString(uploadFlagImagePath)
		imageCompression, _ := cmd.Flags().GetString(uploadFlagCompression)
		imageFormat, _ := cmd.Flags().GetString(uploadFlagFormat)
		architecture, _ := cmd.Flags().GetString(uploadFlagArchitecture)
		serverType, _ := cmd.Flags().GetString(uploadFlagServerType)
		description, _ := cmd.Flags().GetString(uploadFlagDescription)
		labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels)

		options := hcloudimages.UploadOptions{
			ImageCompression: hcloudimages.Compression(imageCompression),
			ImageFormat:      hcloudimages.Format(imageFormat),
			Description:      hcloud.Ptr(description),
			Labels:           labels,
		}

		if imageURLString != "" {
			imageURL, err := url.Parse(imageURLString)
			if err != nil {
				return fmt.Errorf("unable to parse url from --%s=%q: %w", uploadFlagImageURL, imageURLString, err)
			}

			// Check for image size
			resp, err := http.Head(imageURL.String())
			switch {
			case err != nil:
				logger.DebugContext(ctx, "failed to check for file size, error on request", "err", err)
			case resp.ContentLength == -1:
				logger.DebugContext(ctx, "failed to check for file size, server did not set the Content-Length", "err", err)
			default:
				options.ImageSize = resp.ContentLength
			}

			options.ImageURL = imageURL
		} else if imagePathString != "" {
			stat, err := os.Stat(imagePathString)
			if err != nil {
				logger.DebugContext(ctx, "failed to check for file size, error on stat", "err", err)
			} else {
				options.ImageSize = stat.Size()
			}

			imageFile, err := os.Open(imagePathString)
			if err != nil {
				return fmt.Errorf("unable to read file from --%s=%q: %w", uploadFlagImagePath, imagePathString, err)
			}

			options.ImageReader = imageFile
		}

		if architecture != "" {
			options.Architecture = hcloud.Architecture(architecture)
		} else if serverType != "" {
			options.ServerType = &hcloud.ServerType{Name: serverType}
		}

		image, err := client.Upload(ctx, options)
		if err != nil {
			return fmt.Errorf("failed to upload the image: %w", err)
		}

		logger.InfoContext(ctx, "Successfully uploaded the image!", "image", image.ID)

		return nil
	},
}

func init() {
	RootCmd.AddCommand(uploadCmd)

	uploadCmd.Flags().String(uploadFlagImageURL, "", "Remote URL of the disk image that should be uploaded")
	uploadCmd.Flags().String(uploadFlagImagePath, "", "Local path to the disk image that should be uploaded")
	uploadCmd.MarkFlagsMutuallyExclusive(uploadFlagImageURL, uploadFlagImagePath)
	uploadCmd.MarkFlagsOneRequired(uploadFlagImageURL, uploadFlagImagePath)

	uploadCmd.Flags().String(uploadFlagCompression, "", "Type of compression that was used on the disk image [choices: bz2, xz]")
	_ = uploadCmd.RegisterFlagCompletionFunc(
		uploadFlagCompression,
		cobra.FixedCompletions([]string{string(hcloudimages.CompressionBZ2), string(hcloudimages.CompressionXZ)}, cobra.ShellCompDirectiveNoFileComp),
	)

	uploadCmd.Flags().String(uploadFlagFormat, "", "Format of the image. [choices: qcow2]")
	_ = uploadCmd.RegisterFlagCompletionFunc(
		uploadFlagFormat,
		cobra.FixedCompletions([]string{string(hcloudimages.FormatQCOW2)}, cobra.ShellCompDirectiveNoFileComp),
	)

	uploadCmd.Flags().String(uploadFlagArchitecture, "", "CPU architecture of the disk image [choices: x86, arm]")
	_ = uploadCmd.RegisterFlagCompletionFunc(
		uploadFlagArchitecture,
		cobra.FixedCompletions([]string{string(hcloud.ArchitectureX86), string(hcloud.ArchitectureARM)}, cobra.ShellCompDirectiveNoFileComp),
	)

	uploadCmd.Flags().String(uploadFlagServerType, "", "Explicitly use this server type to generate the image. Mutually exclusive with --architecture.")

	// Only one of them needs to be set
	uploadCmd.MarkFlagsOneRequired(uploadFlagArchitecture, uploadFlagServerType)
	uploadCmd.MarkFlagsMutuallyExclusive(uploadFlagArchitecture, uploadFlagServerType)

	uploadCmd.Flags().String(uploadFlagDescription, "", "Description for the resulting image")

	uploadCmd.Flags().StringToString(uploadFlagLabels, map[string]string{}, "Labels for the resulting image")
}
