package repositoryroutecontract

import (
	"encoding/base64"
	"net/http"
	"net/url"
	"strings"
)

// RepositoryCredential extracts the credential formats accepted by AGS data
// plane routes. Embedders use the same parser when selecting the AGS auth path.
func RepositoryCredential(r *http.Request) string {
	if r == nil {
		return ""
	}
	raw := strings.TrimSpace(r.Header.Get("Authorization"))
	lower := strings.ToLower(raw)
	switch {
	case strings.HasPrefix(lower, "bearer "):
		return strings.TrimSpace(raw[len("bearer "):])
	case strings.HasPrefix(lower, "token "):
		return strings.TrimSpace(raw[len("token "):])
	case strings.HasPrefix(lower, "basic "):
		decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(raw[len("basic "):]))
		if err != nil {
			return ""
		}
		_, password, ok := strings.Cut(string(decoded), ":")
		if ok {
			return strings.TrimSpace(password)
		}
	}
	return ""
}

// RepositoryAuthorizationTarget is the dependency-light Desktop/runtime
// preclassifier. The split Gateway and target seam use AGS registration-owned
// metadata instead; this parser cannot activate a route.
func RepositoryAuthorizationTarget(r *http.Request) (string, string, bool) {
	if r == nil || r.URL == nil {
		return "", "", false
	}
	path := strings.Trim(strings.TrimSpace(r.URL.Path), "/")
	if repository, capability, ok := GitSmartHTTPAuthorizationTarget(r); ok {
		return repository, capability, true
	}
	parts := strings.Split(path, "/")
	prefix := "api/v3/repos/"
	if strings.HasPrefix(path, "api/ext/v1/repos/") {
		prefix = "api/ext/v1/repos/"
	} else if !strings.HasPrefix(path, prefix) {
		return "", "", false
	}
	rest := strings.TrimPrefix(path, prefix)
	parts = strings.Split(rest, "/")
	if len(parts) < 2 || strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" {
		return "", "", false
	}
	repository := parts[0] + "/" + parts[1]
	operation := strings.Join(parts[2:], "/")
	archiveOperation := repositoryArchiveOperation(r, prefix)
	if repositoryRESTManagementOperation(operation) {
		return "", "", false
	}
	switch r.Method {
	case http.MethodGet, http.MethodHead, http.MethodOptions:
		if repositoryRESTReadOperation(operation, archiveOperation) {
			return repository, "repo:read", true
		}
	case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
		if repositoryRESTWriteOperation(operation) {
			return repository, "repo:write", true
		}
	}
	return "", "", false
}

// GitSmartHTTPAuthorizationTarget recognizes only the three operations that
// the AGS router registers for smart HTTP.
func GitSmartHTTPAuthorizationTarget(request *http.Request) (string, string, bool) {
	if request == nil || request.URL == nil {
		return "", "", false
	}
	parts := strings.Split(strings.Trim(request.URL.Path, "/"), "/")
	if len(parts) < 3 || parts[0] == "" || !strings.HasSuffix(parts[1], ".git") {
		return "", "", false
	}
	repo := strings.TrimSuffix(parts[1], ".git")
	if repo == "" {
		return "", "", false
	}
	repository := parts[0] + "/" + repo
	switch {
	case request.Method == http.MethodGet && len(parts) == 4 && parts[2] == "info" && parts[3] == "refs":
		service := strings.TrimSpace(request.URL.Query().Get("service"))
		switch service {
		case "", "git-upload-pack":
			return repository, "repo:clone", true
		case "git-receive-pack":
			return repository, "repo:write", true
		default:
			return "", "", false
		}
	case request.Method == http.MethodPost && len(parts) == 3 && parts[2] == "git-upload-pack":
		return repository, "repo:clone", true
	case request.Method == http.MethodPost && len(parts) == 3 && parts[2] == "git-receive-pack":
		return repository, "repo:write", true
	default:
		return "", "", false
	}
}

