package skills

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"

	git "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/config"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
	gittransport "github.com/go-git/go-git/v5/plumbing/transport"
	"gopkg.in/yaml.v3"

	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	"github.com/tidbcloud/AgentTeam/presets"
)

// GoGitRepositoryContentManager initializes, scans, and imports content for
// AgentTeam-managed skill repositories using go-git only. It never shells out to git
// on production paths.
type GoGitRepositoryContentManager struct {
	Handler             http.Handler
	BaseURL             string
	AuthHeader          string
	AuthorizationHeader string
	WorkDir             string
}

func (i GoGitRepositoryContentManager) InitializeSkillRepository(ctx context.Context, request SkillRepositoryInitializeRequest) error {
	if strings.TrimSpace(request.Slug) == "" || strings.TrimSpace(request.Name) == "" || strings.TrimSpace(request.Description) == "" {
		return invalidArgument("skill name, slug, and description are required")
	}
	workDir, cleanup, err := i.tempDir("meshx-skill-init-*")
	if err != nil {
		return err
	}
	defer cleanup()
	repo, err := git.PlainInitWithOptions(workDir, &git.PlainInitOptions{Bare: false, InitOptions: git.InitOptions{DefaultBranch: plumbing.NewBranchReferenceName(firstNonEmpty(request.Repository.DefaultBranch, "main"))}})
	if err != nil {
		return fmt.Errorf("initialize skill template repository: %w", err)
	}
	worktree, err := repo.Worktree()
	if err != nil {
		return fmt.Errorf("open skill template worktree: %w", err)
	}
	for _, file := range skillTemplateFiles(request.Slug, request.Name, request.Description, request.Locale) {
		path := filepath.Clean(filepath.FromSlash(file.path))
		if path == "." || strings.HasPrefix(path, ".."+string(filepath.Separator)) || filepath.IsAbs(path) {
			return fmt.Errorf("invalid skill template path %q", file.path)
		}
		target := filepath.Join(workDir, path)
		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
			return fmt.Errorf("create skill template directory %q: %w", file.path, err)
		}
		if err := os.WriteFile(target, []byte(file.content), 0o644); err != nil {
			return fmt.Errorf("write skill template file %q: %w", file.path, err)
		}
		if _, err := worktree.Add(filepath.ToSlash(path)); err != nil {
			return fmt.Errorf("stage skill template file %q: %w", file.path, err)
		}
	}
	_, err = worktree.Commit(firstNonEmpty(request.Message, presets.SkillScaffoldInitialCommitMessage(request.Locale)), &git.CommitOptions{Author: &object.Signature{Name: firstNonEmpty(request.AuthorName, "MeshX Skills"), Email: firstNonEmpty(request.AuthorEmail, "meshx-skills@agentteam.local"), When: time.Now().UTC()}})
	if err != nil {
		return fmt.Errorf("commit skill template: %w", err)
	}
	remoteURL, err := i.remoteURL(request.Repository)
	if err != nil {
		return err
	}
	if _, err := repo.CreateRemote(&config.RemoteConfig{Name: "origin", URLs: []string{remoteURL}}); err != nil {
		return fmt.Errorf("configure skill template remote: %w", err)
	}
	pushOptions := &git.PushOptions{RemoteName: "origin"}
	if auth := i.targetAuth(); auth != nil {
		pushOptions.Auth = auth
	}
	if err := repo.PushContext(ctx, pushOptions); err != nil {
		return fmt.Errorf("push skill template: %w", err)
	}
	return nil
}

