//go:build functional
// +build functional

package functional

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"code.gitea.io/sdk/gitea"
)

// ===== SDK API tests (verify test environment and API connectivity) =====

func TestAPIConnection(t *testing.T) {
	env := NewTestEnv(t)
	env.VerifyAPIConnection()
}

func TestListRepositories(t *testing.T) {
	env := NewTestEnv(t)

	repos, _, err := env.Client.ListUserRepos(env.Owner, gitea.ListReposOptions{})
	if err != nil {
		t.Fatalf("failed to list repositories: %v", err)
	}

	if len(repos) == 0 {
		t.Fatalf("expected at least one repository, got none")
	}

	t.Logf("Found %d repositories", len(repos))

	found := false
	for _, repo := range repos {
		if repo.Name == env.RepoName {
			found = true
			t.Logf("Found test repository: %s", repo.FullName)
			break
		}
	}

	if !found {
		t.Fatalf("test repository %s not found in user's repositories", env.RepoName)
	}
}

func TestGetRepository(t *testing.T) {
	env := NewTestEnv(t)

	repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName)
	if err != nil {
		t.Fatalf("failed to get repository: %v", err)
	}

	if repo.Name != env.RepoName {
		t.Fatalf("expected repo name %s, got %s", env.RepoName, repo.Name)
	}

	if repo.Owner.UserName != env.Owner {
		t.Fatalf("expected owner %s, got %s", env.Owner, repo.Owner.UserName)
	}

	t.Logf("Repository: %s (%s)", repo.FullName, repo.Description)
}

func TestAPIErrorHandling(t *testing.T) {
	env := NewTestEnv(t)

	_, _, err := env.Client.GetIssue(env.Owner, env.RepoName, 999999)
	if err == nil {
		t.Fatalf("expected error for non-existent issue, got none")
	}

	t.Logf("Properly handled error: %v", err)
}

// ===== CLI Issue Commands =====

func TestCLIIssueList(t *testing.T) {
	env := NewTestEnv(t)

	// Create a test issue so the list is not empty
	issueNum := env.CreateTestIssue("[FGJ E2E Test] Issue List", "For issue list test")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "list",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue list failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if !strings.Contains(result.Stdout, "Issue List") {
		t.Fatalf("expected issue list output to contain our test issue, got: %s", result.Stdout)
	}

	t.Logf("Successfully listed issues via CLI")
}

func TestCLIIssueListJSON(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] JSON List", "For JSON output test")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "list", "--json",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue list --json failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var issues []interface{}
	if err := json.Unmarshal([]byte(result.Stdout), &issues); err != nil {
		t.Fatalf("failed to parse JSON output: %v\nOutput: %s", err, result.Stdout)
	}

	if len(issues) == 0 {
		t.Fatalf("expected at least one issue in JSON output")
	}

	t.Logf("Successfully listed %d issues via CLI with --json", len(issues))
}

func TestCLIIssueView(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] View Test", "Testing issue view")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "view", fmt.Sprintf("%d", issueNum),
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue view failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if !strings.Contains(result.Stdout, "View Test") {
		t.Fatalf("expected issue view output to contain title, got: %s", result.Stdout)
	}

	t.Logf("Successfully viewed issue #%d via CLI", issueNum)
}

func TestCLIIssueViewJSON(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] JSON View", "Testing JSON view")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "view", fmt.Sprintf("%d", issueNum), "--json",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue view --json failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var data map[string]interface{}
	if err := json.Unmarshal([]byte(result.Stdout), &data); err != nil {
		t.Fatalf("failed to parse JSON output: %v\nOutput: %s", err, result.Stdout)
	}

	// issue view --json wraps the issue in an "issue" key
	issueObj, _ := data["issue"].(map[string]interface{})
	if issueObj == nil {
		// fallback: maybe it's flat
		issueObj = data
	}
	if title, ok := issueObj["title"].(string); !ok || !strings.Contains(title, "JSON View") {
		t.Fatalf("expected title containing 'JSON View' in JSON output, got: %v", issueObj["title"])
	}

	t.Logf("Successfully viewed issue #%d via CLI with --json", issueNum)
}