// RepositoryIDAuthorizationTarget parses repository-scoped APIs whose public
// contract addresses an AGS repository by numeric id rather than owner/name.
func RepositoryIDAuthorizationTarget(r *http.Request) (string, string, bool) {
	if r == nil || r.URL == nil {
		return "", "", false
	}
	path := strings.Trim(strings.TrimSpace(r.URL.Path), "/")
	const prefix = "api/v3/repositories/"
	if !strings.HasPrefix(path, prefix) {
		return "", "", false
	}
	parts := strings.Split(strings.TrimPrefix(path, prefix), "/")
	if len(parts) < 2 || parts[0] == "" || parts[1] != "environments" {
		return "", "", false
	}
	for _, char := range parts[0] {
		if char < '0' || char > '9' {
			return "", "", false
		}
	}
	capability := "repo:read"
	if r.Method != http.MethodGet && r.Method != http.MethodHead && r.Method != http.MethodOptions {
		capability = "repo:write"
	}
	return parts[0], capability, true
}

func repositoryRESTManagementOperation(operation string) bool {
	operation = strings.ToLower(strings.Trim(operation, "/"))
	if operation == "" {
		return false
	}
	for _, prefix := range []string{
		"transfer", "collaborators", "invitations", "keys", "deploy-keys",
		"hooks", "teams", "rulesets", "environments",
		"actions/secrets", "actions/variables", "actions/permissions",
	} {
		if operation == strings.TrimSuffix(prefix, "/") || strings.HasPrefix(operation, prefix) {
			return true
		}
	}
	return strings.HasPrefix(operation, "branches/") &&
		(strings.Contains(operation, "/protection") || strings.Contains(operation, "/rules"))
}

func repositoryRESTReadOperation(operation, archiveOperation string) bool {
	operation = strings.ToLower(strings.Trim(operation, "/"))
	if operation == "" {
		return true
	}
	if repositoryArchiveReadOperation(archiveOperation) {
		return true
	}
	if operation == "assignees" || operation == "summary" {
		return true
	}
	for _, prefix := range []string{
		"actions/artifacts", "actions/jobs", "actions/runs", "actions/workflows",
		"branches", "check-runs", "check-suites", "commits", "compare", "contents",
		"contributors", "deployments", "forks", "git", "issues", "labels", "languages",
		"license", "milestones", "pulls", "readme", "releases", "stargazers",
		"statuses", "subscribers", "tags", "topics", "wiki",
	} {
		if repositoryRESTOperationMatches(operation, prefix) {
			return true
		}
	}
	return false
}

func repositoryArchiveOperation(request *http.Request, prefix string) string {
	if request == nil || request.URL == nil {
		return ""
	}
	escapedPath := request.URL.EscapedPath()
	if request.URL.RawPath != "" {
		decoded, err := url.PathUnescape(request.URL.RawPath)
		if err != nil || decoded != request.URL.Path {
			return ""
		}
		escapedPath = request.URL.RawPath
	}
	escaped := strings.Trim(escapedPath, "/")
	escapedPrefix := strings.Trim(prefix, "/")
	if !strings.HasPrefix(escaped, escapedPrefix+"/") {
		return ""
	}
	rest := strings.TrimPrefix(escaped, escapedPrefix+"/")
	parts := strings.Split(rest, "/")
	if len(parts) != 6 || parts[0] == "" || parts[1] == "" ||
		parts[2] != "archive" || parts[3] != "refs" || parts[4] != "tags" || parts[5] == "" {
		return ""
	}
	tagFile, err := url.PathUnescape(parts[5])
	if err != nil {
		return ""
	}
	return "archive/refs/tags/" + tagFile
}

func repositoryArchiveReadOperation(operation string) bool {
	const prefix = "archive/refs/tags/"
	if !strings.HasPrefix(operation, prefix) {
		return false
	}
	tagFile := strings.TrimPrefix(operation, prefix)
	if tagFile == "" {
		return false
	}
	for _, suffix := range []string{".tar.gz", ".zip"} {
		if strings.HasSuffix(tagFile, suffix) {
			return strings.TrimSuffix(tagFile, suffix) != ""
		}
	}
	return false
}

func repositoryRESTWriteOperation(operation string) bool {
	operation = strings.ToLower(strings.Trim(operation, "/"))
	for _, prefix := range []string{
		"contents", "git/refs", "git/tags", "issues", "pulls", "statuses",
		"commits", "releases", "wiki", "labels", "milestones",
		"actions/workflows", "actions/runs",
	} {
		if repositoryRESTOperationMatches(operation, prefix) {
			return true
		}
	}
	return false
}

func repositoryRESTOperationMatches(operation, prefix string) bool {
	prefix = strings.Trim(strings.ToLower(prefix), "/")
	return operation == prefix || strings.HasPrefix(operation, prefix+"/")
}