func (i GoGitRepositoryContentManager) ListSkillRepositorySkills(ctx context.Context, repository repos.RepositoryIdentity) (SkillRepositoryScanData, error) {
	workDir, cleanup, err := i.tempDir("meshx-skill-list-*")
	if err != nil {
		return SkillRepositoryScanData{}, err
	}
	defer cleanup()
	repo, err := i.cloneTargetRepository(ctx, repository, workDir)
	if err != nil {
		return SkillRepositoryScanData{}, err
	}
	head, err := repo.Head()
	if err != nil {
		return SkillRepositoryScanData{}, err
	}
	preview, err := ScanSkillRepository(workDir, SkillRepositoryScanLimit)
	if err != nil {
		return SkillRepositoryScanData{}, err
	}
	preview.CommitSHA = head.Hash().String()
	return preview, nil
}

func (i GoGitRepositoryContentManager) ReadSkillRepositorySkills(ctx context.Context, repository repos.RepositoryIdentity, request SkillRepositoryContentsRequest) (SkillRepositoryContents, error) {
	expectedCommitSHA := strings.TrimSpace(request.ExpectedCommitSHA)
	if !skillCommitSHAPattern.MatchString(expectedCommitSHA) {
		return SkillRepositoryContents{}, invalidArgument("expectedCommitSha must be a full commit SHA")
	}
	paths, err := normalizeSkillRepositoryContentPaths(request.Paths)
	if err != nil {
		return SkillRepositoryContents{}, err
	}
	workDir, cleanup, err := i.tempDir("meshx-skill-read-*")
	if err != nil {
		return SkillRepositoryContents{}, err
	}
	defer cleanup()
	repo, err := i.cloneTargetRepository(ctx, repository, workDir)
	if err != nil {
		return SkillRepositoryContents{}, err
	}
	head, err := repo.Head()
	if err != nil {
		return SkillRepositoryContents{}, err
	}
	commitSHA := head.Hash().String()
	if commitSHA != expectedCommitSHA {
		return SkillRepositoryContents{}, ErrSkillRepositorySnapshotChanged
	}
	scan, err := ScanSkillRepository(workDir, SkillRepositoryScanLimit)
	if err != nil {
		return SkillRepositoryContents{}, fmt.Errorf("scan skill repository content: %w", err)
	}
	if scan.Truncated {
		return SkillRepositoryContents{}, codedInvalidArgument(SkillImportErrorScanLimitExceeded, "skill repository scan limit exceeded")
	}
	valid := make(map[string]SkillRepositoryDetectedSkill, len(scan.DetectedSkills))
	for _, detected := range scan.DetectedSkills {
		if detected.Status == SkillRepositorySkillStatusValid {
			valid[detected.Path] = detected
		}
	}
	items := make([]SkillRepositoryContent, 0, len(paths))
	for _, path := range paths {
		detected, ok := valid[path]
		if !ok {
			return SkillRepositoryContents{}, notFound("skill content not found")
		}
		content, err := os.ReadFile(filepath.Join(workDir, filepath.FromSlash(detected.Path)))
		if err != nil {
			return SkillRepositoryContents{}, fmt.Errorf("read skill content %q: %w", detected.Path, err)
		}
		items = append(items, SkillRepositoryContent{Name: detected.Name, Path: detected.Path, Content: string(content)})
	}
	return SkillRepositoryContents{CommitSHA: commitSHA, Items: items}, nil
}

func normalizeSkillRepositoryContentPaths(paths []string) ([]string, error) {
	if len(paths) == 0 {
		return nil, invalidArgument("paths are required")
	}
	if len(paths) > SkillRepositoryContentsPathLimit {
		return nil, invalidArgument("too many skill paths")
	}
	normalized := make([]string, 0, len(paths))
	seen := make(map[string]struct{}, len(paths))
	for _, path := range paths {
		path = strings.TrimSpace(path)
		if path == "" {
			return nil, invalidArgument("skill path is required")
		}
		if len(path) > SkillRepositoryContentsPathMaxBytes {
			return nil, invalidArgument("skill path is too long")
		}
		if _, ok := seen[path]; ok {
			continue
		}
		seen[path] = struct{}{}
		normalized = append(normalized, path)
	}
	return normalized, nil
}