func TestCLIIssueCreate(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "create",
		"-t", "[FGJ E2E Test] CLI Created Issue",
		"-b", "Created directly via fj CLI",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue create failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var issueNum int64
	for _, line := range strings.Split(strings.TrimSpace(result.Stdout), "\n") {
		if _, err := fmt.Sscanf(line, "Issue created: #%d", &issueNum); err == nil {
			break
		}
	}
	if issueNum == 0 {
		t.Fatalf("failed to parse issue number from output: %s", result.Stdout)
	}

	defer env.CleanupIssue(issueNum)
	t.Logf("Successfully created issue #%d via CLI", issueNum)
}

func TestCLIIssueCreateWithLabels(t *testing.T) {
	env := NewTestEnv(t)

	env.EnsureTestLabels()

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "create",
		"-t", "[FGJ E2E Test] Issue with Labels",
		"-b", "This issue was created with labels",
		"-l", "bug",
		"-l", "enhancement",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue create with labels failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var issueNum int64
	for _, line := range strings.Split(strings.TrimSpace(result.Stdout), "\n") {
		if _, err := fmt.Sscanf(line, "Issue created: #%d", &issueNum); err == nil {
			break
		}
	}
	if issueNum == 0 {
		t.Fatalf("failed to parse issue number from output: %s", result.Stdout)
	}

	defer env.CleanupIssue(issueNum)

	labels, err := env.GetIssueLabels(issueNum)
	if err != nil {
		t.Fatalf("failed to get issue labels: %v", err)
	}

	if len(labels) != 2 {
		t.Fatalf("expected 2 labels, got %d", len(labels))
	}

	labelNames := make(map[string]bool)
	for _, label := range labels {
		labelNames[label.Name] = true
	}

	if !labelNames["bug"] || !labelNames["enhancement"] {
		t.Fatalf("expected labels 'bug' and 'enhancement', got %v", labelNames)
	}

	t.Logf("Successfully created issue #%d with labels: bug, enhancement", issueNum)
}

func TestCLIIssueComment(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] Comment Test", "Testing comment via CLI")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "comment",
		fmt.Sprintf("%d", issueNum),
		"-b", "CLI comment from functional test",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue comment failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	// Verify comment was added via API
	comments, _, err := env.Client.ListIssueComments(env.Owner, env.RepoName, issueNum, gitea.ListIssueCommentOptions{})
	if err != nil {
		t.Fatalf("failed to list comments: %v", err)
	}

	found := false
	for _, c := range comments {
		if c.Body == "CLI comment from functional test" {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("comment not found on issue #%d", issueNum)
	}

	t.Logf("Successfully commented on issue #%d via CLI", issueNum)
}

func TestCLIIssueClose(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] Close Test", "Will be closed via CLI")

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "close",
		fmt.Sprintf("%d", issueNum),
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue close failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
	if err != nil {
		t.Fatalf("failed to get issue: %v", err)
	}

	if issue.State != "closed" {
		t.Fatalf("expected issue state 'closed', got '%s'", issue.State)
	}

	t.Logf("Successfully closed issue #%d via CLI", issueNum)
}

func TestCLIIssueCloseWithComment(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] Close with comment", "Will be closed with a comment")

	commentText := "Fixed in v2.0 - closing via functional test"

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "close",
		fmt.Sprintf("%d", issueNum),
		"-c", commentText,
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue close -c failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
	if err != nil {
		t.Fatalf("failed to get issue: %v", err)
	}

	if issue.State != "closed" {
		t.Fatalf("expected issue state 'closed', got '%s'", issue.State)
	}

	comments, _, err := env.Client.ListIssueComments(env.Owner, env.RepoName, issueNum, gitea.ListIssueCommentOptions{})
	if err != nil {
		t.Fatalf("failed to list issue comments: %v", err)
	}

	found := false
	for _, comment := range comments {
		if comment.Body == commentText {
			found = true
			break
		}
	}

	if !found {
		t.Fatalf("comment '%s' not found in issue comments", commentText)
	}

	t.Logf("Successfully closed issue #%d with comment via CLI", issueNum)
}

