package archive

import (
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/schrodinger-test/once/repack/log"
	"github.com/pingcap/schrodinger-test/once/repack/util"
	"go.uber.org/zap"
)

func Open(ref Ref) (io.ReadCloser, error) {
	if strings.HasPrefix(ref.Addr, "http") {
		log.Debug("download archive", zap.String("addr", ref.Addr))
		res, err := http.Get(ref.Addr)
		if err != nil {
			return nil, errors.Annotate(err, "http open")
		}
		return res.Body, nil
	} else {
		f, err := os.Open(ref.Addr)
		if err != nil {
			return nil, errors.Annotate(err, "file open")
		}
		return f, nil
	}
}

func CachedOpen(ref Ref) (io.ReadCloser, error) {
	// TODO: create/check lock files
	var (
		local *os.File
		hash  string
	)
	dir := os.Getenv("ARCHIVE_CACHE_DIR")
	if len(dir) == 0 {
		dir = filepath.Join(os.Getenv("HOME"), ".pingcap/cache/", string(ref.Kind))
	}
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		return nil, errors.Annotate(err, "create cache directory")
	}
	for _, tag := range ref.Tags {
		p := filepath.Join(dir, tag)
		if _, err := os.Stat(p); os.IsNotExist(err) {
			continue
		}
		local, err = os.Open(p)
		if err == nil {
			break
		}
		log.Warn("open cached file",
			zap.String("path", p), zap.Error(err))
	}
	if local != nil {
		log.Debug("open cached file", zap.String("path", local.Name()))
		return local, nil
	}
	res, err := Open(ref)
	if err != nil {
		return nil, err
	}
	for _, tag := range ref.Tags {
		if len(tag) >= 40 && util.IsHexString(tag, false) {
			hash = tag
			break
		}
	}
	if len(hash) == 0 {
		return res, nil
	}
	p := filepath.Join(dir, hash)
	out, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		log.Warn("new cache file",
			zap.String("path", p), zap.Error(err))
		return res, nil
	}
	defer res.Close()
	defer out.Close()

	n, err := io.Copy(out, res)
	if err != nil {
		return nil, errors.Annotate(err, "write cache file")
	}
	log.Debug("write cache file",
		zap.String("path", out.Name()), zap.Int64("bytes", n))

	for _, tag := range ref.Tags {
		if tag == hash {
			continue
		}
		p := filepath.Join(dir, tag)
		os.MkdirAll(filepath.Dir(p), 0755)
		os.Symlink(out.Name(), p)
	}

	return os.Open(out.Name())
}
