package change

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"
)

type ReleaseInfo struct {
	Author struct {
		Login string `json:"login"`
	} `json:"author"`
	PublishedAt string `json:"published_at"`
	Body        string `json:"body"`
	HTMLURL     string `json:"html_url"`
}

type CommitInfo struct {
	Commit struct {
		Message string `json:"message"`
	} `json:"commit"`
}

type ErrorInfo struct {
	Message string
}

type GithubAPI struct {
	Owner string
	Repo  string
	Token string
}

// GetReleaseInfo for getting kratos release info.
func (g *GithubAPI) GetReleaseInfo(version string) ReleaseInfo {
	api := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", g.Owner, g.Repo)
	if version != "latest" {
		api = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, version)
	}
	resp, code := requestGithubAPI(api, "GET", nil, g.Token)
	if code != http.StatusOK {
		printGithubErrorInfo(resp)
	}
	releaseInfo := ReleaseInfo{}
	err := json.Unmarshal(resp, &releaseInfo)
	if err != nil {
		fatal(err)
	}
	return releaseInfo
}

// GetCommitsInfo for getting kratos commits info.
func (g *GithubAPI) GetCommitsInfo() []CommitInfo {
	info := g.GetReleaseInfo("latest")
	page := 1
	var list []CommitInfo
	for {
		url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits?pre_page=100&page=%d&since=%s", g.Owner, g.Repo, page, info.PublishedAt)
		resp, code := requestGithubAPI(url, "GET", nil, g.Token)
		if code != http.StatusOK {
			printGithubErrorInfo(resp)
		}
		var res []CommitInfo
		err := json.Unmarshal(resp, &res)
		if err != nil {
			fatal(err)
		}
		list = append(list, res...)
		if len(res) < http.StatusContinue {
			break
		}
		page++
	}
	return list
}

func printGithubErrorInfo(body []byte) {
	errorInfo := &ErrorInfo{}
	err := json.Unmarshal(body, errorInfo)
	if err != nil {
		fatal(err)
	}
	fatal(errors.New(errorInfo.Message))
}

func requestGithubAPI(url string, method string, body io.Reader, token string) ([]byte, int) {
	cli := &http.Client{Timeout: 60 * time.Second}
	request, err := http.NewRequest(method, url, body)
	if err != nil {
		fatal(err)
	}
	if token != "" {
		request.Header.Add("Authorization", token)
	}
	resp, err := cli.Do(request)
	if err != nil {
		fatal(err)
	}
	defer resp.Body.Close()
	resBody, err := io.ReadAll(resp.Body)
	if err != nil {
		fatal(err)
	}
	return resBody, resp.StatusCode
}

func ParseCommitsInfo(info []CommitInfo) string {
	group := map[string][]string{
		"fix":   {},
		"feat":  {},
		"deps":  {},
		"break": {},
		"chore": {},
		"other": {},
	}

	for _, commitInfo := range info {
		msg := commitInfo.Commit.Message
		index := strings.Index(fmt.Sprintf("%q", msg), `\n`)
		if index != -1 {
			msg = msg[:index-1]
		}
		prefix := []string{"fix", "feat", "deps", "break", "chore"}
		var matched bool
		for _, v := range prefix {
			msg = strings.TrimPrefix(msg, " ")
			if strings.HasPrefix(msg, v) {
				group[v] = append(group[v], msg)
				matched = true
			}
		}
		if !matched {
			group["other"] = append(group["other"], msg)
		}
	}

	md := make(map[string]string)
	for key, value := range group {
		var text string
		switch key {
		case "break":
			text = "### Breaking Changes\n"
		case "deps":
			text = "### Dependencies\n"
		case "feat":
			text = "### New Features\n"
		case "fix":
			text = "### Bug Fixes\n"
		case "chore":
			text = "### Chores\n"
		case "other":
			text = "### Others\n"
		}
		if len(value) > 0 {
			md[key] += text
			for _, value := range value {
				md[key] += fmt.Sprintf("- %s\n", value)
			}
		}
	}
	return fmt.Sprint(md["break"], md["deps"], md["feat"], md["fix"], md["chore"], md["other"])
}

func ParseReleaseInfo(info ReleaseInfo) string {
	reg := regexp.MustCompile(`(?m)^\s*$[\r\n]*|[\r\n]+\s+\z|<[\S\s]+?>`)
	body := reg.ReplaceAll([]byte(info.Body), []byte(""))
	if string(body) == "" {
		body = []byte("no release info")
	}
	splitters := "--------------------------------------------"
	return fmt.Sprintf(
		"Author: %s\nDate: %s\nUrl: %s\n\n%s\n\n%s\n\n%s\n",
		info.Author.Login,
		info.PublishedAt,
		info.HTMLURL,
		splitters,
		body,
		splitters,
	)
}

func ParseGithubURL(url string) (owner string, repo string) {
	var start int
	start = strings.Index(url, "//")
	if start == -1 {
		start = strings.Index(url, ":") + 1
	} else {
		start += 2
	}
	end := strings.LastIndex(url, "/")
	gitIndex := strings.LastIndex(url, ".git")
	if gitIndex == -1 {
		repo = url[strings.LastIndex(url, "/")+1:]
	} else {
		repo = url[strings.LastIndex(url, "/")+1 : gitIndex]
	}
	tmp := url[start:end]
	owner = tmp[strings.Index(tmp, "/")+1:]
	return
}

func fatal(err error) {
	fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err)
	os.Exit(1)
}
