package skills

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func TestParseGitHubRef(t *testing.T) {
	tests := []struct {
		name           string
		repo           string
		wantOwner      string
		wantRepoName   string
		wantRef        string
		wantSubPath    string
		wantErr        bool
		wantErrContain string
	}{
		{
			name:         "simple owner/repo",
			repo:         "sipeed/picoclaw",
			wantOwner:    "sipeed",
			wantRepoName: "picoclaw",
			wantRef:      "main",
			wantSubPath:  "",
		},
		{
			name:         "owner/repo with subpath",
			repo:         "sipeed/picoclaw/skills/test",
			wantOwner:    "sipeed",
			wantRepoName: "picoclaw",
			wantRef:      "main",
			wantSubPath:  "skills/test",
		},
		{
			name:         "full URL with tree",
			repo:         "https://github.com/sipeed/picoclaw/tree/dev/skills/test",
			wantOwner:    "sipeed",
			wantRepoName: "picoclaw",
			wantRef:      "dev",
			wantSubPath:  "skills/test",
		},
		{
			name:         "full URL with blob",
			repo:         "https://github.com/sipeed/picoclaw/blob/main/README.md",
			wantOwner:    "sipeed",
			wantRepoName: "picoclaw",
			wantRef:      "main",
			wantSubPath:  "README.md",
		},
		{
			name:         "full URL without ref",
			repo:         "https://github.com/sipeed/picoclaw",
			wantOwner:    "sipeed",
			wantRepoName: "picoclaw",
			wantRef:      "main",
			wantSubPath:  "",
		},
		{
			name:           "invalid format - single part",
			repo:           "sipeed",
			wantErr:        true,
			wantErrContain: "expected 'owner/repo'",
		},
		{
			name:           "invalid URL",
			repo:           "http://[invalid",
			wantErr:        true,
			wantErrContain: "invalid URL",
		},
		{
			name:           "invalid GitHub URL - only one path part",
			repo:           "https://github.com/sipeed",
			wantErr:        true,
			wantErrContain: "invalid GitHub URL",
		},
		{
			name:         "with whitespace",
			repo:         "  sipeed/picoclaw  ",
			wantOwner:    "sipeed",
			wantRepoName: "picoclaw",
			wantRef:      "main",
			wantSubPath:  "",
		},
		{
			name:           "invalid non github host",
			repo:           "https://gitlab.com/sipeed/picoclaw/-/tree/main/skills/test",
			wantErr:        true,
			wantErrContain: `invalid GitHub URL host "gitlab.com"`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ref, err := parseGitHubRef(tt.repo)

			if tt.wantErr {
				if err == nil {
					t.Errorf("parseGitHubRef() error = nil, wantErr = true")
					return
				}
				if tt.wantErrContain != "" && !strings.Contains(err.Error(), tt.wantErrContain) {
					t.Errorf("parseGitHubRef() error = %v, want error containing %v", err, tt.wantErrContain)
				}
				return
			}

			if err != nil {
				t.Errorf("parseGitHubRef() unexpected error = %v", err)
				return
			}

			if ref.Owner != tt.wantOwner {
				t.Errorf("parseGitHubRef() owner = %v, want %v", ref.Owner, tt.wantOwner)
			}
			if ref.RepoName != tt.wantRepoName {
				t.Errorf("parseGitHubRef() repoName = %v, want %v", ref.RepoName, tt.wantRepoName)
			}
			if ref.Ref != tt.wantRef {
				t.Errorf("parseGitHubRef() ref = %v, want %v", ref.Ref, tt.wantRef)
			}
			if ref.SubPath != tt.wantSubPath {
				t.Errorf("parseGitHubRef() subPath = %v, want %v", ref.SubPath, tt.wantSubPath)
			}
		})
	}
}