func TestCLIIssueEditTitle(t *testing.T) {
	env := NewTestEnv(t)

	issueNum := env.CreateTestIssue("[FGJ E2E Test] Original Title", "Will be edited")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "edit",
		fmt.Sprintf("%d", issueNum),
		"-t", "[FGJ E2E Test] Updated Title",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue edit failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum)
	if err != nil {
		t.Fatalf("failed to get issue: %v", err)
	}

	if issue.Title != "[FGJ E2E Test] Updated Title" {
		t.Fatalf("expected updated title, got '%s'", issue.Title)
	}

	t.Logf("Successfully edited issue #%d title via CLI", issueNum)
}

func TestCLIIssueEditAddLabels(t *testing.T) {
	env := NewTestEnv(t)

	env.EnsureTestLabels()

	issueNum := env.CreateTestIssue("[FGJ E2E Test] Add Labels", "Will have labels added")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "edit",
		fmt.Sprintf("%d", issueNum),
		"--add-label", "bug",
		"--add-label", "help-wanted",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue edit add-label failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	labels, err := env.GetIssueLabels(issueNum)
	if err != nil {
		t.Fatalf("failed to get labels after edit: %v", err)
	}

	if len(labels) != 2 {
		t.Fatalf("expected 2 labels after edit, got %d", len(labels))
	}

	labelNames := make(map[string]bool)
	for _, label := range labels {
		labelNames[label.Name] = true
	}

	if !labelNames["bug"] || !labelNames["help-wanted"] {
		t.Fatalf("expected labels 'bug' and 'help-wanted', got %v", labelNames)
	}

	t.Logf("Successfully added labels to issue #%d via CLI", issueNum)
}

