package resolver

import (
	"context"
	"errors"
	"net/http"
	"path"
	"regexp"

	"github.com/cirruslabs/cirrus-cli/pkg/larker/fs"
	"github.com/cirruslabs/cirrus-cli/pkg/larker/fs/git"
	"github.com/cirruslabs/cirrus-cli/pkg/larker/fs/github"
	"github.com/cirruslabs/cirrus-cli/pkg/larker/fs/scopedlayer"
)

type relativeLocation struct {
	Path string
}

type gitLocation struct {
	URL      string
	Revision string
	Path     string
}

type gitHubLocation struct {
	Owner    string
	Name     string
	Revision string
	Path     string
}

var (
	ErrRetrievalFailed     = errors.New("failed to retrieve a module")
	ErrUnsupportedLocation = errors.New("unsupported location")
)

const (
	// Captures the path after / in non-greedy manner.
	optionalPath = `(?:/(?P<path>.*?))?`

	// Captures the revision after @ in non-greedy manner.
	optionalRevision = `(?:@(?P<revision>.*))?`
)

var (
	githubRegexVariant = regexp.MustCompile(
		`^(?P<root>github\.com/(?P<owner>.*?)/(?P<name>.*?))` + optionalPath + optionalRevision + `$`,
	)

	genericGitRegexVariant = regexp.MustCompile(`^(?P<root>.*?)\.git` + optionalPath + optionalRevision + `$`)
)

func parseLocation(module string) interface{} {
	matches := githubRegexVariant.FindStringSubmatch(module)
	if matches != nil {
		owner := matches[githubRegexVariant.SubexpIndex("owner")]
		name := matches[githubRegexVariant.SubexpIndex("name")]

		revision := matches[githubRegexVariant.SubexpIndex("revision")]
		if revision == "" {
			revision = "main"
		}

		modulePath := matches[githubRegexVariant.SubexpIndex("path")]
		if modulePath == "" {
			modulePath = "lib.star"
		}

		return gitHubLocation{
			Owner:    owner,
			Name:     name,
			Revision: revision,
			Path:     modulePath,
		}
	}

	matches = genericGitRegexVariant.FindStringSubmatch(module)
	if matches != nil {
		revision := matches[genericGitRegexVariant.SubexpIndex("revision")]
		if revision == "" {
			revision = "main"
		}

		modulePath := matches[genericGitRegexVariant.SubexpIndex("path")]
		if modulePath == "" {
			modulePath = "lib.star"
		}

		return gitLocation{
			URL:      "https://" + matches[genericGitRegexVariant.SubexpIndex("root")] + ".git",
			Revision: revision,
			Path:     modulePath,
		}
	}

	return relativeLocation{Path: module}
}

func FindModuleFS(
	ctx context.Context,
	currentFS fs.FileSystem,
	env map[string]string,
	module string,
	httpClient *http.Client,
) (fs.FileSystem, string, error) {
	return findLocatorFS(ctx, currentFS, env, parseLocation(module), httpClient)
}

func findLocatorFS(
	ctx context.Context,
	currentFS fs.FileSystem,
	env map[string]string,
	location interface{},
	httpClient *http.Client,
) (fs.FileSystem, string, error) {
	switch typedLocation := location.(type) {
	case gitHubLocation:
		token := env["CIRRUS_REPO_CLONE_TOKEN"]

		ghFS, err := github.New(typedLocation.Owner, typedLocation.Name, typedLocation.Revision, token, httpClient)
		if err != nil {
			return nil, "", err
		}
		return ghFS, typedLocation.Path, nil
	case gitLocation:
		gitFS, err := git.New(ctx, typedLocation.URL, typedLocation.Revision)
		if err != nil {
			return nil, "", err
		}
		return gitFS, typedLocation.Path, nil
	case relativeLocation:
		return scopedlayer.New(currentFS, path.Dir(typedLocation.Path)), path.Base(typedLocation.Path), nil
	default:
		return nil, "", ErrUnsupportedLocation
	}
}