func (i GoGitRepositoryContentManager) ImportSkillRepository(ctx context.Context, request SkillRepositoryImportRequest) (SkillRepositoryImportResult, error) {
	workDir, cleanup, err := i.tempDir("meshx-skill-import-*")
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}
	defer cleanup()

	repoDir := filepath.Join(workDir, "repo")
	repo, err := i.openTargetRepositoryForImport(ctx, request.TargetRepository, repoDir)
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}
	worktree, err := repo.Worktree()
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}

	sourceDir, sourceCleanup, _, warnings, err := importSourceArchive(ctx, request, workDir)
	defer sourceCleanup()
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}

	scan, err := ScanSkillRepository(sourceDir, SkillRepositoryScanLimit)
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}

	targetSkillsDir := filepath.Join(repoDir, "skills")
	importedSkills, err := importScannedSkills(sourceDir, targetSkillsDir, scan, request.ReplaceSkills)
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}
	mergedScan, err := ScanSkillRepository(repoDir, SkillRepositoryScanLimit)
	if err != nil {
		return SkillRepositoryImportResult{}, err
	}
	if mergedScan.Truncated {
		return SkillRepositoryImportResult{}, codedInvalidArgument(SkillImportErrorScanLimitExceeded, "skill import merged repository scan limit exceeded")
	}
	mergedSkills := validDetectedSkills(mergedScan.DetectedSkills)
	if err := updateReadmeSkillIndex(repoDir, mergedSkills); err != nil {
		return SkillRepositoryImportResult{}, err
	}
	if err := worktree.AddWithOptions(&git.AddOptions{All: true}); err != nil {
		return SkillRepositoryImportResult{}, fmt.Errorf("stage skill import: %w", err)
	}
	commit, err := worktree.Commit(firstNonEmpty(request.Message, "Import skills"), &git.CommitOptions{
		All:               true,
		AllowEmptyCommits: true,
		Author:            &object.Signature{Name: firstNonEmpty(request.AuthorName, "MeshX Skills"), Email: firstNonEmpty(request.AuthorEmail, "meshx-skills@agentteam.local"), When: time.Now().UTC()},
	})
	if err != nil {
		return SkillRepositoryImportResult{}, fmt.Errorf("commit skill import: %w", err)
	}
	pushOptions := &git.PushOptions{RemoteName: "origin"}
	if auth := i.targetAuth(); auth != nil {
		pushOptions.Auth = auth
	}
	if err := repo.PushContext(ctx, pushOptions); err != nil {
		return SkillRepositoryImportResult{}, fmt.Errorf("push skill import: %w", err)
	}
	return SkillRepositoryImportResult{CommitSHA: commit.String(), SkillCount: len(mergedSkills), ImportedSkills: importedSkills, Warnings: warnings}, nil
}

func (i GoGitRepositoryContentManager) openTargetRepositoryForImport(ctx context.Context, repository repos.RepositoryIdentity, target string) (*git.Repository, error) {
	repo, err := i.cloneTargetRepositoryRaw(ctx, repository, target)
	if err == nil {
		return repo, nil
	}
	if !errors.Is(err, gittransport.ErrEmptyRemoteRepository) {
		return nil, mapSkillGitError(err)
	}
	remoteURL, remoteErr := i.remoteURL(repository)
	if remoteErr != nil {
		return nil, remoteErr
	}
	if removeErr := os.RemoveAll(target); removeErr != nil {
		return nil, fmt.Errorf("reset empty skill repository workspace: %w", removeErr)
	}
	repo, err = git.PlainInitWithOptions(target, &git.PlainInitOptions{
		Bare: false,
		InitOptions: git.InitOptions{
			DefaultBranch: plumbing.NewBranchReferenceName(firstNonEmpty(repository.DefaultBranch, "main")),
		},
	})
	if err != nil {
		return nil, fmt.Errorf("initialize empty skill repository clone: %w", err)
	}
	if _, err := repo.CreateRemote(&config.RemoteConfig{Name: "origin", URLs: []string{remoteURL}}); err != nil {
		return nil, fmt.Errorf("configure empty skill repository remote: %w", err)
	}
	return repo, nil
}