func TestParseGitHubRefWithBaseURL(t *testing.T) {
	ref, err := parseGitHubRefWithBaseURL(
		"https://ghe.example.com/git/org/repo/tree/dev/skills/test",
		"https://ghe.example.com/git",
		"main",
	)
	if err != nil {
		t.Fatalf("parseGitHubRefWithBaseURL() unexpected error = %v", err)
	}
	if ref.Owner != "org" {
		t.Fatalf("owner = %q, want org", ref.Owner)
	}
	if ref.RepoName != "repo" {
		t.Fatalf("repo = %q, want repo", ref.RepoName)
	}
	if ref.Ref != "dev" {
		t.Fatalf("ref = %q, want dev", ref.Ref)
	}
	if ref.SubPath != "skills/test" {
		t.Fatalf("subPath = %q, want skills/test", ref.SubPath)
	}

	dirName, err := githubInstallDirNameWithBaseURL(
		"https://ghe.example.com/git/org/repo/tree/dev/skills/test",
		"https://ghe.example.com/git",
	)
	if err != nil {
		t.Fatalf("githubInstallDirNameWithBaseURL() unexpected error = %v", err)
	}
	if dirName != "test" {
		t.Fatalf("dirName = %q, want test", dirName)
	}

	dirName, err = githubInstallDirNameWithBaseURL(
		"https://ghe.example.com/git/org/repo/blob/dev/skills/test/SKILL.md",
		"https://ghe.example.com/git",
	)
	if err != nil {
		t.Fatalf("githubInstallDirNameWithBaseURL() unexpected error for blob skill url = %v", err)
	}
	if dirName != "test" {
		t.Fatalf("dirName for nested blob skill = %q, want test", dirName)
	}

	dirName, err = githubInstallDirNameWithBaseURL(
		"https://ghe.example.com/git/org/repo/blob/dev/SKILL.md",
		"https://ghe.example.com/git",
	)
	if err != nil {
		t.Fatalf("githubInstallDirNameWithBaseURL() unexpected error for repo root blob skill = %v", err)
	}
	if dirName != "repo" {
		t.Fatalf("dirName for repo root blob skill = %q, want repo", dirName)
	}

	ref, err = parseGitHubRefWithBaseURL("https://ghe.example.com/git/org/repo", "https://ghe.example.com/git", "")
	if err != nil {
		t.Fatalf("parseGitHubRefWithBaseURL() unexpected error = %v", err)
	}
	if ref.Ref != "" {
		t.Fatalf("ref = %q, want empty", ref.Ref)
	}

	ref, err = parseGitHubRefWithBaseURL(
		"https://github.com/org/repo/tree/feature/skills-registry/.agents/skills/pr-review",
		"",
		"main",
	)
	if err != nil {
		t.Fatalf("parseGitHubRefWithBaseURL() unexpected error for slash branch = %v", err)
	}
	if ref.Ref != "feature/skills-registry" {
		t.Fatalf("ref = %q, want feature/skills-registry", ref.Ref)
	}
	if ref.SubPath != ".agents/skills/pr-review" {
		t.Fatalf("subPath = %q, want .agents/skills/pr-review", ref.SubPath)
	}

	_, err = parseGitHubRefWithBaseURL(
		"https://gitlab.example.com/org/repo/-/tree/dev/skills/test",
		"https://ghe.example.com/git",
		"main",
	)
	if err == nil {
		t.Fatal("parseGitHubRefWithBaseURL() error = nil, want invalid host error")
	}
	if !strings.Contains(err.Error(), `invalid GitHub URL host "gitlab.example.com"`) {
		t.Fatalf("unexpected error = %v", err)
	}

	_, err = parseGitHubRefWithBaseURL(
		"http://ghe.example.com/git/org/repo/tree/dev/skills/test",
		"https://ghe.example.com/git",
		"main",
	)
	if err == nil {
		t.Fatal("parseGitHubRefWithBaseURL() error = nil, want invalid host error for scheme mismatch")
	}
	if !strings.Contains(err.Error(), `invalid GitHub URL host "ghe.example.com"`) {
		t.Fatalf("unexpected scheme mismatch error = %v", err)
	}

	_, err = parseGitHubRefWithBaseURL(
		"https://github.com/org/repo/pull/2442",
		"",
		"main",
	)
	if err == nil {
		t.Fatal("parseGitHubRefWithBaseURL() error = nil, want invalid repository URL path error")
	}
	if !strings.Contains(err.Error(), `invalid GitHub repository URL path "/org/repo/pull/2442"`) {
		t.Fatalf("unexpected PR URL error = %v", err)
	}

	_, err = parseGitHubRefWithBaseURL(
		"https://github.com/org/repo/tree",
		"",
		"main",
	)
	if err == nil {
		t.Fatal("parseGitHubRefWithBaseURL() error = nil, want invalid tree URL path error")
	}
	if !strings.Contains(err.Error(), `invalid GitHub tree URL path "/org/repo/tree"`) {
		t.Fatalf("unexpected short tree URL error = %v", err)
	}
}

