package main

import (
	"bytes"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/Songmu/gitconfig"
	"github.com/x-motemen/ghq/logger"
)

// Convert SCP-like URL to SSH URL(e.g. [user@]host.xz:path/to/repo.git/)
// ref. http://git-scm.com/docs/git-fetch#_git_urls
// (golang hasn't supported Perl-like negative look-behind match)
var (
	hasSchemePattern          = regexp.MustCompile("^[^:]+://")
	scpLikeURLPattern         = regexp.MustCompile("^([^@]+@)?([^:]+):(/?.+)$")
	looksLikeAuthorityPattern = regexp.MustCompile(`[A-Za-z0-9]\.[A-Za-z]+(?::\d{1,5})?$`)
	codecommitLikeURLPattern  = regexp.MustCompile(`^(codecommit):(?::([a-z][a-z0-9-]+):)?//(?:([^]]+)@)?([\w\.-]+)$`)
)

func newURL(ref string, ssh, forceMe bool) (*url.URL, error) {
	// If argURL is a "./foo" or "../bar" form,
	// find repository name trailing after github.com/USER/.
	ref = filepath.ToSlash(ref)
	parts := strings.Split(ref, "/")
	if parts[0] == "." || parts[0] == ".." {
		if wd, err := os.Getwd(); err == nil {
			path := filepath.Clean(filepath.Join(wd, filepath.Join(parts...)))

			var localRepoRoot string
			roots, err := localRepositoryRoots(true)
			if err != nil {
				return nil, err
			}
			for _, r := range roots {
				p := strings.TrimPrefix(path, r+string(filepath.Separator))
				if p != path && (localRepoRoot == "" || len(p) < len(localRepoRoot)) {
					localRepoRoot = filepath.ToSlash(p)
				}
			}

			if localRepoRoot != "" {
				// Guess it
				logger.Log("resolved", fmt.Sprintf("relative %q to %q", ref, "https://"+localRepoRoot))
				ref = "https://" + localRepoRoot
			}
		}
	}

	if codecommitLikeURLPattern.MatchString(ref) {
		// SEE ALSO:
		// https://github.com/aws/git-remote-codecommit/blob/master/git_remote_codecommit/__init__.py#L68
		matched := codecommitLikeURLPattern.FindStringSubmatch(ref)
		region := matched[2]

		if matched[2] == "" {
			// Region detection priority:
			// 1. Explicit specification (codecommit::region://...)
			// 2. Environment variables
			//     a. AWS_REGION (implicit priority)
			//     b. AWS_DEFAULT_REGION
			// 3. AWS CLI profiles
			// SEE ALSO:
			// https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-precedence
			var exists bool
			region, exists = os.LookupEnv("AWS_REGION")
			if !exists {
				region, exists = os.LookupEnv("AWS_DEFAULT_REGION")
			}

			if !exists {
				var stdout bytes.Buffer
				var stderr bytes.Buffer

				cmd := exec.Command("aws", "configure", "get", "region")
				cmd.Stdout = &stdout
				cmd.Stderr = &stderr

				err := cmd.Run()
				if err != nil {
					if stderr.String() == "" {
						fmt.Fprintln(os.Stderr, "You must specify a region. You can also configure your region by running \"aws configure\".")
					} else {
						fmt.Fprint(os.Stderr, stderr.String())
					}
					os.Exit(1)
				}

				region = strings.TrimSpace(stdout.String())
			}
		}

		return &url.URL{
			Scheme: matched[1],
			Host:   region,
			User:   url.User(matched[3]),
			Path:   matched[4],
			Opaque: ref,
		}, nil
	}

	if !hasSchemePattern.MatchString(ref) {
		if scpLikeURLPattern.MatchString(ref) {
			matched := scpLikeURLPattern.FindStringSubmatch(ref)
			user := matched[1]
			host := matched[2]
			path := matched[3]
			// When two conditions below are satisfied:
			// 1. the path is a relative path, which not beginning with a slash, like `path/to/repo`.
			// 2. the host is not github.com, which doesn't support relative paths.
			// then we convert the given SCP style URL to a normal style and relative path URL using tilde, like `ssh://user@repo.example.com/~/path/to/repo`.
			if strings.HasPrefix(path, "/") {
				path = strings.TrimPrefix(path, "/")
			} else if host != "github.com" {
				path = "~/" + path
			}
			ref = fmt.Sprintf("ssh://%s%s/%s", user, host, path)
		} else {
			// If ref is like "github.com/motemen/ghq" convert to "https://github.com/motemen/ghq"
			paths := strings.Split(ref, "/")
			if len(paths) > 1 && looksLikeAuthorityPattern.MatchString(paths[0]) {
				ref = "https://" + ref
			}
		}
	}

	u, err := url.Parse(ref)
	if err != nil {
		return nil, err
	}
	if !u.IsAbs() {
		if !strings.Contains(u.Path, "/") {
			u.Path, err = fillUsernameToPath(u.Path, forceMe)
			if err != nil {
				return nil, err
			}
		}
		u.Scheme = "https"
		u.Host = "github.com"
		if u.Path[0] != '/' {
			u.Path = "/" + u.Path
		}
	}

	if ssh {
		// Assume Git repository if `-p` is given.
		if u, err = convertGitURLHTTPToSSH(u); err != nil {
			return nil, fmt.Errorf("could not convert URL %q: %w", u, err)
		}
	}

	return u, nil
}

func convertGitURLHTTPToSSH(u *url.URL) (*url.URL, error) {
	user := "git"
	if u.User != nil {
		user = u.User.Username()
	}
	sshURL := fmt.Sprintf("ssh://%s@%s%s", user, u.Host, u.Path)
	return u.Parse(sshURL)
}

func detectUserName() (string, error) {
	user, err := gitconfig.Get("ghq.user")
	if (err != nil && !gitconfig.IsNotFound(err)) || user != "" {
		return user, err
	}

	user, err = gitconfig.GitHubUser("")
	if (err != nil && !gitconfig.IsNotFound(err)) || user != "" {
		return user, err
	}

	switch runtime.GOOS {
	case "windows":
		user = os.Getenv("USERNAME")
	default:
		user = os.Getenv("USER")
	}
	if user == "" {
		// Make the error if it does not match any pattern
		return "", fmt.Errorf("failed to detect username. You can set ghq.user to your gitconfig")
	}
	return user, nil
}

func fillUsernameToPath(path string, forceMe bool) (string, error) {
	if !forceMe {
		completeUser, err := gitconfig.Bool("ghq.completeUser")
		if err != nil && !gitconfig.IsNotFound(err) {
			return path, err
		}
		if err == nil && !completeUser {
			return path + "/" + path, nil
		}
	}
	user, err := detectUserName()
	if err != nil {
		return path, err
	}
	return user + "/" + path, nil
}
