package stdpull

import (
	"errors"
	"fmt"
	"io"
	"io/fs"
	"net/http"
	url2 "net/url"
	"os"
	"path"
	"path/filepath"

	"github.com/containers/podman/v5/pkg/machine/compression"
	"github.com/containers/podman/v5/pkg/machine/define"
	"github.com/containers/podman/v5/utils"
	"github.com/containers/storage/pkg/fileutils"
	"github.com/sirupsen/logrus"
)

type DiskFromURL struct {
	u            *url2.URL
	finalPath    *define.VMFile
	tempLocation *define.VMFile
	cache        bool
}

func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile, optionalTempFileName *string, cache bool) (*DiskFromURL, error) {
	var (
		err error
	)
	u, err := url2.Parse(inputPath)
	if err != nil {
		return nil, err
	}

	// Make sure the temporary location exists before we get too deep
	if err := fileutils.Exists(tempDir.GetPath()); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			return nil, fmt.Errorf("temporary download directory %s does not exist", tempDir.GetPath())
		}
	}

	remoteImageName := path.Base(inputPath)
	if optionalTempFileName != nil {
		remoteImageName = *optionalTempFileName
	}
	if remoteImageName == "" {
		return nil, fmt.Errorf("invalid url: unable to determine image name in %q", inputPath)
	}

	tempLocation, err := tempDir.AppendToNewVMFile(remoteImageName, nil)
	if err != nil {
		return nil, err
	}

	return &DiskFromURL{
		u:            u,
		finalPath:    finalPath,
		tempLocation: tempLocation,
		cache:        cache,
	}, nil
}

func (d *DiskFromURL) Get() error {
	// this fetches the image and writes it to the temporary location
	if err := d.pull(); err != nil {
		return err
	}
	if !d.cache {
		defer func() {
			if err := utils.GuardedRemoveAll(d.tempLocation.GetPath()); err != nil {
				if !errors.Is(err, os.ErrNotExist) {
					logrus.Warn("failed to clean machine image cache: ", err)
				}
			}
		}()
	}

	logrus.Debugf("decompressing (if needed) %s to %s", d.tempLocation.GetPath(), d.finalPath.GetPath())
	return compression.Decompress(d.tempLocation, d.finalPath.GetPath())
}

func (d *DiskFromURL) pull() error {
	out, err := os.Create(d.tempLocation.GetPath())
	if err != nil {
		return err
	}
	defer func() {
		if err := out.Close(); err != nil {
			logrus.Error(err)
		}
	}()

	resp, err := http.Get(d.u.String())
	if err != nil {
		return err
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			logrus.Error(err)
		}
	}()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("downloading VM image %s: %s", d.u.String(), resp.Status)
	}
	size := resp.ContentLength
	prefix := "Downloading VM image: " + filepath.Base(d.tempLocation.GetPath())
	onComplete := prefix + ": done"

	p, bar := utils.ProgressBar(prefix, size, onComplete)

	proxyReader := bar.ProxyReader(resp.Body)
	defer func() {
		if err := proxyReader.Close(); err != nil {
			logrus.Error(err)
		}
	}()

	if _, err := io.Copy(out, proxyReader); err != nil {
		return err
	}

	p.Wait()
	return nil
}
