package automation

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"sort"
	"strings"

	"github.com/go-chi/chi/v5"
)

type AutomationSourceRequest struct {
	WorkspaceID         string
	RepositoryOwner     string
	RepositoryName      string
	DefinitionPath      string
	GitRef              string
	AuthorizationHeader string
}

type AutomationSourceSnapshot struct {
	RepositoryOwner  string
	RepositoryName   string
	DefinitionPath   string
	GitRef           string
	CommitSHA        string
	ContentID        string
	ContentVersionID string
	Content          string
}

type AutomationSourceResolver interface {
	ResolveAutomationSource(ctx context.Context, request AutomationSourceRequest) (AutomationSourceSnapshot, error)
}

type AutomationSourceRepository struct {
	ID            int64
	Owner         string
	Name          string
	FullName      string
	DefaultBranch string
	Private       bool
	HTMLURL       string
}

type AutomationSourceFile struct {
	RepositoryOwner  string
	RepositoryName   string
	Path             string
	Name             string
	SHA              string
	Size             int64
	Ref              string
	ContentID        string
	ContentVersionID string
}

type AutomationSourceContent struct {
	AutomationSourceFile
	Content  string
	Encoding string
}

type AutomationSourceSavedFile struct {
	AutomationSourceFile
	Encoding string
}

type AutomationSourceCommit struct {
	SHA              string
	ContentVersionID string
	Message          string
}

type AutomationSourceWriteRequest struct {
	WorkspaceID         string
	Actor               string
	RepositoryOwner     string
	RepositoryName      string
	DefinitionPath      string
	Branch              string
	Content             string
	Message             string
	SHA                 string
	AuthorizationHeader string
}

type AutomationSourceDeleteRequest struct {
	WorkspaceID         string
	Actor               string
	RepositoryOwner     string
	RepositoryName      string
	DefinitionPath      string
	Branch              string
	Message             string
	SHA                 string
	AuthorizationHeader string
}

type AutomationSourceWriteResult struct {
	Content AutomationSourceSavedFile
	Commit  AutomationSourceCommit
	Created bool
}

type AutomationSourceControlPlane interface {
	ListAutomationRepositories(ctx context.Context, authorizationHeader string, page int, perPage int) ([]AutomationSourceRepository, error)
	ListAutomationOrgRepositories(ctx context.Context, authorizationHeader string, org string, page int, perPage int) ([]AutomationSourceRepository, error)
	ListAutomationFiles(ctx context.Context, request AutomationSourceRequest) ([]AutomationSourceFile, error)
	GetAutomationFile(ctx context.Context, request AutomationSourceRequest) (AutomationSourceContent, error)
}

type AutomationSourceWriteControlPlane interface {
	PutAutomationFile(ctx context.Context, request AutomationSourceWriteRequest) (AutomationSourceWriteResult, error)
	DeleteAutomationFile(ctx context.Context, request AutomationSourceDeleteRequest) error
}

type AGSHTTPAutomationSourceResolver struct {
	Handler                  http.Handler
	AuthHeader               string
	MaxAutomationSourceBytes int64
}

func (r AGSHTTPAutomationSourceResolver) ResolveAutomationSource(ctx context.Context, request AutomationSourceRequest) (AutomationSourceSnapshot, error) {
	if r.Handler == nil {
		return AutomationSourceSnapshot{}, fmt.Errorf("automation source resolver handler is nil")
	}
	owner := strings.TrimSpace(request.RepositoryOwner)
	repo := strings.TrimSpace(request.RepositoryName)
	definitionPath := cleanDefinitionPath(request.DefinitionPath)
	ref := strings.TrimSpace(request.GitRef)
	if ref == "" {
		ref = "HEAD"
	}
	commitSHA, err := r.resolveCommit(ctx, request.AuthorizationHeader, owner, repo, ref)
	if err != nil {
		return AutomationSourceSnapshot{}, err
	}
	content, err := r.readAutomationFile(ctx, request.AuthorizationHeader, owner, repo, definitionPath, commitSHA)
	if err != nil {
		return AutomationSourceSnapshot{}, err
	}
	return AutomationSourceSnapshot{RepositoryOwner: owner, RepositoryName: repo, DefinitionPath: definitionPath, GitRef: ref, CommitSHA: commitSHA, Content: content}, nil
}