func TestCLIIssueEditRemoveLabels(t *testing.T) {
	env := NewTestEnv(t)

	env.EnsureTestLabels()

	issue, _, err := env.Client.CreateIssue(env.Owner, env.RepoName, gitea.CreateIssueOption{
		Title: "[FGJ E2E Test] Remove Labels",
		Body:  "Will have labels removed",
	})
	if err != nil {
		t.Fatalf("failed to create issue: %v", err)
	}
	defer env.CleanupIssue(issue.Index)

	labelIDs := env.GetLabelIDs([]string{"bug", "enhancement"})
	_, _, err = env.Client.AddIssueLabels(env.Owner, env.RepoName, issue.Index, gitea.IssueLabelsOption{
		Labels: labelIDs,
	})
	if err != nil {
		t.Fatalf("failed to add initial labels: %v", err)
	}

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "edit",
		fmt.Sprintf("%d", issue.Index),
		"--remove-label", "bug",
	)

	if result.ExitCode != 0 {
		t.Fatalf("issue edit remove-label failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	labels, err := env.GetIssueLabels(issue.Index)
	if err != nil {
		t.Fatalf("failed to get labels after edit: %v", err)
	}

	if len(labels) != 1 {
		t.Fatalf("expected 1 label after removal, got %d", len(labels))
	}

	if labels[0].Name != "enhancement" {
		t.Fatalf("expected remaining label 'enhancement', got '%s'", labels[0].Name)
	}

	t.Logf("Successfully removed label from issue #%d via CLI", issue.Index)
}

// ===== CLI PR Commands =====

func TestCLIPRList(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"pr", "list",
	)

	if result.ExitCode != 0 {
		t.Fatalf("pr list failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	t.Logf("Successfully listed pull requests via CLI")
}

func TestCLIPRListJSON(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"pr", "list", "--json",
	)

	if result.ExitCode != 0 {
		t.Fatalf("pr list --json failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var prs []interface{}
	if err := json.Unmarshal([]byte(result.Stdout), &prs); err != nil {
		t.Fatalf("failed to parse JSON output: %v\nOutput: %s", err, result.Stdout)
	}

	t.Logf("Successfully listed %d PRs via CLI with --json", len(prs))
}

func TestCLIPRView(t *testing.T) {
	env := NewTestEnv(t)

	prs, err := env.ListPullRequests()
	if err != nil {
		t.Fatalf("failed to list PRs: %v", err)
	}

	if len(prs) == 0 {
		t.Skip("No PRs available to view")
	}

	prNumber := prs[0].Index

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"pr", "view",
		fmt.Sprintf("%d", prNumber),
	)

	if result.ExitCode != 0 {
		t.Fatalf("pr view failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if result.Stdout == "" {
		t.Fatalf("pr view produced no output")
	}

	t.Logf("Successfully viewed PR #%d via CLI", prNumber)
}

func TestCLIPRDiff(t *testing.T) {
	env := NewTestEnv(t)

	prs, err := env.ListPullRequests()
	if err != nil {
		t.Fatalf("failed to list PRs: %v", err)
	}

	if len(prs) == 0 {
		t.Skip("No PRs available for diff test")
	}

	prNumber := prs[0].Index

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"pr", "diff",
		fmt.Sprintf("%d", prNumber),
		"--name-only",
	)

	if result.ExitCode != 0 {
		t.Fatalf("pr diff failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if strings.TrimSpace(result.Stdout) == "" {
		t.Fatalf("pr diff --name-only produced no output")
	}

	t.Logf("Successfully retrieved diff for PR #%d via CLI", prNumber)
}

func TestCLIPRComment(t *testing.T) {
	env := NewTestEnv(t)

	// PRs share the comment API with issues
	issueNum := env.CreateTestIssue("[FGJ E2E Test] PR Comment Test", "Testing pr comment command")
	defer env.CleanupIssue(issueNum)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"pr", "comment",
		fmt.Sprintf("%d", issueNum),
		"-b", "Automated test comment via fj pr comment",
	)

	if result.ExitCode != 0 {
		t.Fatalf("pr comment failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	t.Logf("Successfully commented on issue #%d via fj pr comment", issueNum)
}

// ===== CLI Repo Commands =====

func TestCLIRepoView(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"repo", "view",
		fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
	)

	if result.ExitCode != 0 {
		t.Fatalf("repo view failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if !strings.Contains(result.Stdout, env.RepoName) {
		t.Fatalf("expected repo name in output, got: %s", result.Stdout)
	}

	t.Logf("Successfully viewed repo via CLI")
}

func TestCLIRepoList(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"repo", "list",
	)

	if result.ExitCode != 0 {
		t.Fatalf("repo list failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if !strings.Contains(result.Stdout, env.RepoName) {
		t.Fatalf("expected test repo in list output, got: %s", result.Stdout)
	}

	t.Logf("Successfully listed repos via CLI")
}

func TestCLIRepoCreate(t *testing.T) {
	env := NewTestEnv(t)

	repoName := fmt.Sprintf("fj-test-create-%d", time.Now().UnixNano())
	defer env.CleanupRepo(env.Owner, repoName)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"repo", "create", repoName,
		"--public",
		"-d", "Created by fj functional test",
	)

	if result.ExitCode != 0 {
		t.Fatalf("repo create failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if !strings.Contains(result.Stdout, repoName) {
		t.Fatalf("expected output to contain repo name %q, got: %s", repoName, result.Stdout)
	}

	repo, _, err := env.Client.GetRepo(env.Owner, repoName)
	if err != nil {
		t.Fatalf("repo was not created or not accessible: %v", err)
	}
	if repo.Private {
		t.Fatalf("expected public repo, got private")
	}
	if repo.Description != "Created by fj functional test" {
		t.Fatalf("expected description %q, got %q", "Created by fj functional test", repo.Description)
	}

	t.Logf("Successfully created repository %s via CLI", repo.FullName)
}

func TestCLIRepoEdit(t *testing.T) {
	env := NewTestEnv(t)

	repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName)
	if err != nil {
		t.Fatalf("failed to get repo: %v", err)
	}
	originalDesc := repo.Description

	defer func() {
		env.RunCLI(
			"--hostname", env.Hostname,
			"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
			"repo", "edit",
			"-d", originalDesc,
		)
	}()

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"repo", "edit",
		"-d", "Updated description via functional test",
	)

	if result.ExitCode != 0 {
		t.Fatalf("repo edit failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	updatedRepo, _, err := env.Client.GetRepo(env.Owner, env.RepoName)
	if err != nil {
		t.Fatalf("failed to get repo after edit: %v", err)
	}

	if updatedRepo.Description != "Updated description via functional test" {
		t.Fatalf("expected description %q, got %q",
			"Updated description via functional test", updatedRepo.Description)
	}

	t.Logf("Successfully edited repo description via CLI")
}

func TestCLIRepoClone(t *testing.T) {
	env := NewTestEnv(t)

	tmpDir := t.TempDir()
	clonePath := fmt.Sprintf("%s/fj-clone", tmpDir)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"repo", "clone",
		fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		clonePath,
	)

	if result.ExitCode != 0 {
		t.Skip("repo clone requires full authentication setup")
	}

	gitDir := fmt.Sprintf("%s/.git", clonePath)
	if _, err := os.Stat(gitDir); err != nil {
		t.Logf("Warning: .git directory not found, clone may not have completed")
		return
	}

	t.Logf("Successfully cloned repository to %s via CLI", clonePath)
}

// ===== CLI Release Commands =====

func TestCLIReleaseList(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"release", "list",
	)

	if result.ExitCode != 0 {
		t.Fatalf("release list failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	t.Logf("Successfully listed releases via CLI")
}

func TestCLIReleaseCreateUploadDelete(t *testing.T) {
	env := NewTestEnv(t)

	tag := fmt.Sprintf("fj-test-%d", time.Now().UnixNano())
	title := "FGJ CLI Release Test"
	notes := "Release created by functional tests"

	tmpDir := t.TempDir()
	assetPath := fmt.Sprintf("%s/asset.txt", tmpDir)
	if err := os.WriteFile(assetPath, []byte("fj release asset"), 0600); err != nil {
		t.Fatalf("failed to create asset file: %v", err)
	}

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"release", "create", tag,
		"-t", title,
		"-n", notes,
		assetPath,
	)
	if result.ExitCode != 0 {
		t.Fatalf("release create failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	release, _, err := env.Client.GetReleaseByTag(env.Owner, env.RepoName, tag)
	if err != nil {
		t.Fatalf("failed to fetch created release: %v", err)
	}
	if release.Title != title {
		t.Fatalf("expected release title %q, got %q", title, release.Title)
	}

	attachments, _, err := env.Client.ListReleaseAttachments(env.Owner, env.RepoName, release.ID, gitea.ListReleaseAttachmentsOptions{})
	if err != nil {
		t.Fatalf("failed to list release assets: %v", err)
	}
	found := false
	for _, attachment := range attachments {
		if attachment.Name == "asset.txt" {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("uploaded asset not found in release")
	}

	deleteResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"release", "delete", tag,
	)
	if deleteResult.ExitCode != 0 {
		t.Fatalf("release delete failed with exit code %d: %s", deleteResult.ExitCode, deleteResult.Stderr)
	}

	_, _, err = env.Client.GetReleaseByTag(env.Owner, env.RepoName, tag)
	if err == nil {
		t.Fatalf("expected release %s to be deleted, but it still exists", tag)
	}
}

func TestCLIReleaseView(t *testing.T) {
	env := NewTestEnv(t)

	// Create a release to view
	tag := fmt.Sprintf("fj-view-test-%d", time.Now().UnixNano())

	createResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"release", "create", tag,
		"-t", "View Test Release",
		"-n", "For release view test",
	)
	if createResult.ExitCode != 0 {
		t.Fatalf("release create failed: %s", createResult.Stderr)
	}

	defer env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"release", "delete", tag,
	)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"release", "view", tag,
	)

	if result.ExitCode != 0 {
		t.Fatalf("release view failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	if !strings.Contains(result.Stdout, "View Test Release") {
		t.Fatalf("expected release title in output, got: %s", result.Stdout)
	}

	t.Logf("Successfully viewed release %s via CLI", tag)
}

// ===== CLI Label Commands =====

func TestCLILabelCreateListEditDelete(t *testing.T) {
	env := NewTestEnv(t)

	rf := fmt.Sprintf("%s/%s", env.Owner, env.RepoName)

	createResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"label", "create", "test-label",
		"--color", "00ff00",
		"-d", "Test label for functional tests",
	)

	if createResult.ExitCode != 0 {
		t.Fatalf("label create failed with exit code %d: %s", createResult.ExitCode, createResult.Stderr)
	}

	listResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"label", "list",
	)

	if listResult.ExitCode != 0 {
		t.Fatalf("label list failed with exit code %d: %s", listResult.ExitCode, listResult.Stderr)
	}

	if !strings.Contains(listResult.Stdout, "test-label") {
		t.Fatalf("label list output does not contain 'test-label': %s", listResult.Stdout)
	}

	editResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"label", "edit", "test-label",
		"--name", "test-label-renamed",
	)

	if editResult.ExitCode != 0 {
		t.Fatalf("label edit failed with exit code %d: %s", editResult.ExitCode, editResult.Stderr)
	}

	deleteResult := env.runCLIWithStdin("y\n",
		"--hostname", env.Hostname,
		"-R", rf,
		"label", "delete", "test-label-renamed",
	)

	if deleteResult.ExitCode != 0 {
		t.Fatalf("label delete failed with exit code %d: %s", deleteResult.ExitCode, deleteResult.Stderr)
	}

	t.Logf("Successfully created, listed, edited, and deleted label via CLI")
}