const (
	skillReadmeIndexBeginMarker = "<!-- BEGIN agentteam skill index -->"
	skillReadmeIndexEndMarker   = "<!-- END agentteam skill index -->"
)

// updateReadmeSkillIndex rewrites the skill index table inside the README's
// BEGIN/END markers to reflect the given detected skills. It preserves the
// existing table header and separator rows and only regenerates data rows. If
// the README or the markers are absent it is a no-op, which keeps older repos
// that predate the markers safe.
func updateReadmeSkillIndex(repoDir string, skills []SkillRepositoryDetectedSkill) error {
	readmePath := filepath.Join(repoDir, "README.md")
	content, err := os.ReadFile(readmePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return fmt.Errorf("read skill readme: %w", err)
	}
	body := string(content)
	beginIdx := strings.Index(body, skillReadmeIndexBeginMarker)
	endIdx := strings.Index(body, skillReadmeIndexEndMarker)
	if beginIdx < 0 || endIdx < 0 || endIdx < beginIdx {
		return nil
	}
	innerStart := beginIdx + len(skillReadmeIndexBeginMarker)
	header, separator, ok := extractSkillIndexHeader(body[innerStart:endIdx])
	if !ok {
		return nil
	}
	var builder strings.Builder
	builder.WriteString(body[:innerStart])
	builder.WriteString("\n")
	builder.WriteString(header)
	builder.WriteString("\n")
	builder.WriteString(separator)
	builder.WriteString("\n")
	for _, skill := range skills {
		builder.WriteString(skillIndexRow(skill))
		builder.WriteString("\n")
	}
	builder.WriteString(body[endIdx:])
	updated := builder.String()
	if updated == body {
		return nil
	}
	if err := os.WriteFile(readmePath, []byte(updated), 0o644); err != nil {
		return fmt.Errorf("write skill readme index: %w", err)
	}
	return nil
}

// extractSkillIndexHeader pulls the first two table lines (the header and the
// dashed separator) out of the content between the README markers.
func extractSkillIndexHeader(inner string) (header, separator string, ok bool) {
	collected := make([]string, 0, 2)
	for _, line := range strings.Split(inner, "\n") {
		trimmed := strings.TrimSpace(line)
		if trimmed == "" || !strings.HasPrefix(trimmed, "|") {
			continue
		}
		collected = append(collected, trimmed)
		if len(collected) == 2 {
			break
		}
	}
	if len(collected) < 2 {
		return "", "", false
	}
	return collected[0], collected[1], true
}

// skillIndexRow builds a single markdown table row for a detected skill. The
// owner cell is left empty because per-skill ownership is not tracked here.
func skillIndexRow(skill SkillRepositoryDetectedSkill) string {
	name := sanitizeSkillIndexCell(skill.Name)
	if name == "" {
		name = sanitizeSkillIndexCell(skill.Path)
	}
	entryPath := strings.TrimSpace(skill.Path)
	if entryPath == "" {
		entryPath = "skills/" + skillRepositoryDirectoryNameForSkill(skill.Name, skill.Path) + "/SKILL.md"
	}
	entry := sanitizeSkillIndexCell(entryPath)
	purpose := sanitizeSkillIndexCell(skill.Description)
	return fmt.Sprintf("| %s | %s | %s |  |", name, entry, purpose)
}

func sanitizeSkillIndexCell(value string) string {
	trimmed := strings.TrimSpace(value)
	trimmed = strings.ReplaceAll(trimmed, "\r", " ")
	trimmed = strings.ReplaceAll(trimmed, "\n", " ")
	trimmed = strings.ReplaceAll(trimmed, "|", "\\|")
	return trimmed
}