func (r AGSHTTPAutomationSourceResolver) ListAutomationRepositories(ctx context.Context, authorizationHeader string, page int, perPage int) ([]AutomationSourceRepository, error) {
	if r.Handler == nil {
		return nil, fmt.Errorf("automation source resolver handler is nil")
	}
	if page <= 0 {
		page = 1
	}
	if perPage <= 0 {
		perPage = 50
	}
	if perPage > 100 {
		perPage = 100
	}
	path := "/api/v3/user/repos?page=" + url.QueryEscape(fmt.Sprint(page)) + "&per_page=" + url.QueryEscape(fmt.Sprint(perPage))
	return r.listAutomationRepositoriesAtPath(ctx, authorizationHeader, path, "list automation repositories via AGS")
}

func (r AGSHTTPAutomationSourceResolver) ListAutomationOrgRepositories(ctx context.Context, authorizationHeader string, org string, page int, perPage int) ([]AutomationSourceRepository, error) {
	if r.Handler == nil {
		return nil, fmt.Errorf("automation source resolver handler is nil")
	}
	if page <= 0 {
		page = 1
	}
	if perPage <= 0 {
		perPage = 50
	}
	if perPage > 100 {
		perPage = 100
	}
	org = strings.TrimSpace(org)
	if org == "" {
		return []AutomationSourceRepository{}, nil
	}
	path := "/api/v3/orgs/" + url.PathEscape(org) + "/repos?page=" + url.QueryEscape(fmt.Sprint(page)) + "&per_page=" + url.QueryEscape(fmt.Sprint(perPage))
	return r.listAutomationRepositoriesAtPath(ctx, authorizationHeader, path, "list automation org repositories via AGS")
}