// ===== CLI Milestone Commands =====

func TestCLIMilestoneCreateListViewEditDelete(t *testing.T) {
	env := NewTestEnv(t)

	rf := fmt.Sprintf("%s/%s", env.Owner, env.RepoName)

	createResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"milestone", "create", "Test Milestone",
		"-d", "For testing",
	)

	if createResult.ExitCode != 0 {
		t.Fatalf("milestone create failed with exit code %d: %s", createResult.ExitCode, createResult.Stderr)
	}

	listResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"milestone", "list",
	)

	if listResult.ExitCode != 0 {
		t.Fatalf("milestone list failed with exit code %d: %s", listResult.ExitCode, listResult.Stderr)
	}

	if !strings.Contains(listResult.Stdout, "Test Milestone") {
		t.Fatalf("milestone list output does not contain 'Test Milestone': %s", listResult.Stdout)
	}

	viewResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"milestone", "view", "Test Milestone",
	)

	if viewResult.ExitCode != 0 {
		t.Fatalf("milestone view failed with exit code %d: %s", viewResult.ExitCode, viewResult.Stderr)
	}

	editResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"milestone", "edit", "Test Milestone",
		"--title", "Updated Milestone",
	)

	if editResult.ExitCode != 0 {
		t.Fatalf("milestone edit failed with exit code %d: %s", editResult.ExitCode, editResult.Stderr)
	}

	deleteResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"milestone", "delete", "Updated Milestone",
	)

	if deleteResult.ExitCode != 0 {
		t.Fatalf("milestone delete failed with exit code %d: %s", deleteResult.ExitCode, deleteResult.Stderr)
	}

	t.Logf("Successfully created, listed, viewed, edited, and deleted milestone via CLI")
}

