package main

import (
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/zhyee/zipstream"
)

type Forge struct{}

type ForgeVersion struct {
	Version string
	Time    string
	Url     string
}

type ForgeLibraryArtifact struct {
	Path string
	Url  string
	Sha1 string
}

type ForgeLibraryDownload struct {
	Artifact ForgeLibraryArtifact
}

type ForgeLibrary struct {
	Name      string
	Downloads ForgeLibraryDownload
}

type ForgeInstallData struct {
	Id           string
	Time         time.Time
	ReleaseTime  time.Time
	InheritsFrom string
	Type         string
	MainClass    string
	Libraries    []ForgeLibrary
}

func parseForgeVersions(html string) []ForgeVersion {
	lines := strings.Split(html, "\n")
	parsing := false
	foundTR := false
	buff := ""
	versions := []ForgeVersion{}
	for _, line := range lines {
		if strings.Contains(line, "<tbody>") {
			parsing = true
		} else if strings.Contains(line, "</tbody>") {
			parsing = false
		} else if parsing {
			if strings.Contains(line, "<tr>") {
				buff = ""
				foundTR = true
			} else if strings.Contains(line, "</tr>") {
				foundTR = false
				versions = append(versions, parseForgeVersion(buff))
			} else if foundTR {
				buff += line + "\n"
			}
		}
	}
	return versions
}

func parseForgeVersion(html string) ForgeVersion {
	lines := strings.Split(html, "\n")
	version := ForgeVersion{}
	for ind, line := range lines {
		if strings.Contains(line, "<td class=\"download-version\">") {
			version.Version = strings.TrimSpace(lines[ind+1])
		} else if strings.Contains(line, "<td class=\"download-time\"") {
			version.Time = strings.Split(strings.Split(line, "<td class=\"download-time\" title=\"")[1], "\">")[0]
		} else if strings.Contains(line, "https://adfoc.us") && strings.Contains(line, "installer.jar") {
			version.Url = strings.Split(strings.Split(line, "&url=")[1], "\">")[0]
		}
	}
	return version
}

func (Forge) GetForgeVersions(mcVersion string) ([]ForgeVersion, error) {
	resp, err := http.Get(fmt.Sprintf("https://files.minecraftforge.net/net/minecraftforge/forge/index_%s.html", mcVersion))
	if err != nil {
		return []ForgeVersion{}, fmt.Errorf("unable to access minecraft forge index: %e", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return []ForgeVersion{}, fmt.Errorf("unable to access minecraft forge index: %s", err)
	}
	data, _ := io.ReadAll(resp.Body)
	return parseForgeVersions(string(data)), nil
}

func GetForgeInstallDataFromVersion(version ForgeVersion) (ForgeInstallData, error) {
	resp, err := http.Get(version.Url)
	if err != nil {
		return ForgeInstallData{}, fmt.Errorf("unable to pull jar file: %e", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return ForgeInstallData{}, fmt.Errorf("unable to pull jar file: %s", resp.Status)
	}
	zs := zipstream.NewReader(resp.Body)
	for {
		entry, err := zs.GetNextEntry()
		if err == io.EOF {
			break
		}
		if entry.Name != "version.json" {
			continue
		}
		f, _ := entry.Open()
		defer f.Close()
		data, _ := io.ReadAll(f)
		installData := ForgeInstallData{}
		json.Unmarshal(data, &installData)
		return installData, nil
	}
	return ForgeInstallData{}, fmt.Errorf("unable to find version.json")
}

func GetForgeInstallData(mcVersion string, forgeVersion string) (ForgeInstallData, error) {
	versions, err := Forge{}.GetForgeVersions(mcVersion)
	if err != nil {
		return ForgeInstallData{}, fmt.Errorf("failed to pull forge versions: %e", err)
	}
	for _, version := range versions {
		if version.Version == forgeVersion {
			return GetForgeInstallDataFromVersion(version)
		}
	}
	return ForgeInstallData{}, fmt.Errorf("unable to find the requested version")
}

func InstallForgeLibs(mcVersion string, forgeVersion string, libDir string) {
	installData, err := GetForgeInstallData(mcVersion, forgeVersion)
	if err != nil {
		fmt.Printf("Unable to get install data: %s\n", err)
	}
	for _, lib := range installData.Libraries {
		if _, err := os.Stat(filepath.Join(libDir, lib.Downloads.Artifact.Path)); err == nil {
			if f, err := os.OpenFile(filepath.Join(libDir, lib.Downloads.Artifact.Path), os.O_RDONLY, 0755); err == nil {
				defer f.Close()
				data, _ := io.ReadAll(f)
				sha := sha1.Sum(data)
				if hex.EncodeToString(sha[:20]) == lib.Downloads.Artifact.Sha1 {
					continue
				}
			}
		}
		resp, err := http.Get(lib.Downloads.Artifact.Url)
		if err != nil {
			fmt.Printf("Unable to download library %s: %s\n", lib.Name, err)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			fmt.Printf("Unable to download library %s: %s\n", lib.Name, resp.Status)
			continue
		}
		tokens := strings.Split(lib.Downloads.Artifact.Path, "/")
		path := ""
		for ind, token := range tokens {
			if ind == len(tokens)-1 {
				break
			}
			path = filepath.Join(path, token)
		}
		os.MkdirAll(filepath.Join(libDir, path), 0755)
		f, _ := os.OpenFile(filepath.Join(libDir, lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755)
		defer f.Close()
		io.Copy(f, resp.Body)
	}
}
