// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !plan9
// +build !plan9

package main

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

const (
	downloadURLPrefix = "https://dl.google.com/go"
)

// downloadGoVersion downloads and upacks the specific go version to dest/go.
func downloadGoVersion(version, ops, arch, dest string) error {
	suffix := "tar.gz"
	if ops == "windows" {
		suffix = "zip"
	}
	uri := fmt.Sprintf("%s/%s.%s-%s.%s", downloadURLPrefix, version, ops, arch, suffix)

	verbosef("Downloading %s", uri)

	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}
	req.Header.Add("User-Agent", fmt.Sprintf("golang.org-getgo/%s", version))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("Downloading Go from %s failed: %v", uri, err)
	}
	if resp.StatusCode > 299 {
		return fmt.Errorf("Downloading Go from %s failed with HTTP status %s", uri, resp.Status)
	}
	defer resp.Body.Close()

	tmpf, err := os.CreateTemp("", "go")
	if err != nil {
		return err
	}
	defer os.Remove(tmpf.Name())

	h := sha256.New()

	w := io.MultiWriter(tmpf, h)
	if _, err := io.Copy(w, resp.Body); err != nil {
		return err
	}

	verbosef("Downloading SHA %s.sha256", uri)

	sresp, err := http.Get(uri + ".sha256")
	if err != nil {
		return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed: %v", uri, err)
	}
	defer sresp.Body.Close()
	if sresp.StatusCode > 299 {
		return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed with HTTP status %s", uri, sresp.Status)
	}

	shasum, err := io.ReadAll(sresp.Body)
	if err != nil {
		return err
	}

	// Check the shasum.
	sum := fmt.Sprintf("%x", h.Sum(nil))
	if sum != string(shasum) {
		return fmt.Errorf("Shasum mismatch %s vs. %s", sum, string(shasum))
	}

	unpackFunc := unpackTar
	if ops == "windows" {
		unpackFunc = unpackZip
	}
	if err := unpackFunc(tmpf.Name(), dest); err != nil {
		return fmt.Errorf("Unpacking Go to %s failed: %v", dest, err)
	}
	return nil
}

func unpack(dest, name string, fi os.FileInfo, r io.Reader) error {
	if strings.HasPrefix(name, "go/") {
		name = name[len("go/"):]
	}

	path := filepath.Join(dest, name)
	if fi.IsDir() {
		return os.MkdirAll(path, fi.Mode())
	}

	f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, r)
	return err
}

func unpackTar(src, dest string) error {
	r, err := os.Open(src)
	if err != nil {
		return err
	}
	defer r.Close()

	archive, err := gzip.NewReader(r)
	if err != nil {
		return err
	}
	defer archive.Close()

	tarReader := tar.NewReader(archive)

	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}

		if err := unpack(dest, header.Name, header.FileInfo(), tarReader); err != nil {
			return err
		}
	}

	return nil
}

func unpackZip(src, dest string) error {
	zr, err := zip.OpenReader(src)
	if err != nil {
		return err
	}

	for _, f := range zr.File {
		fr, err := f.Open()
		if err != nil {
			return err
		}
		if err := unpack(dest, f.Name, f.FileInfo(), fr); err != nil {
			return err
		}
		fr.Close()
	}

	return nil
}

func getLatestGoVersion() (string, error) {
	resp, err := http.Get("https://golang.org/dl/?mode=json")
	if err != nil {
		return "", fmt.Errorf("Getting current Go version failed: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		b, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return "", fmt.Errorf("Could not get current Go release: HTTP %d: %q", resp.StatusCode, b)
	}
	var releases []struct {
		Version string
	}
	err = json.NewDecoder(resp.Body).Decode(&releases)
	if err != nil {
		return "", err
	}
	if len(releases) < 1 {
		return "", fmt.Errorf("Could not get at least one Go release")
	}
	return releases[0].Version, nil
}