func importScannedSkills(sourceDir, targetSkillsDir string, scan SkillRepositoryScanData, replaceExisting bool) ([]SkillRepositoryDetectedSkill, error) {
	if err := validateSkillImportScan(scan); err != nil {
		return nil, err
	}
	validSkills := validDetectedSkills(scan.DetectedSkills)
	if replaceExisting {
		if err := os.RemoveAll(targetSkillsDir); err != nil {
			return nil, fmt.Errorf("replace skill directory: %w", err)
		}
	}
	if err := os.MkdirAll(targetSkillsDir, 0o755); err != nil {
		return nil, fmt.Errorf("create skill directory: %w", err)
	}
	for _, row := range validSkills {
		sourceRoot := detectedSkillRoot(sourceDir, row.Path)
		targetRoot := filepath.Join(targetSkillsDir, skillRepositoryDirectoryNameForSkill(row.Name, row.Path))
		if err := os.RemoveAll(targetRoot); err != nil {
			return nil, fmt.Errorf("replace imported skill %q: %w", row.Name, err)
		}
		excludedRoots := nestedDetectedSkillRootRelPaths(row.Path, scan.DetectedSkills)
		if err := copySkillTree(sourceRoot, targetRoot, excludedRoots); err != nil {
			return nil, fmt.Errorf("copy imported skill %q: %w", row.Name, err)
		}
	}
	return validSkills, nil
}

func validateSkillImportScan(scan SkillRepositoryScanData) error {
	if scan.Truncated {
		return codedInvalidArgument(SkillImportErrorScanLimitExceeded, "skill import scan limit exceeded")
	}
	hasValid := false
	for _, row := range scan.DetectedSkills {
		if row.Status == SkillRepositorySkillStatusValid {
			hasValid = true
		}
	}
	if !hasValid {
		return codedInvalidArgument(SkillImportErrorNoSkillsFound, "no valid SKILL.md found in import source; archive and repository imports must use files named SKILL.md, single Markdown imports are treated as SKILL.md, every SKILL.md must start with YAML frontmatter containing name and description, and name must not contain path separators, .., or control characters")
	}
	return nil
}

func validDetectedSkills(rows []SkillRepositoryDetectedSkill) []SkillRepositoryDetectedSkill {
	out := make([]SkillRepositoryDetectedSkill, 0, len(rows))
	for _, row := range rows {
		if row.Status == SkillRepositorySkillStatusValid {
			out = append(out, row)
		}
	}
	return out
}

func detectedSkillRoot(sourceRoot string, skillPath string) string {
	clean := filepath.Clean(filepath.FromSlash(skillPath))
	if clean == "." || clean == string(filepath.Separator) || filepath.IsAbs(clean) || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
		return sourceRoot
	}
	return filepath.Join(sourceRoot, filepath.Dir(clean))
}

func skillRootRelPath(skillPath string) (string, bool) {
	clean := filepath.ToSlash(filepath.Clean(filepath.FromSlash(skillPath)))
	if clean == "." || clean == "/" || filepath.IsAbs(clean) || strings.HasPrefix(clean, "../") || !strings.EqualFold(filepath.Base(clean), "SKILL.md") {
		return "", false
	}
	dir := filepath.ToSlash(filepath.Dir(clean))
	if dir == "." {
		return "", true
	}
	return dir, true
}

func nestedDetectedSkillRootRelPaths(currentSkillPath string, rows []SkillRepositoryDetectedSkill) []string {
	currentRoot, ok := skillRootRelPath(currentSkillPath)
	if !ok {
		return nil
	}
	out := make([]string, 0, len(rows))
	for _, row := range rows {
		root, ok := skillRootRelPath(row.Path)
		if !ok || root == currentRoot {
			continue
		}
		switch {
		case currentRoot == "" && root != "":
			out = append(out, root)
		case strings.HasPrefix(root, currentRoot+"/"):
			out = append(out, strings.TrimPrefix(root, currentRoot+"/"))
		}
	}
	return out
}