// ===== CLI Wiki Commands =====

func TestCLIWikiCreateListViewEditDelete(t *testing.T) {
	env := NewTestEnv(t)

	rf := fmt.Sprintf("%s/%s", env.Owner, env.RepoName)

	createResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"wiki", "create", "Test Page",
		"-b", "# Test\nContent",
	)

	if createResult.ExitCode != 0 {
		t.Fatalf("wiki create failed with exit code %d: %s", createResult.ExitCode, createResult.Stderr)
	}

	defer func() {
		env.RunCLI(
			"--hostname", env.Hostname,
			"-R", rf,
			"wiki", "delete", "Test Page",
		)
	}()

	listResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"wiki", "list",
	)

	if listResult.ExitCode != 0 {
		t.Fatalf("wiki list failed with exit code %d: %s", listResult.ExitCode, listResult.Stderr)
	}

	if !strings.Contains(listResult.Stdout, "Test Page") {
		t.Fatalf("wiki list output does not contain 'Test Page': %s", listResult.Stdout)
	}

	viewResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"wiki", "view", "Test Page",
	)

	if viewResult.ExitCode != 0 {
		t.Fatalf("wiki view failed with exit code %d: %s", viewResult.ExitCode, viewResult.Stderr)
	}

	if !strings.Contains(viewResult.Stdout, "Content") {
		t.Fatalf("wiki view output does not contain 'Content': %s", viewResult.Stdout)
	}

	editResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"wiki", "edit", "Test Page",
		"-b", "Updated content",
	)

	if editResult.ExitCode != 0 {
		t.Fatalf("wiki edit failed with exit code %d: %s", editResult.ExitCode, editResult.Stderr)
	}

	deleteResult := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", rf,
		"wiki", "delete", "Test Page",
	)

	if deleteResult.ExitCode != 0 {
		t.Fatalf("wiki delete failed with exit code %d: %s", deleteResult.ExitCode, deleteResult.Stderr)
	}

	t.Logf("Successfully created, listed, viewed, edited, and deleted wiki page via CLI")
}