func TestParseGitHubTargetWithBaseURLPreservesSourceEndpoints(t *testing.T) {
	target, err := parseGitHubTargetWithBaseURL(
		"https://github.com/org/repo/tree/main/.agents/skills/pr-review",
		"https://ghe.example.com/git",
		"",
	)
	if err != nil {
		t.Fatalf("parseGitHubTargetWithBaseURL() unexpected error = %v", err)
	}
	if target.Endpoints.WebBaseURL != "https://github.com" {
		t.Fatalf("web base = %q, want https://github.com", target.Endpoints.WebBaseURL)
	}
	if target.Endpoints.APIBaseURL != "https://api.github.com" {
		t.Fatalf("api base = %q, want https://api.github.com", target.Endpoints.APIBaseURL)
	}
	if target.Endpoints.RawBaseURL != "https://raw.githubusercontent.com" {
		t.Fatalf("raw base = %q, want https://raw.githubusercontent.com", target.Endpoints.RawBaseURL)
	}
	if target.Ref.Owner != "org" || target.Ref.RepoName != "repo" {
		t.Fatalf("unexpected ref = %+v", target.Ref)
	}
	if target.Ref.Ref != "main" {
		t.Fatalf("ref = %q, want main", target.Ref.Ref)
	}
	if target.Ref.SubPath != ".agents/skills/pr-review" {
		t.Fatalf("subPath = %q, want .agents/skills/pr-review", target.Ref.SubPath)
	}
}

func TestParseGitHubTargetWithBaseURLPreservesSlashBranchForRepoRootBlobSkill(t *testing.T) {
	target, err := parseGitHubTargetWithBaseURL(
		"https://github.com/org/repo/blob/feature/skills-registry/SKILL.md",
		"",
		"",
	)
	if err != nil {
		t.Fatalf("parseGitHubTargetWithBaseURL() unexpected error = %v", err)
	}
	if target.Ref.Ref != "feature/skills-registry" {
		t.Fatalf("ref = %q, want feature/skills-registry", target.Ref.Ref)
	}
	if target.Ref.SubPath != "SKILL.md" {
		t.Fatalf("subPath = %q, want SKILL.md", target.Ref.SubPath)
	}
}

func TestSkillInstallerResolveGitHubRefUsesDefaultBranch(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v3/repos/org/repo":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"default_branch":"master"}`))
		default:
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	installer, err := NewSkillInstallerWithBaseURL(t.TempDir(), server.URL, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstallerWithBaseURL() error = %v", err)
	}

	target, err := installer.resolveGitHubTarget(context.Background(), "org/repo/skills/test", "")
	if err != nil {
		t.Fatalf("resolveGitHubTarget() error = %v", err)
	}
	ref := target.Ref
	if ref.Ref != "master" {
		t.Fatalf("ref = %q, want master", ref.Ref)
	}
	if ref.SubPath != "skills/test" {
		t.Fatalf("subPath = %q, want skills/test", ref.SubPath)
	}
}