func copySkillTree(source, target string, excludedRootRelPaths []string) error {
	sourceAbs, err := filepath.Abs(source)
	if err != nil {
		return err
	}
	targetAbs, err := filepath.Abs(target)
	if err != nil {
		return err
	}
	info, err := os.Lstat(sourceAbs)
	if err != nil {
		return err
	}
	if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
		return codedInvalidArgument(SkillImportErrorCopyFailed, "skill source root is not a safe directory")
	}
	excludedRoots := normalizedExcludedRootRelPaths(excludedRootRelPaths)
	return filepath.WalkDir(sourceAbs, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		rel, err := filepath.Rel(sourceAbs, path)
		if err != nil {
			return err
		}
		if rel == "." {
			return os.MkdirAll(targetAbs, 0o755)
		}
		relSlash := filepath.ToSlash(rel)
		if d.IsDir() && excludedRoots[relSlash] {
			return filepath.SkipDir
		}
		if relSlash == ".git" || strings.HasPrefix(relSlash, ".git/") || strings.Contains(relSlash, "/.git/") {
			if d.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		if d.Type()&os.ModeSymlink != 0 {
			return codedInvalidArgument(SkillRepositoryIssueSymlink, "skill import source contains symlink")
		}
		info, err := d.Info()
		if err != nil {
			return err
		}
		if info.Mode()&os.ModeType != 0 && !info.IsDir() {
			return codedInvalidArgument(SkillImportErrorCopyFailed, "skill import source contains special file")
		}
		dstRel := normalizeCopiedSkillRelPath(rel)
		dst := filepath.Join(targetAbs, dstRel)
		relToTarget, err := filepath.Rel(targetAbs, dst)
		if err != nil {
			return err
		}
		if relToTarget == ".." || strings.HasPrefix(relToTarget, ".."+string(filepath.Separator)) || filepath.IsAbs(relToTarget) {
			return codedInvalidArgument(SkillImportErrorCopyFailed, "skill import target path escaped skills directory")
		}
		if d.IsDir() {
			return os.MkdirAll(dst, 0o755)
		}
		if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
			return err
		}
		src, err := os.Open(path)
		if err != nil {
			return err
		}
		dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode().Perm())
		if err != nil {
			_ = src.Close()
			return err
		}
		_, copyErr := io.Copy(dstFile, src)
		srcCloseErr := src.Close()
		closeErr := dstFile.Close()
		if copyErr != nil {
			return copyErr
		}
		if srcCloseErr != nil {
			return srcCloseErr
		}
		return closeErr
	})
}

func normalizedExcludedRootRelPaths(paths []string) map[string]bool {
	out := make(map[string]bool, len(paths))
	for _, item := range paths {
		clean := filepath.ToSlash(filepath.Clean(filepath.FromSlash(item)))
		if clean == "." || clean == "/" || filepath.IsAbs(clean) || strings.HasPrefix(clean, "../") {
			continue
		}
		out[clean] = true
	}
	return out
}

func normalizeCopiedSkillRelPath(rel string) string {
	if strings.EqualFold(filepath.Base(rel), "SKILL.md") {
		dir := filepath.Dir(rel)
		if dir == "." {
			return "SKILL.md"
		}
		return filepath.Join(dir, "SKILL.md")
	}
	return rel
}

func (i GoGitRepositoryContentManager) cloneTargetRepository(ctx context.Context, repository repos.RepositoryIdentity, target string) (*git.Repository, error) {
	repo, err := i.cloneTargetRepositoryRaw(ctx, repository, target)
	if err != nil {
		return nil, mapSkillGitError(err)
	}
	return repo, nil
}