// ===== CLI Actions Commands =====

func TestCLIActionsRunList(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"actions", "run", "list",
	)

	// Actions might not be enabled, so we accept both success and failure
	if result.ExitCode != 0 {
		if bytes.Contains([]byte(result.Stderr), []byte("404")) {
			t.Logf("Note: Actions not available on this instance")
			return
		}
		t.Fatalf("actions run list failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	t.Logf("Successfully listed workflow runs via CLI")
}

// ===== CLI API Command =====

func TestCLIAPIGet(t *testing.T) {
	env := NewTestEnv(t)

	endpoint := fmt.Sprintf("/repos/%s/%s", env.Owner, env.RepoName)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"api", endpoint,
	)

	if result.ExitCode != 0 {
		t.Fatalf("api GET failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var data map[string]interface{}
	if err := json.Unmarshal([]byte(result.Stdout), &data); err != nil {
		t.Fatalf("failed to parse JSON output: %v", err)
	}

	name, ok := data["name"].(string)
	if !ok || name != env.RepoName {
		t.Fatalf("expected repo name %q in JSON output, got %v", env.RepoName, data["name"])
	}

	t.Logf("Successfully retrieved repo info via fj api GET")
}

func TestCLIAPIPostAndDelete(t *testing.T) {
	env := NewTestEnv(t)

	endpoint := fmt.Sprintf("/repos/%s/%s/issues", env.Owner, env.RepoName)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"api", endpoint,
		"-X", "POST",
		"-f", "title=[FGJ E2E Test] API Post Test",
		"-f", "body=Created via fj api command",
	)

	if result.ExitCode != 0 {
		t.Fatalf("api POST failed with exit code %d: %s", result.ExitCode, result.Stderr)
	}

	var issueData map[string]interface{}
	if err := json.Unmarshal([]byte(result.Stdout), &issueData); err != nil {
		t.Fatalf("failed to parse JSON response: %v", err)
	}

	issueNumber, ok := issueData["number"].(float64)
	if !ok || issueNumber == 0 {
		t.Fatalf("expected issue number in response, got %v", issueData["number"])
	}

	issueNum := int64(issueNumber)
	defer env.CleanupIssue(issueNum)

	t.Logf("Successfully created issue #%d via fj api POST", issueNum)
}

// ===== Structured Error Output =====

func TestCLIJSONErrors(t *testing.T) {
	env := NewTestEnv(t)

	result := env.RunCLI(
		"--hostname", env.Hostname,
		"-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName),
		"issue", "view", "999999",
		"--json-errors",
	)

	if result.ExitCode == 0 {
		t.Fatalf("expected non-zero exit code for non-existent issue, got 0")
	}

	// Stderr may contain cobra usage text mixed with JSON error output.
	stderr := result.Stderr
	jsonStart := strings.Index(stderr, "{")
	jsonEnd := strings.LastIndex(stderr, "}")
	if jsonStart < 0 || jsonEnd < 0 || jsonEnd <= jsonStart {
		t.Fatalf("expected JSON error output in stderr, but no JSON found.\nStderr: %s", stderr)
	}
	jsonStr := stderr[jsonStart : jsonEnd+1]

	var errData map[string]interface{}
	if err := json.Unmarshal([]byte(jsonStr), &errData); err != nil {
		t.Fatalf("expected JSON error output in stderr, got parse error: %v\nJSON: %s\nFull stderr: %s", err, jsonStr, stderr)
	}

	if _, hasCode := errData["code"]; !hasCode {
		if _, hasMsg := errData["message"]; !hasMsg {
			if _, hasErr := errData["error"]; !hasErr {
				t.Fatalf("JSON error output missing expected fields (code/message/error): %v", errData)
			}
		}
	}

	t.Logf("Successfully verified --json-errors flag produces structured JSON error output")
}