func TestSkillInstallerInstallFromGitHubToDirSupportsBlobSkillURL(t *testing.T) {
	tmpDir := t.TempDir()
	var server *httptest.Server
	server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/v3/repos/org/repo/contents/.agents/skills/pr-review":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`[
				{"type":"file","name":"SKILL.md","download_url":"` + server.URL + `/raw/org/repo/main/.agents/skills/pr-review/SKILL.md"},
				{"type":"dir","name":"scripts","url":"` + server.URL + `/api/v3/repos/org/repo/contents/.agents/skills/pr-review/scripts?ref=main"}
			]`))
		case "/api/v3/repos/org/repo/contents/.agents/skills/pr-review/scripts":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`[
				{"type":"file","name":"check.sh","download_url":"` + server.URL + `/raw/org/repo/main/.agents/skills/pr-review/scripts/check.sh"}
			]`))
		case "/raw/org/repo/main/.agents/skills/pr-review/SKILL.md":
			_, _ = w.Write([]byte("---\nname: pr-review\ndescription: PR review skill\n---\n# PR Review\n"))
		case "/raw/org/repo/main/.agents/skills/pr-review/scripts/check.sh":
			_, _ = w.Write([]byte("#!/bin/sh\nexit 0\n"))
		default:
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	installer, err := NewSkillInstallerWithBaseURL(tmpDir, server.URL, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstallerWithBaseURL() error = %v", err)
	}

	targetDir := filepath.Join(tmpDir, "skills", "pr-review")
	result, err := installer.InstallFromGitHubToDir(
		context.Background(),
		server.URL+"/org/repo/blob/main/.agents/skills/pr-review/SKILL.md",
		"",
		targetDir,
	)
	if err != nil {
		t.Fatalf("InstallFromGitHubToDir() error = %v", err)
	}
	if result.Version != "main" {
		t.Fatalf("version = %q, want main", result.Version)
	}

	content, err := os.ReadFile(filepath.Join(targetDir, "SKILL.md"))
	if err != nil {
		t.Fatalf("ReadFile(SKILL.md) error = %v", err)
	}
	if !strings.Contains(string(content), "name: pr-review") {
		t.Fatalf("SKILL.md content = %q, want skill metadata", string(content))
	}

	scriptPath := filepath.Join(targetDir, "scripts", "check.sh")
	if _, err := os.Stat(scriptPath); err != nil {
		t.Fatalf("Stat(scripts/check.sh) error = %v", err)
	}
}

func TestShouldDownload(t *testing.T) {
	tests := []struct {
		name string
		file string
		root bool
		want bool
	}{
		{"SKILL.md at root", "SKILL.md", true, true},
		{"other file at root", "README.md", true, false},
		{"script at root", "script.py", true, false},
		{"SKILL.md not at root", "SKILL.md", false, true},
		{"any file not at root", "any.txt", false, true},
		{"script not at root", "script.py", false, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := shouldDownload(tt.file, tt.root)
			if got != tt.want {
				t.Errorf("shouldDownload(%q, %v) = %v, want %v", tt.file, tt.root, got, tt.want)
			}
		})
	}
}

func TestIsSkillDirectory(t *testing.T) {
	tests := []struct {
		name string
		dir  string
		want bool
	}{
		{"scripts dir", "scripts", true},
		{"references dir", "references", true},
		{"assets dir", "assets", true},
		{"templates dir", "templates", true},
		{"docs dir", "docs", true},
		{"other dir", "other", false},
		{"src dir", "src", false},
		{"empty string", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isSkillDirectory(tt.dir)
			if got != tt.want {
				t.Errorf("isSkillDirectory(%q) = %v, want %v", tt.dir, got, tt.want)
			}
		})
	}
}

func TestNewSkillInstaller(t *testing.T) {
	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "test-token", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	if installer == nil {
		t.Fatal("NewSkillInstaller() returned nil")
	}

	if installer.workspace != tmpDir {
		t.Errorf("workspace = %v, want %v", installer.workspace, tmpDir)
	}

	if installer.githubToken != "test-token" {
		t.Errorf("githubToken = %v, want 'test-token'", installer.githubToken)
	}

	if installer.githubBaseURL != "https://github.com" {
		t.Errorf("githubBaseURL = %v, want https://github.com", installer.githubBaseURL)
	}
	if installer.githubAPIBaseURL != "https://api.github.com" {
		t.Errorf("githubAPIBaseURL = %v, want https://api.github.com", installer.githubAPIBaseURL)
	}
	if installer.githubRawBaseURL != "https://raw.githubusercontent.com" {
		t.Errorf("githubRawBaseURL = %v, want https://raw.githubusercontent.com", installer.githubRawBaseURL)
	}

	if installer.proxy != "" {
		t.Errorf("proxy = %v, want empty", installer.proxy)
	}

	if installer.client == nil {
		t.Error("client is nil")
	} else if installer.client.Timeout != 15*time.Second {
		t.Errorf("client.Timeout = %v, want 15s", installer.client.Timeout)
	}
}

func TestNewSkillInstaller_WithProxy(t *testing.T) {
	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "test-token", "http://127.0.0.1:7890")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	if installer.proxy != "http://127.0.0.1:7890" {
		t.Errorf("proxy = %v, want 'http://127.0.0.1:7890'", installer.proxy)
	}

	if installer.client == nil {
		t.Fatal("client is nil")
	}

	// Verify the transport has proxy configured
	transport, ok := installer.client.Transport.(*http.Transport)
	if !ok {
		t.Fatal("client.Transport is not *http.Transport")
	}

	if transport.Proxy == nil {
		t.Error("transport.Proxy is nil, expected non-nil")
	}
}

func TestNewSkillInstaller_WithBaseURL(t *testing.T) {
	tmpDir := t.TempDir()
	installer, err := NewSkillInstallerWithBaseURL(tmpDir, "https://github.example.com", "test-token", "")
	if err != nil {
		t.Fatalf("NewSkillInstallerWithBaseURL() error = %v", err)
	}

	if installer.githubBaseURL != "https://github.example.com" {
		t.Errorf("githubBaseURL = %v, want https://github.example.com", installer.githubBaseURL)
	}
	if installer.githubAPIBaseURL != "https://github.example.com/api/v3" {
		t.Errorf("githubAPIBaseURL = %v, want https://github.example.com/api/v3", installer.githubAPIBaseURL)
	}
	if installer.githubRawBaseURL != "https://github.example.com/raw" {
		t.Errorf("githubRawBaseURL = %v, want https://github.example.com/raw", installer.githubRawBaseURL)
	}
}

func TestNewSkillInstaller_InvalidProxy(t *testing.T) {
	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "test-token", "://invalid-proxy")
	if err == nil {
		t.Error("NewSkillInstaller() expected error for invalid proxy, got nil")
	}
	if installer != nil {
		t.Error("expected nil installer on error")
	}
}

func TestSkillInstaller_DownloadFile(t *testing.T) {
	// Create a test server that serves files
	content := "test file content for skill download"
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Errorf("expected GET, got %s", r.Method)
		}
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(content))
	}))
	defer server.Close()

	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	t.Run("successful download", func(t *testing.T) {
		localPath := filepath.Join(tmpDir, "test-skill", "SKILL.md")
		err := installer.downloadFile(context.Background(), server.URL, localPath)
		if err != nil {
			t.Errorf("downloadFile() error = %v", err)
			return
		}

		// Verify file was downloaded
		data, err := os.ReadFile(localPath)
		if err != nil {
			t.Errorf("failed to read downloaded file: %v", err)
			return
		}

		if string(data) != content {
			t.Errorf("downloaded content = %q, want %q", string(data), content)
		}

		// Check file permissions
		info, err := os.Stat(localPath)
		if err != nil {
			t.Errorf("failed to stat file: %v", err)
			return
		}

		if info.Mode().Perm() != 0o600 {
			t.Errorf("file permissions = %o, want %o", info.Mode().Perm(), 0o600)
		}
	})

	t.Run("http error", func(t *testing.T) {
		errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("not found"))
		}))
		defer errorServer.Close()

		localPath := filepath.Join(tmpDir, "error-test", "SKILL.md")
		err := installer.downloadFile(context.Background(), errorServer.URL, localPath)
		if err == nil {
			t.Error("downloadFile() expected error for 404, got nil")
		}
	})
}

func TestSkillInstaller_DownloadRaw(t *testing.T) {
	content := "raw skill content"
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(content))
	}))
	defer server.Close()

	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	// Replace the client with one that points to our test server
	// We need to modify the URL in the function, so we'll test indirectly

	localDir := filepath.Join(tmpDir, "raw-test")
	ctx := context.Background()

	// Create a simple test by calling downloadFile directly since downloadRaw
	// constructs its own URL
	testFile := filepath.Join(localDir, "SKILL.md")
	err = installer.downloadFile(ctx, server.URL, testFile)
	if err != nil {
		t.Errorf("downloadFile() error = %v", err)
	}

	// Verify file content
	data, err := os.ReadFile(testFile)
	if err != nil {
		t.Errorf("failed to read file: %v", err)
		return
	}

	if string(data) != content {
		t.Errorf("content = %q, want %q", string(data), content)
	}
}

func TestSkillInstaller_Uninstall(t *testing.T) {
	tmpDir := t.TempDir()
	skillsDir := filepath.Join(tmpDir, "skills")
	os.MkdirAll(skillsDir, 0o755)

	installer, err := NewSkillInstaller(tmpDir, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	t.Run("uninstall existing skill", func(t *testing.T) {
		skillName := "test-skill"
		skillDir := filepath.Join(skillsDir, skillName)

		// Create skill directory with a file
		os.MkdirAll(skillDir, 0o755)
		os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("test"), 0o644)

		if err := installer.Uninstall(skillName); err != nil {
			t.Errorf("Uninstall() error = %v", err)
		}

		// Verify directory was removed
		if _, err := os.Stat(skillDir); !os.IsNotExist(err) {
			t.Error("skill directory still exists after uninstall")
		}
	})

	t.Run("uninstall non-existent skill", func(t *testing.T) {
		if err := installer.Uninstall("non-existent-skill"); err == nil {
			t.Error("Uninstall() expected error for non-existent skill, got nil")
		} else if !strings.Contains(err.Error(), "not found") {
			t.Errorf("error message = %q, want 'not found'", err.Error())
		}
	})

	t.Run("uninstall with path separator", func(t *testing.T) {
		skillName := "owner/repo/skill-name"
		skillDir := filepath.Join(skillsDir, "skill-name")

		// Create skill directory
		os.MkdirAll(skillDir, 0o755)
		os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("test"), 0o644)

		if err := installer.Uninstall(skillName); err != nil {
			t.Errorf("Uninstall() error = %v", err)
		}

		if _, err := os.Stat(skillDir); !os.IsNotExist(err) {
			t.Error("skill directory still exists after uninstall")
		}
	})

	t.Run("uninstall with trailing slash", func(t *testing.T) {
		skillName := "skill-name/"
		skillDir := filepath.Join(skillsDir, "skill-name")

		// Create skill directory
		os.MkdirAll(skillDir, 0o755)
		os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("test"), 0o644)

		if err := installer.Uninstall(skillName); err != nil {
			t.Errorf("Uninstall() error = %v", err)
		}

		if _, err := os.Stat(skillDir); !os.IsNotExist(err) {
			t.Error("skill directory still exists after uninstall")
		}
	})
}

func TestSkillInstaller_InstallFromGitHub_SkillAlreadyExists(t *testing.T) {
	tmpDir := t.TempDir()
	skillsDir := filepath.Join(tmpDir, "skills")
	os.MkdirAll(skillsDir, 0o755)

	installer, err := NewSkillInstaller(tmpDir, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	// Create an existing skill directory
	existingSkill := filepath.Join(skillsDir, "picoclaw")
	os.MkdirAll(existingSkill, 0o755)
	os.WriteFile(filepath.Join(existingSkill, "SKILL.md"), []byte("existing"), 0o644)

	// Try to install the same skill - should fail
	err = installer.InstallFromGitHub(context.Background(), "sipeed/picoclaw")
	if err == nil {
		t.Error("InstallFromGitHub() expected error for existing skill, got nil")
	}
	if !strings.Contains(err.Error(), "already exists") {
		t.Errorf("error message = %q, want 'already exists'", err.Error())
	}
}

func TestGitHubContent_Struct(t *testing.T) {
	// Test that GitHubContent struct can be properly unmarshaled
	jsonData := `{
		"name": "test.md",
		"path": "skills/test.md",
		"type": "file",
		"download_url": "https://example.com/download",
		"url": "https://api.github.com/contents/skills/test.md"
	}`

	var content GitHubContent
	err := json.Unmarshal([]byte(jsonData), &content)
	if err != nil {
		t.Errorf("failed to unmarshal GitHubContent: %v", err)
	}

	if content.Name != "test.md" {
		t.Errorf("Name = %q, want 'test.md'", content.Name)
	}
	if content.Type != "file" {
		t.Errorf("Type = %q, want 'file'", content.Type)
	}
	if content.DownloadURL != "https://example.com/download" {
		t.Errorf("DownloadURL = %q, want 'https://example.com/download'", content.DownloadURL)
	}
}

func TestSkillInstaller_GetGithubDirAllFiles(t *testing.T) {
	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	// Create a test server that mimics GitHub API
	fileContent := "skill file content"
	var serverURL string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Check for authorization header
		authHeader := r.Header.Get("Authorization")
		if authHeader != "" && !strings.HasPrefix(authHeader, "Bearer ") {
			t.Errorf("expected Bearer token, got: %s", authHeader)
		}

		// Return different responses based on path
		if strings.Contains(r.URL.Path, "/contents") {
			// API response for directory listing
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)

			items := []map[string]any{
				{
					"name":         "SKILL.md",
					"path":         "SKILL.md",
					"type":         "file",
					"download_url": serverURL + "/download/SKILL.md",
				},
				{
					"name": "scripts",
					"path": "scripts",
					"type": "dir",
					"url":  serverURL + "/api/scripts",
				},
			}
			json.NewEncoder(w).Encode(items)
		} else if strings.Contains(r.URL.Path, "/api/scripts") {
			// API response for scripts subdirectory
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)

			items := []map[string]any{
				{
					"name":         "test.py",
					"path":         "scripts/test.py",
					"type":         "file",
					"download_url": serverURL + "/download/test.py",
				},
			}
			json.NewEncoder(w).Encode(items)
		} else if strings.Contains(r.URL.Path, "/download/") {
			// Raw file download
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(fileContent))
		} else {
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	serverURL = server.URL
	defer server.Close()

	localDir := filepath.Join(tmpDir, "test-skill")

	t.Run("download from GitHub API", func(t *testing.T) {
		err := installer.getGithubDirAllFiles(context.Background(), server.URL+"/contents", localDir, true)
		if err != nil {
			t.Errorf("getGithubDirAllFiles() error = %v", err)
			return
		}

		// Verify SKILL.md was downloaded
		skillMd := filepath.Join(localDir, "SKILL.md")
		data, err := os.ReadFile(skillMd)
		if err != nil {
			t.Errorf("failed to read SKILL.md: %v", err)
			return
		}
		if string(data) != fileContent {
			t.Errorf("SKILL.md content = %q, want %q", string(data), fileContent)
		}

		// Verify scripts directory and file
		scriptFile := filepath.Join(localDir, "scripts", "test.py")
		data, err = os.ReadFile(scriptFile)
		if err != nil {
			t.Errorf("failed to read test.py: %v", err)
			return
		}
		if string(data) != fileContent {
			t.Errorf("test.py content = %q, want %q", string(data), fileContent)
		}
	})

	t.Run("http error response", func(t *testing.T) {
		errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusForbidden)
		}))
		defer errorServer.Close()

		err := installer.getGithubDirAllFiles(
			context.Background(),
			errorServer.URL,
			filepath.Join(tmpDir, "error-test"),
			true,
		)
		if err == nil {
			t.Error("getGithubDirAllFiles() expected error for 403, got nil")
		}
	})
}

func TestSkillInstaller_InstallFromGitHub_WithToken(t *testing.T) {
	tmpDir := t.TempDir()
	skillsDir := filepath.Join(tmpDir, "skills")
	os.MkdirAll(skillsDir, 0o755)

	var serverURL string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Capture the authorization header
		authHeader := r.Header.Get("Authorization")
		if authHeader != "" {
			tokenReceived := strings.TrimPrefix(authHeader, "Bearer ")
			t.Fatalf("github token is %s", tokenReceived)
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)

		items := []map[string]any{
			{
				"name":         "SKILL.md",
				"path":         "SKILL.md",
				"type":         "file",
				"download_url": serverURL + "/download/SKILL.md",
			},
		}
		json.NewEncoder(w).Encode(items)
	}))
	serverURL = server.URL
	defer server.Close()

	installer, err := NewSkillInstaller(tmpDir, "test-github-token", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	// We need to test the token is passed - the actual install will fail
	// because we're not fully mocking the download, but we can verify
	// the token is sent in the request

	// Use a simple context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// The install will fail because download URL isn't properly set up,
	// but the token should be sent in the API request
	_ = installer.InstallFromGitHub(ctx, "owner/repo")

	// Note: We can't easily intercept the download request since it's a different URL,
	// but the fact that the API request was made verifies the token flow
	// In a real scenario, the token would be sent to both API and raw downloads
}

func TestSkillInstaller_ContextCancellation(t *testing.T) {
	tmpDir := t.TempDir()
	installer, err := NewSkillInstaller(tmpDir, "", "")
	if err != nil {
		t.Fatalf("NewSkillInstaller() error = %v", err)
	}

	// Create a slow server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(100 * time.Millisecond)
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("response"))
	}))
	defer server.Close()

	// Create a canceled context
	ctx, cancel := context.WithCancel(context.Background())
	cancel() // Cancel immediately

	localPath := filepath.Join(tmpDir, "cancel-test", "file.txt")
	err = installer.downloadFile(ctx, server.URL, localPath)

	if err == nil {
		t.Error("downloadFile() expected error for canceled context, got nil")
	}
}