func (i GoGitRepositoryContentManager) cloneTargetRepositoryRaw(ctx context.Context, repository repos.RepositoryIdentity, target string) (*git.Repository, error) {
	remoteURL, err := i.remoteURL(repository)
	if err != nil {
		return nil, err
	}
	cloneOptions := &git.CloneOptions{URL: remoteURL, SingleBranch: true}
	if branch := strings.TrimSpace(repository.DefaultBranch); branch != "" {
		cloneOptions.ReferenceName = plumbing.NewBranchReferenceName(branch)
	}
	if auth := i.targetAuth(); auth != nil {
		cloneOptions.Auth = auth
	}
	repo, err := git.PlainCloneContext(ctx, target, false, cloneOptions)
	return repo, err
}

func (i GoGitRepositoryContentManager) tempDir(pattern string) (string, func(), error) {
	base := strings.TrimSpace(i.WorkDir)
	if base != "" {
		if err := os.MkdirAll(base, 0o755); err != nil {
			return "", func() {}, err
		}
	}
	dir, err := os.MkdirTemp(base, strings.TrimSpace(pattern))
	if err != nil {
		return "", func() {}, err
	}
	return dir, func() { _ = os.RemoveAll(dir) }, nil
}

func (i GoGitRepositoryContentManager) remoteURL(repository repos.RepositoryIdentity) (string, error) {
	full := strings.TrimSpace(repository.FullName)
	if full == "" {
		full = strings.TrimSpace(repository.Owner) + "/" + strings.TrimSpace(repository.Name)
	}
	owner, repo, ok := strings.Cut(full, "/")
	if !ok || strings.TrimSpace(owner) == "" || strings.TrimSpace(repo) == "" {
		return "", invalidArgument("valid skill repository is required")
	}
	parsed, err := url.Parse(strings.TrimRight(strings.TrimSpace(i.BaseURL), "/"))
	if err != nil || parsed.Scheme == "" || parsed.Host == "" {
		return "", invalidArgument("valid AGS base URL is required")
	}
	parsed.Path = strings.TrimRight(parsed.Path, "/") + "/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + ".git"
	parsed.RawQuery = ""
	parsed.Fragment = ""
	return parsed.String(), nil
}

func (i GoGitRepositoryContentManager) targetAuth() *skillGitAuth {
	if strings.TrimSpace(i.AuthorizationHeader) == "" {
		return nil
	}
	header := strings.TrimSpace(i.AuthHeader)
	if header == "" {
		header = "Authorization"
	}
	return &skillGitAuth{headerName: header, value: i.AuthorizationHeader}
}

type skillGitAuth struct{ headerName, value, username, password string }

func (a *skillGitAuth) SetAuth(r *http.Request) {
	if a == nil {
		return
	}
	if strings.TrimSpace(a.password) != "" {
		r.SetBasicAuth(strings.TrimSpace(a.username), strings.TrimSpace(a.password))
		return
	}
	if strings.TrimSpace(a.value) != "" {
		r.Header.Set(a.headerName, a.value)
	}
}
func (a *skillGitAuth) Name() string   { return "http-agentteam-auth-header" }
func (a *skillGitAuth) String() string { return a.Name() + " - *******" }

func mapSkillGitError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, gittransport.ErrEmptyRemoteRepository) {
		return codedInvalidArgument(SkillImportErrorRepositoryUnavailable, "repository is empty")
	}
	if errors.Is(err, git.ErrRepositoryNotExists) {
		return codedInvalidArgument(SkillImportErrorRepositoryUnavailable, "skill repository is unreachable")
	}
	msg := strings.ToLower(err.Error())
	switch {
	case strings.Contains(msg, "403") || strings.Contains(msg, "forbidden"):
		return codedForbidden(SkillImportErrorPermissionDenied, "repository access is forbidden")
	case strings.Contains(msg, "401") || strings.Contains(msg, "unauthorized") || strings.Contains(msg, "authentication required") || strings.Contains(msg, "authorization"):
		return codedInvalidArgument(SkillImportErrorPermissionDenied, "repository credentials were rejected")
	case strings.Contains(msg, "not found") || strings.Contains(msg, "repository not found"):
		return codedInvalidArgument(SkillImportErrorRepositoryUnavailable, "repository is unreachable")
	case strings.Contains(msg, "couldn't find remote ref") || strings.Contains(msg, "reference not found"):
		return codedInvalidArgument(SkillImportErrorRepositoryUnavailable, "repository ref was not found")
	default:
		return fmt.Errorf("clone skill repository: %w", err)
	}
}

