package main

import (
	"fmt"
	"net/url"
	"regexp"
	"time"
)

var reGitHubPR = regexp.MustCompile(`^https://github\.com/([^/?#]+)/([^/?#]+)/pull/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`)

func githubPagination(i int) url.Values {
	params := make(url.Values)
	params.Set("page", fmt.Sprintf("%v", i+1))
	return params
}

type GitHub struct{}

var _ Forge = GitHub{}

func (GitHub) FetchStatus(urls []string) (string, error) {
	for _, u := range urls {
		if reGoogleGerritCL.MatchString(u) {
			return "", nil
		}
	}
	return fetchPerURLStatus(urls, func(u string) (string, error) {
		m := reGitHubPR.FindStringSubmatch(u)
		if m == nil {
			return "", nil
		}
		user := m[1]
		repo := m[2]
		prnum := m[3]

		urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum

		var obj struct {
			// State values are "open" and "closed".
			State          string `json:"state"`
			Merged         bool   `json:"merged"`
			MergeCommitSha string `json:"merge_commit_sha"`
		}
		if err := httpGetJSON(urlStr, nil, &obj); err != nil {
			return "", err
		}
		ret := obj.State
		if obj.Merged {
			ret = statusMerged
			tag, err := getGitTagThatContainsAll("https://github.com/"+user+"/"+repo, obj.MergeCommitSha)
			if err != nil {
				return "", err
			}
			if tag != "" {
				ret = fmt.Sprintf(statusReleasedFmt, tag)
			}
		}

		return ret, nil
	})
}

func (GitHub) FetchSubmittedAt(urls []string) (time.Time, error) {
	return fetchPerURLSubmittedAt(urls, func(u string) (time.Time, error) {
		m := reGitHubPR.FindStringSubmatch(u)
		if m == nil {
			return time.Time{}, nil
		}
		user := m[1]
		repo := m[2]
		prnum := m[3]

		urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum

		var obj struct {
			CreatedAt time.Time `json:"created_at"`
		}
		if err := httpGetJSON(urlStr, nil, &obj); err != nil {
			return time.Time{}, err
		}
		return obj.CreatedAt, nil
	})
}

func (GitHub) FetchLastUpdated(urls []string) (time.Time, User, error) {
	for _, u := range urls {
		if reGoogleGerritCL.MatchString(u) {
			return time.Time{}, User{}, nil
		}
	}
	return fetchPerURLLastUpdated(urls, func(u string) (time.Time, User, error) {
		m := reGitHubPR.FindStringSubmatch(u)
		if m == nil {
			return time.Time{}, User{}, nil
		}
		user := m[1]
		repo := m[2]
		prnum := m[3]

		urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum

		var obj struct {
			UpdatedAt time.Time `json:"updated_at"`

			CreatedAt time.Time `json:"created_at"`
			CreatedBy struct {
				Login   string `json:"login"`
				HTMLURL string `json:"html_url"`
			} `json:"user"`

			MergedAt time.Time `json:"merged_at"`
			MergedBy struct {
				Login   string `json:"login"`
				HTMLURL string `json:"html_url"`
			} `json:"merged_by"`
		}
		if err := httpGetJSON(urlStr, nil, &obj); err != nil {
			return time.Time{}, User{}, err
		}

		retUpdatedAt := obj.UpdatedAt
		var retUser User

		if retUser == (User{}) && withinOneSecond(obj.CreatedAt, retUpdatedAt) {
			retUser.Name = obj.CreatedBy.Login
			retUser.URL = obj.CreatedBy.HTMLURL
		}
		if retUser == (User{}) && withinOneSecond(obj.MergedAt, retUpdatedAt) {
			retUser.Name = obj.MergedBy.Login
			retUser.URL = obj.MergedBy.HTMLURL
		}
		if retUser == (User{}) {
			// "normal" comments
			var comments []struct {
				UpdatedAt time.Time `json:"updated_at"`
				User      struct {
					Login   string `json:"login"`
					HTMLURL string `json:"html_url"`
				} `json:"user"`
			}
			if err := httpGetPaginatedJSON("https://api.github.com/repos/"+user+"/"+repo+"/issues/"+prnum+"/comments", nil, &comments, githubPagination); err != nil {
				return time.Time{}, User{}, err
			}
			for _, comment := range comments {
				if withinOneSecond(comment.UpdatedAt, retUpdatedAt) {
					retUser.Name = comment.User.Login
					retUser.URL = comment.User.HTMLURL
					break
				}
			}
		}
		if retUser == (User{}) {
			// comments on a specific part of the diff
			var reviewComments []struct {
				UpdatedAt time.Time `json:"updated_at"`
				User      struct {
					Login   string `json:"login"`
					HTMLURL string `json:"html_url"`
				} `json:"user"`
			}
			if err := httpGetPaginatedJSON("https://api.github.com/repos/"+user+"/"+repo+"/pulls/"+prnum+"/comments", nil, &reviewComments, githubPagination); err != nil {
				return time.Time{}, User{}, err
			}
			for _, comment := range reviewComments {
				if withinOneSecond(comment.UpdatedAt, retUpdatedAt) {
					retUser.Name = comment.User.Login
					retUser.URL = comment.User.HTMLURL
					break
				}
			}
		}
		if retUser == (User{}) {
			var events []struct {
				CreatedAt time.Time `json:"created_at"`
				Actor     struct {
					Login   string `json:"login"`
					HTMLURL string `json:"html_url"`
				} `json:"actor"`
			}
			if err := httpGetJSON("https://api.github.com/repos/"+user+"/"+repo+"/issues/"+prnum+"/events", nil, &events); err != nil {
				return time.Time{}, User{}, err
			}
			for _, event := range events {
				if withinOneSecond(event.CreatedAt, retUpdatedAt) {
					retUser.Name = event.Actor.Login
					retUser.URL = event.Actor.HTMLURL
					break
				}
			}
		}

		return retUpdatedAt, retUser, nil
	})
}