package git

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
)

// GitError 是 git 子进程返回非零或超时时的统一错误形式。Stderr 已截断 ≤2000 字符；
// 上层（workspace.Service）会根据 stderr 内容映射到具体业务错误码。
type GitError struct {
	Args     []string
	ExitCode int
	Stderr   string
	Cause    error
}

func (e *GitError) Error() string {
	// 对 Args 中形如 scheme://user:pass@host 的 URL 脱敏，避免 PAT 明文
	// 写进 last_sync_error / audit。
	redacted := make([]string, len(e.Args))
	for i, a := range e.Args {
		redacted[i] = redactURLUserinfo(a)
	}
	// stderr 为空说明进程多半没跑起来（chdir 失败、二进制缺失、超时被 kill），
	// 此时 Cause 才是唯一有效信息，不能丢。
	detail := e.Stderr
	if detail == "" && e.Cause != nil {
		detail = e.Cause.Error()
	}
	return fmt.Sprintf("git %v: exit %d: %s", redacted, e.ExitCode, detail)
}

// redactURLUserinfo 剥离形如 scheme://user[:pass]@host 中的 userinfo，
// 输出 scheme://host...。非 URL 字符串或解析失败时原样返回。
func redactURLUserinfo(s string) string {
	if !strings.Contains(s, "://") {
		return s
	}
	u, err := url.Parse(s)
	if err != nil || u.User == nil {
		return s
	}
	u.User = nil
	return u.String()
}

func (e *GitError) Unwrap() error { return e.Cause }

// IsAuthError 通过 stderr 关键字识别 401/403 类型的远端拒绝（PAT 失效、SSH key
// 不被信任等）。grep 的关键字源自常见 git 远端的英文响应；模糊但可用。
func IsAuthError(err error) bool {
	var ge *GitError
	if !errors.As(err, &ge) {
		return false
	}
	for _, k := range []string{
		"Authentication failed",
		"could not read Username",
		"Permission denied (publickey)",
		"403 Forbidden",
		"401 Unauthorized",
		"fatal: Authentication",
	} {
		if containsCase(ge.Stderr, k) {
			return true
		}
	}
	return false
}

// IsConflict 用于识别 "branch already exists" 等 worktree add 冲突场景。
func IsConflict(err error) bool {
	var ge *GitError
	if !errors.As(err, &ge) {
		return false
	}
	for _, k := range []string{
		"already exists",
		"is already checked out",
		"already used by worktree",
	} {
		if containsCase(ge.Stderr, k) {
			return true
		}
	}
	return false
}

// containsCase 大小写不敏感子串匹配；避免引入 strings.EqualFold 之类逐字符开销。
func containsCase(s, sub string) bool {
	if len(sub) == 0 || len(s) < len(sub) {
		return false
	}
	S, Sub := lower(s), lower(sub)
	return indexOf(S, Sub) >= 0
}

func lower(s string) string {
	b := make([]byte, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c >= 'A' && c <= 'Z' {
			c += 'a' - 'A'
		}
		b[i] = c
	}
	return string(b)
}

func indexOf(s, sub string) int {
	for i := 0; i+len(sub) <= len(s); i++ {
		if s[i:i+len(sub)] == sub {
			return i
		}
	}
	return -1
}