type skillTemplateFile struct{ path, content string }

func skillTemplateFiles(slug, name, description, locale string) []skillTemplateFile {
	slug = strings.TrimSpace(slug)
	files, err := presets.SkillScaffoldFiles(name, description, locale)
	if err != nil {
		// Scaffold loading is fatal — without it the repository cannot be
		// initialized. Convert the error into a single empty result; callers
		// already surface write errors when they iterate the result.
		return nil
	}
	out := make([]skillTemplateFile, 0, len(files))
	for _, file := range files {
		content := file.Content
		if slug != "" {
			switch file.Path {
			case "README.md":
				content = seedSkillScaffoldReadme(content, slug, name, description)
			case "AGENTS.md":
				content = seedSkillScaffoldAgents(content)
			}
		}
		out = append(out, skillTemplateFile{path: file.Path, content: content})
	}
	if slug != "" {
		out = append(out, skillTemplateFile{
			path:    "skills/" + slug + "/SKILL.md",
			content: skillTemplateSkillFile(name, description),
		})
	}
	return out
}

func skillTemplateSkillFile(name, description string) string {
	frontmatter, err := yaml.Marshal(struct {
		Name        string `yaml:"name"`
		Description string `yaml:"description"`
	}{
		Name:        name,
		Description: description,
	})
	if err != nil {
		frontmatter = []byte("name: " + name + "\ndescription: " + description + "\n")
	}
	return fmt.Sprintf("---\n%s---\n\n# %s\n\n%s\n", frontmatter, name, description)
}

func seedSkillScaffoldReadme(content, slug, name, description string) string {
	row := fmt.Sprintf(
		"| %s | %s | %s |  |",
		sanitizeSkillIndexCell(name),
		sanitizeSkillIndexCell("skills/"+slug+"/SKILL.md"),
		sanitizeSkillIndexCell(description),
	)
	content = strings.ReplaceAll(content, "The initial template does not create a default skill.", "The initial template creates the first skill shown below.")
	content = strings.ReplaceAll(content, "当前模板不创建默认技能。", "当前模板会创建下方的初始技能。")
	content = strings.ReplaceAll(content, "初期テンプレートはデフォルトスキルを作成しません。", "初期テンプレートは下の初期スキルを作成します。")
	return strings.Replace(content, skillReadmeIndexEndMarker, row+"\n"+skillReadmeIndexEndMarker, 1)
}

func seedSkillScaffoldAgents(content string) string {
	content = strings.ReplaceAll(content, "New repository templates place only `README.md` under `skills/`; they do not create a default skill from the repository name.", "New repository templates include one initial skill under `skills/<skill-directory>/`; keep future skills under the same directory convention.")
	content = strings.ReplaceAll(content, "新仓库模板只在 `skills/` 下放置 `README.md`，不会按仓库名称创建默认技能。", "新仓库模板会在 `skills/<skill-directory>/` 下创建一个初始技能；后续技能沿用相同目录约定。")
	content = strings.ReplaceAll(content, "新規リポジトリテンプレートは `skills/` 配下に `README.md` だけを置き、リポジトリ名からデフォルトスキルを作成しません。", "新規リポジトリテンプレートは `skills/<skill-directory>/` 配下に初期スキルを 1 つ作成します。今後のスキルも同じディレクトリ規約に従います。")
	return content
}