func (r AGSHTTPAutomationSourceResolver) listAutomationRepositoriesAtPath(ctx context.Context, authorizationHeader string, path string, operation string) ([]AutomationSourceRepository, error) {
	status, body := r.do(ctx, authorizationHeader, http.MethodGet, path)
	if status == http.StatusNotFound {
		return []AutomationSourceRepository{}, nil
	}
	if status != http.StatusOK {
		return nil, fmt.Errorf("%s: status %d", operation, status)
	}
	var payload []struct {
		ID            int64  `json:"id"`
		Name          string `json:"name"`
		FullName      string `json:"full_name"`
		Private       bool   `json:"private"`
		HTMLURL       string `json:"html_url"`
		DefaultBranch string `json:"default_branch"`
		Owner         struct {
			Login string `json:"login"`
		} `json:"owner"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return nil, fmt.Errorf("%s: decode response: %w", operation, err)
	}
	out := make([]AutomationSourceRepository, 0, len(payload))
	for _, repo := range payload {
		name := strings.TrimSpace(repo.Name)
		owner := strings.TrimSpace(repo.Owner.Login)
		fullName := strings.TrimSpace(repo.FullName)
		if owner == "" && strings.Contains(fullName, "/") {
			owner = strings.SplitN(fullName, "/", 2)[0]
		}
		if name == "" && strings.Contains(fullName, "/") {
			name = strings.SplitN(fullName, "/", 2)[1]
		}
		if fullName == "" && owner != "" && name != "" {
			fullName = owner + "/" + name
		}
		out = append(out, AutomationSourceRepository{ID: repo.ID, Owner: owner, Name: name, FullName: fullName, Private: repo.Private, HTMLURL: repo.HTMLURL, DefaultBranch: repo.DefaultBranch})
	}
	return out, nil
}

func (r AGSHTTPAutomationSourceResolver) ListAutomationFiles(ctx context.Context, request AutomationSourceRequest) ([]AutomationSourceFile, error) {
	if r.Handler == nil {
		return nil, fmt.Errorf("automation source resolver handler is nil")
	}
	owner := strings.TrimSpace(request.RepositoryOwner)
	repo := strings.TrimSpace(request.RepositoryName)
	ref := strings.TrimSpace(request.GitRef)
	files, err := r.listAutomationFilesAtPath(ctx, request.AuthorizationHeader, owner, repo, ".agentteam", ref)
	if err != nil {
		return nil, err
	}
	sort.Slice(files, func(i, j int) bool { return files[i].Path < files[j].Path })
	return files, nil
}

func (r AGSHTTPAutomationSourceResolver) listAutomationFilesAtPath(ctx context.Context, authorizationHeader, owner, repo, dir, ref string) ([]AutomationSourceFile, error) {
	dir = cleanDefinitionPath(dir)
	if dir == "" {
		dir = ".agentteam"
	}
	apiPath := "/api/v3/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/contents/" + pathEscapeSegments(dir)
	if ref != "" {
		apiPath += "?ref=" + url.QueryEscape(ref)
	}
	status, body := r.do(ctx, authorizationHeader, http.MethodGet, apiPath)
	if status == http.StatusNotFound {
		return []AutomationSourceFile{}, nil
	}
	if status != http.StatusOK {
		return nil, fmt.Errorf("list automation files via AGS: status %d", status)
	}
	entries, err := decodeAGSContentEntries(body)
	if err != nil {
		return nil, fmt.Errorf("list automation files via AGS: decode response: %w", err)
	}
	out := make([]AutomationSourceFile, 0, len(entries))
	for _, entry := range entries {
		entryPath := cleanDefinitionPath(entry.Path)
		switch entry.Type {
		case "file":
			if !isAutomationSourcePath(entryPath) {
				continue
			}
			out = append(out, AutomationSourceFile{RepositoryOwner: owner, RepositoryName: repo, Path: entryPath, Name: entry.Name, SHA: entry.SHA, Size: entry.Size, Ref: ref})
		case "dir", "directory":
			if entryPath == "" || !strings.HasPrefix(entryPath, ".agentteam/") {
				continue
			}
			nested, err := r.listAutomationFilesAtPath(ctx, authorizationHeader, owner, repo, entryPath, ref)
			if err != nil {
				return nil, err
			}
			out = append(out, nested...)
		default:
			continue
		}
	}
	return out, nil
}

func (r AGSHTTPAutomationSourceResolver) GetAutomationFile(ctx context.Context, request AutomationSourceRequest) (AutomationSourceContent, error) {
	if r.Handler == nil {
		return AutomationSourceContent{}, fmt.Errorf("automation source resolver handler is nil")
	}
	owner := strings.TrimSpace(request.RepositoryOwner)
	repo := strings.TrimSpace(request.RepositoryName)
	definitionPath := cleanDefinitionPath(request.DefinitionPath)
	ref := strings.TrimSpace(request.GitRef)
	content, entry, err := r.readAutomationFileEntry(ctx, request.AuthorizationHeader, owner, repo, definitionPath, ref)
	if err != nil {
		return AutomationSourceContent{}, err
	}
	return AutomationSourceContent{AutomationSourceFile: AutomationSourceFile{RepositoryOwner: owner, RepositoryName: repo, Path: definitionPath, Name: entry.Name, SHA: entry.SHA, Size: entry.Size, Ref: ref}, Content: content, Encoding: "utf-8"}, nil
}

func (r AGSHTTPAutomationSourceResolver) PutAutomationFile(ctx context.Context, request AutomationSourceWriteRequest) (AutomationSourceWriteResult, error) {
	if r.Handler == nil {
		return AutomationSourceWriteResult{}, fmt.Errorf("automation source resolver handler is nil")
	}
	owner := strings.TrimSpace(request.RepositoryOwner)
	repo := strings.TrimSpace(request.RepositoryName)
	definitionPath := cleanDefinitionPath(request.DefinitionPath)
	branch := strings.TrimSpace(request.Branch)
	payload := map[string]string{
		"content": base64.StdEncoding.EncodeToString([]byte(request.Content)),
	}
	if message := strings.TrimSpace(request.Message); message != "" {
		payload["message"] = message
	}
	if branch != "" {
		payload["branch"] = branch
	}
	if sha := strings.TrimSpace(request.SHA); sha != "" {
		payload["sha"] = sha
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return AutomationSourceWriteResult{}, fmt.Errorf("encode automation source write request: %w", err)
	}
	apiPath := "/api/v3/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/contents/" + pathEscapeSegments(definitionPath)
	status, responseBody := r.doBody(ctx, request.AuthorizationHeader, http.MethodPut, apiPath, bytes.NewReader(body))
	if status != http.StatusOK && status != http.StatusCreated {
		return AutomationSourceWriteResult{}, automationSourceStatusError("write automation file via AGS", status, responseBody)
	}
	var response struct {
		Content agsContentEntry `json:"content"`
		Commit  struct {
			SHA     string `json:"sha"`
			Message string `json:"message"`
		} `json:"commit"`
	}
	if err := json.Unmarshal(responseBody, &response); err != nil {
		return AutomationSourceWriteResult{}, fmt.Errorf("write automation file via AGS: decode response: %w", err)
	}
	entry := response.Content
	if strings.TrimSpace(entry.Path) == "" {
		entry.Path = definitionPath
	}
	if strings.TrimSpace(entry.Name) == "" {
		entry.Name = automationSourceFileName(entry.Path)
	}
	if entry.Size == 0 && request.Content != "" {
		entry.Size = int64(len([]byte(request.Content)))
	}
	return AutomationSourceWriteResult{
		Content: AutomationSourceSavedFile{
			AutomationSourceFile: AutomationSourceFile{
				RepositoryOwner: owner,
				RepositoryName:  repo,
				Path:            cleanDefinitionPath(entry.Path),
				Name:            entry.Name,
				SHA:             entry.SHA,
				Size:            entry.Size,
				Ref:             branch,
			},
			Encoding: "utf-8",
		},
		Commit:  AutomationSourceCommit{SHA: response.Commit.SHA, Message: response.Commit.Message},
		Created: status == http.StatusCreated,
	}, nil
}

func (r AGSHTTPAutomationSourceResolver) DeleteAutomationFile(ctx context.Context, request AutomationSourceDeleteRequest) error {
	if r.Handler == nil {
		return fmt.Errorf("automation source resolver handler is nil")
	}
	owner := strings.TrimSpace(request.RepositoryOwner)
	repo := strings.TrimSpace(request.RepositoryName)
	definitionPath := cleanDefinitionPath(request.DefinitionPath)
	payload := map[string]string{
		"message": strings.TrimSpace(request.Message),
		"sha":     strings.TrimSpace(request.SHA),
	}
	if branch := strings.TrimSpace(request.Branch); branch != "" {
		payload["branch"] = branch
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("encode automation source delete request: %w", err)
	}
	apiPath := "/api/v3/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/contents/" + pathEscapeSegments(definitionPath)
	status, responseBody := r.doBody(ctx, request.AuthorizationHeader, http.MethodDelete, apiPath, bytes.NewReader(body))
	if status != http.StatusOK && status != http.StatusNoContent {
		return automationSourceStatusError("delete automation file via AGS", status, responseBody)
	}
	return nil
}

func (r AGSHTTPAutomationSourceResolver) resolveCommit(ctx context.Context, auth, owner, repo, ref string) (string, error) {
	if isCommitSHA(ref) {
		return r.verifyCommit(ctx, auth, owner, repo, ref)
	}
	trimmed := strings.TrimPrefix(ref, "refs/")
	for _, prefix := range []string{"heads/", "tags/"} {
		name := trimmed
		if strings.HasPrefix(name, prefix) {
			name = strings.TrimPrefix(name, prefix)
		} else if prefix == "tags/" && strings.HasPrefix(trimmed, "heads/") {
			continue
		} else if prefix == "heads/" && strings.HasPrefix(trimmed, "tags/") {
			continue
		}
		if name == "" || name == "HEAD" {
			continue
		}
		path := "/api/v3/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/git/refs/" + prefix + pathEscapeSegments(name)
		status, body := r.do(ctx, auth, http.MethodGet, path)
		if status == http.StatusOK {
			var payload struct {
				Object struct {
					SHA string `json:"sha"`
				} `json:"object"`
			}
			if err := json.Unmarshal(body, &payload); err == nil && strings.TrimSpace(payload.Object.SHA) != "" {
				return payload.Object.SHA, nil
			}
		}
	}
	return r.verifyCommit(ctx, auth, owner, repo, ref)
}

func (r AGSHTTPAutomationSourceResolver) verifyCommit(ctx context.Context, auth, owner, repo, ref string) (string, error) {
	path := "/api/v3/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/commits/" + url.PathEscape(ref)
	status, body := r.do(ctx, auth, http.MethodGet, path)
	if status != http.StatusOK {
		return "", automationSourceStatusError(fmt.Sprintf("resolve automation ref %q via AGS", ref), status, body)
	}
	var payload struct {
		SHA string `json:"sha"`
	}
	if err := json.Unmarshal(body, &payload); err != nil || strings.TrimSpace(payload.SHA) == "" {
		if isCommitSHA(ref) {
			return ref, nil
		}
		return "", fmt.Errorf("resolve automation ref %q via AGS: invalid commit response", ref)
	}
	return payload.SHA, nil
}

func (r AGSHTTPAutomationSourceResolver) readAutomationFile(ctx context.Context, auth, owner, repo, definitionPath, commitSHA string) (string, error) {
	content, _, err := r.readAutomationFileEntry(ctx, auth, owner, repo, definitionPath, commitSHA)
	return content, err
}

func (r AGSHTTPAutomationSourceResolver) readAutomationFileEntry(ctx context.Context, auth, owner, repo, definitionPath, ref string) (string, agsContentEntry, error) {
	apiPath := "/api/v3/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/contents/" + pathEscapeSegments(definitionPath)
	if strings.TrimSpace(ref) != "" {
		apiPath += "?ref=" + url.QueryEscape(ref)
	}
	status, body, err := r.doBodyLimit(ctx, auth, http.MethodGet, apiPath, nil, r.automationSourceResponseLimit())
	if err != nil {
		return "", agsContentEntry{}, err
	}
	if status != http.StatusOK {
		return "", agsContentEntry{}, automationSourceStatusError(fmt.Sprintf("read automation file %q at %s via AGS", definitionPath, ref), status, body)
	}
	var payload agsContentEntry
	if err := json.Unmarshal(body, &payload); err != nil {
		return "", agsContentEntry{}, fmt.Errorf("read automation file via AGS: decode response: %w", err)
	}
	if payload.Type != "file" || payload.Encoding != "base64" {
		return "", agsContentEntry{}, fmt.Errorf("read automation file via AGS: path is not a file")
	}
	if r.MaxAutomationSourceBytes > 0 && payload.Size > r.MaxAutomationSourceBytes {
		return "", agsContentEntry{}, fmt.Errorf("read automation file via AGS: automation source exceeds maximum size")
	}
	decoded, err := base64.StdEncoding.DecodeString(removeASCIIWhitespace(payload.Content))
	if err != nil {
		return "", agsContentEntry{}, fmt.Errorf("read automation file via AGS: decode content: %w", err)
	}
	if r.MaxAutomationSourceBytes > 0 && int64(len(decoded)) > r.MaxAutomationSourceBytes {
		return "", agsContentEntry{}, fmt.Errorf("read automation file via AGS: automation source exceeds maximum size")
	}
	return string(decoded), payload, nil
}

func (r AGSHTTPAutomationSourceResolver) do(ctx context.Context, auth, method, path string) (int, []byte) {
	return r.doBody(ctx, auth, method, path, nil)
}

func (r AGSHTTPAutomationSourceResolver) doBody(ctx context.Context, auth, method, path string, body io.Reader) (int, []byte) {
	status, payload, _ := r.doBodyLimit(ctx, auth, method, path, body, 0)
	return status, payload
}

func (r AGSHTTPAutomationSourceResolver) doBodyLimit(ctx context.Context, auth, method, path string, body io.Reader, maxBodyBytes int64) (int, []byte, error) {
	requestCtx := ctx
	if requestCtx == nil {
		requestCtx = context.Background()
	}
	requestCtx = context.WithValue(requestCtx, chi.RouteCtxKey, nil)
	req := httptest.NewRequest(method, path, body).WithContext(requestCtx)
	setAGSAuthHeader(req.Header, r.AuthHeader, auth)
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	rec := newLimitedResponseRecorder(maxBodyBytes)
	r.Handler.ServeHTTP(rec, req)
	if rec.exceeded {
		return rec.Code(), rec.Body(), fmt.Errorf("automation source response exceeds maximum size")
	}
	return rec.Code(), rec.Body(), nil
}

func (r AGSHTTPAutomationSourceResolver) authHeader() string {
	if header := strings.TrimSpace(r.AuthHeader); header != "" {
		return header
	}
	return "Authorization"
}

func (r AGSHTTPAutomationSourceResolver) automationSourceResponseLimit() int64 {
	if r.MaxAutomationSourceBytes <= 0 {
		return 0
	}
	return automationBase64EncodedLen(r.MaxAutomationSourceBytes) + 64*1024
}

type limitedResponseRecorder struct {
	header   http.Header
	code     int
	body     bytes.Buffer
	limit    int64
	exceeded bool
}

func newLimitedResponseRecorder(limit int64) *limitedResponseRecorder {
	return &limitedResponseRecorder{header: http.Header{}, limit: limit}
}

func (r *limitedResponseRecorder) Header() http.Header {
	return r.header
}

func (r *limitedResponseRecorder) WriteHeader(code int) {
	if r.code == 0 {
		r.code = code
	}
}

func (r *limitedResponseRecorder) Write(payload []byte) (int, error) {
	if r.code == 0 {
		r.code = http.StatusOK
	}
	if r.limit <= 0 {
		_, _ = r.body.Write(payload)
		return len(payload), nil
	}
	remaining := r.limit - int64(r.body.Len())
	if remaining <= 0 {
		r.exceeded = true
		return len(payload), nil
	}
	if int64(len(payload)) > remaining {
		_, _ = r.body.Write(payload[:remaining])
		r.exceeded = true
		return len(payload), nil
	}
	_, _ = r.body.Write(payload)
	return len(payload), nil
}

func (r *limitedResponseRecorder) Code() int {
	if r.code == 0 {
		return http.StatusOK
	}
	return r.code
}

func (r *limitedResponseRecorder) Body() []byte {
	return r.body.Bytes()
}

func cleanDefinitionPath(value string) string {
	return strings.Trim(strings.TrimSpace(value), "/")
}

func pathEscapeSegments(value string) string {
	parts := strings.Split(strings.Trim(value, "/"), "/")
	for idx, part := range parts {
		parts[idx] = url.PathEscape(part)
	}
	return strings.Join(parts, "/")
}

func isCommitSHA(value string) bool {
	if len(value) != 40 {
		return false
	}
	for _, ch := range value {
		if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') && (ch < 'A' || ch > 'F') {
			return false
		}
	}
	return true
}

func removeASCIIWhitespace(value string) string {
	return strings.Map(func(r rune) rune {
		switch r {
		case ' ', '\n', '\r', '\t':
			return -1
		default:
			return r
		}
	}, value)
}

type agsContentEntry struct {
	Type     string `json:"type"`
	Name     string `json:"name"`
	Path     string `json:"path"`
	SHA      string `json:"sha"`
	Size     int64  `json:"size"`
	Encoding string `json:"encoding"`
	Content  string `json:"content"`
}

func decodeAGSContentEntries(body []byte) ([]agsContentEntry, error) {
	var entries []agsContentEntry
	if err := json.Unmarshal(body, &entries); err == nil {
		return entries, nil
	}
	var entry agsContentEntry
	if err := json.Unmarshal(body, &entry); err != nil {
		return nil, err
	}
	return []agsContentEntry{entry}, nil
}

func automationSourceStatusError(operation string, status int, body []byte) error {
	message := agsErrorMessage(body)
	if message == "" {
		message = fmt.Sprintf("%s: status %d", operation, status)
	}
	switch status {
	case http.StatusBadRequest, http.StatusUnprocessableEntity:
		return invalidArgument(message)
	case http.StatusUnauthorized:
		return unauthorized(message)
	case http.StatusForbidden:
		return forbidden(message)
	case http.StatusNotFound:
		return notFound(message)
	case http.StatusConflict:
		return conflict(message)
	default:
		return fmt.Errorf("%s: status %d", operation, status)
	}
}

func agsErrorMessage(body []byte) string {
	var payload struct {
		Message string `json:"message"`
		Error   string `json:"error"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return ""
	}
	if message := strings.TrimSpace(payload.Message); message != "" {
		return message
	}
	return strings.TrimSpace(payload.Error)
}

func automationSourceFileName(value string) string {
	value = strings.Trim(value, "/")
	if idx := strings.LastIndex(value, "/"); idx >= 0 {
		return value[idx+1:]
	}
	return value
}
