package cmd

import (
	"fmt"
	"net/http"
	"os/exec"
	"strings"

	"code.gitea.io/sdk/gitea"
	"forgejo.zerova.net/public/fj/internal/api"
	"forgejo.zerova.net/public/fj/internal/config"
	gitpkg "forgejo.zerova.net/public/fj/internal/git"
	"forgejo.zerova.net/public/fj/internal/text"
	"github.com/spf13/cobra"
)

var prCmd = &cobra.Command{
	Use:     "pr",
	Aliases: []string{"pull-request"},
	Short:   "Manage pull requests",
	Long:    "Create, view, list, and manage pull requests.",
}

var prListCmd = &cobra.Command{
	Use:   "list [flags]",
	Short: "List pull requests",
	Long:  "List pull requests in a repository.",
	Example: `  # List open pull requests
  fj pr list

  # List all pull requests for a specific repo
  fj pr list -s all -R owner/repo

  # Output as JSON
  fj pr list --json`,
	RunE: runPRList,
}

var prViewCmd = &cobra.Command{
	Use:   "view [<number>]",
	Short: "View a pull request",
	Long:  "Display detailed information about a pull request.",
	Example: `  # View pull request #5
  fj pr view 5

  # View using URL
  fj pr view https://codeberg.org/owner/repo/pulls/5

  # View PR for current branch
  fj pr view

  # Open in browser
  fj pr view 5 --web

  # View as JSON
  fj pr view 5 --json`,
	Args: cobra.MaximumNArgs(1),
	RunE: runPRView,
}

var prCreateCmd = &cobra.Command{
	Use:   "create",
	Short: "Create a pull request",
	Long:  "Create a new pull request.",
	Example: `  # Create a pull request from feature branch to main
  fj pr create -t "Add login page" -H feature/login

  # Create with body and custom base branch
  fj pr create -t "Fix bug" -b "Closes #42" -H fix/bug -B develop

  # Create and self-assign
  fj pr create -t "Update docs" -H docs/update -a @me`,
	RunE: runPRCreate,
}

var prMergeCmd = &cobra.Command{
	Use:   "merge <number>",
	Short: "Merge a pull request",
	Long:  "Merge a pull request.",
	Example: `  # Merge pull request #5
  fj pr merge 5

  # Squash merge
  fj pr merge 5 --merge-method squash

  # Rebase merge
  fj pr merge 5 --merge-method rebase

  # Merge without confirmation
  fj pr merge 5 -y`,
	Args: cobra.ExactArgs(1),
	RunE: runPRMerge,
}

var prCloseCmd = &cobra.Command{
	Use:   "close <number>",
	Short: "Close a pull request",
	Long:  "Close a pull request without merging.",
	Example: `  # Close PR #5
  fj pr close 5

  # Close with a comment
  fj pr close 5 -c "Won't merge, superseded by #10"`,
	Args: cobra.ExactArgs(1),
	RunE: runPRClose,
}

var prReopenCmd = &cobra.Command{
	Use:   "reopen <number>",
	Short: "Reopen a pull request",
	Long:  "Reopen a closed pull request.",
	Example: `  # Reopen PR #5
  fj pr reopen 5`,
	Args: cobra.ExactArgs(1),
	RunE: runPRReopen,
}

var prEditCmd = &cobra.Command{
	Use:   "edit <number>",
	Short: "Edit a pull request",
	Long:  "Edit a pull request's title, body, or metadata.",
	Example: `  # Update the title of PR #5
  fj pr edit 5 -t "Updated title"

  # Add assignees and labels
  fj pr edit 5 --add-assignee user1 --add-label bug

  # Remove a reviewer and set milestone
  fj pr edit 5 --remove-reviewer user2 --milestone "v1.0"`,
	Args: cobra.ExactArgs(1),
	RunE: runPREdit,
}

var prCheckoutCmd = &cobra.Command{
	Use:   "checkout <number>",
	Short: "Check out a pull request locally",
	Long:  "Check out the head branch of a pull request.",
	Example: `  # Check out PR #5
  fj pr checkout 5`,
	Args: cobra.ExactArgs(1),
	RunE: runPRCheckout,
}

func init() {
	rootCmd.AddCommand(prCmd)
	prCmd.AddCommand(prListCmd)
	prCmd.AddCommand(prViewCmd)
	prCmd.AddCommand(prCreateCmd)
	prCmd.AddCommand(prMergeCmd)
	prCmd.AddCommand(prCloseCmd)
	prCmd.AddCommand(prReopenCmd)
	prCmd.AddCommand(prEditCmd)
	prCmd.AddCommand(prCheckoutCmd)

	prCloseCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	prCloseCmd.Flags().StringP("comment", "c", "", "Comment body to add before closing")

	prReopenCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")

	prListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	prListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
	prListCmd.Flags().IntP("limit", "L", 30, "Maximum number of results")
	prListCmd.Flags().StringP("assignee", "a", "", "Filter by assignee username")
	prListCmd.Flags().String("author", "", "Filter by author username")
	prListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label names")
	prListCmd.Flags().StringP("search", "S", "", "Search keyword filter")
	prListCmd.Flags().Bool("draft", false, "Filter by draft status")
	prListCmd.Flags().String("head", "", "Filter by head branch")
	prListCmd.Flags().String("base", "", "Filter by base branch")
	prListCmd.Flags().BoolP("web", "w", false, "Open list in web browser")
	addJSONFlags(prListCmd, "Output pull requests as JSON")

	prViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	addJSONFlags(prViewCmd, "Output pull request as JSON")
	prViewCmd.Flags().BoolP("web", "w", false, "Open in web browser")

	prCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	prCreateCmd.Flags().StringP("title", "t", "", "Title for the pull request")
	prCreateCmd.Flags().StringP("body", "b", "", "Body for the pull request")
	prCreateCmd.Flags().StringP("head", "H", "", "Head branch")
	prCreateCmd.Flags().StringP("base", "B", "", "Base branch (default: main)")
	prCreateCmd.Flags().StringSliceP("assignee", "a", []string{}, "Assign people by their login. Use \"@me\" to self-assign.")
	prCreateCmd.Flags().BoolP("draft", "d", false, "Create as draft pull request")
	prCreateCmd.Flags().StringSliceP("reviewer", "r", nil, "Request reviewers by username")
	prCreateCmd.Flags().StringSliceP("label", "l", nil, "Add labels by name")
	prCreateCmd.Flags().StringP("milestone", "m", "", "Set milestone by name")

	prMergeCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	prMergeCmd.Flags().String("merge-method", "merge", "Merge method: merge, rebase, squash")
	prMergeCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
	prMergeCmd.Flags().BoolP("delete-branch", "d", false, "Delete the branch after merge")
	prMergeCmd.Flags().Bool("auto", false, "Merge automatically when checks succeed")

	prCheckoutCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")

	prEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	prEditCmd.Flags().StringP("title", "t", "", "New title for the pull request")
	prEditCmd.Flags().StringP("body", "b", "", "New body for the pull request")
	prEditCmd.Flags().StringP("base", "B", "", "New base branch for the pull request")
	prEditCmd.Flags().StringSlice("add-assignee", nil, "Assignees to add (login names)")
	prEditCmd.Flags().StringSlice("remove-assignee", nil, "Assignees to remove (login names)")
	prEditCmd.Flags().StringSlice("add-label", nil, "Labels to add (by name)")
	prEditCmd.Flags().StringSlice("remove-label", nil, "Labels to remove (by name)")
	prEditCmd.Flags().StringSlice("add-reviewer", nil, "Reviewers to add (login names)")
	prEditCmd.Flags().StringSlice("remove-reviewer", nil, "Reviewers to remove (login names)")
	prEditCmd.Flags().String("milestone", "", "Milestone name to set (empty string to clear)")
}

func runPRList(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	state, _ := cmd.Flags().GetString("state")
	limit, _ := cmd.Flags().GetInt("limit")
	assignee, _ := cmd.Flags().GetString("assignee")
	author, _ := cmd.Flags().GetString("author")
	labels, _ := cmd.Flags().GetStringSlice("label")
	search, _ := cmd.Flags().GetString("search")
	draft, _ := cmd.Flags().GetBool("draft")
	head, _ := cmd.Flags().GetString("head")
	base, _ := cmd.Flags().GetString("base")

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	if web, _ := cmd.Flags().GetBool("web"); web {
		return ios.OpenInBrowser(fmt.Sprintf("https://%s/%s/%s/pulls", client.Hostname(), owner, name))
	}

	var stateType gitea.StateType
	switch strings.ToLower(state) {
	case "open":
		stateType = gitea.StateOpen
	case "closed":
		stateType = gitea.StateClosed
	case "all":
		stateType = gitea.StateAll
	default:
		return fmt.Errorf("invalid state: %s", state)
	}

	needsClientFilter := assignee != "" || author != "" || len(labels) > 0 || search != "" || draft || head != "" || base != ""

	ios.StartSpinner("Fetching pull requests...")
	var prs []*gitea.PullRequest
	if needsClientFilter {
		page := 1
		for {
			batch, _, err := client.ListRepoPullRequests(owner, name, gitea.ListPullRequestsOptions{
				State:       stateType,
				ListOptions: gitea.ListOptions{Page: page, PageSize: 50},
			})
			if err != nil {
				ios.StopSpinner()
				return fmt.Errorf("failed to list pull requests: %w", err)
			}
			prs = append(prs, batch...)
			if len(batch) < 50 {
				break
			}
			page++
		}
		prs = filterPRs(prs, author, assignee, labels, search, draft, head, base)
		if len(prs) > limit {
			prs = prs[:limit]
		}
	} else {
		prs, _, err = client.ListRepoPullRequests(owner, name, gitea.ListPullRequestsOptions{
			State:       stateType,
			ListOptions: gitea.ListOptions{PageSize: limit},
		})
		if err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to list pull requests: %w", err)
		}
	}
	ios.StopSpinner()

	if wantJSON(cmd) {
		return outputJSON(cmd, prs)
	}

	if len(prs) == 0 {
		fmt.Fprintf(ios.Out, "No %s pull requests in %s/%s\n", state, owner, name)
		return nil
	}

	tp := ios.NewTablePrinter()
	tp.AddHeader("NUMBER", "TITLE", "BRANCH", "STATE")
	for _, pr := range prs {
		tp.AddRow(fmt.Sprintf("#%d", pr.Index), pr.Title, pr.Head.Ref, string(pr.State))
	}
	return tp.Render()
}

func filterPRs(prs []*gitea.PullRequest, author, assignee string, labels []string, search string, draft bool, head, base string) []*gitea.PullRequest {
	var result []*gitea.PullRequest
	for _, pr := range prs {
		if author != "" && !strings.EqualFold(pr.Poster.UserName, author) {
			continue
		}
		if assignee != "" {
			found := false
			for _, a := range pr.Assignees {
				if strings.EqualFold(a.UserName, assignee) {
					found = true
					break
				}
			}
			if !found {
				continue
			}
		}
		if len(labels) > 0 {
			prLabelNames := make(map[string]bool)
			for _, l := range pr.Labels {
				prLabelNames[strings.ToLower(l.Name)] = true
			}
			allFound := true
			for _, label := range labels {
				if !prLabelNames[strings.ToLower(label)] {
					allFound = false
					break
				}
			}
			if !allFound {
				continue
			}
		}
		if search != "" && !strings.Contains(strings.ToLower(pr.Title), strings.ToLower(search)) {
			continue
		}
		if draft && !pr.Draft {
			continue
		}
		if head != "" && !strings.EqualFold(pr.Head.Ref, head) {
			continue
		}
		if base != "" && !strings.EqualFold(pr.Base.Ref, base) {
			continue
		}
		result = append(result, pr)
	}
	return result
}

func runPRView(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	var prNumber int64
	if len(args) == 0 {
		// Try to find PR for current branch
		branch, branchErr := gitpkg.GetCurrentBranch()
		if branchErr != nil {
			return fmt.Errorf("no pull request number specified and could not detect current branch: %w", branchErr)
		}

		ios.StartSpinner("Finding pull request for branch...")
		prs, _, listErr := client.ListRepoPullRequests(owner, name, gitea.ListPullRequestsOptions{
			State: gitea.StateOpen,
		})
		ios.StopSpinner()
		if listErr != nil {
			return fmt.Errorf("failed to list pull requests: %w", listErr)
		}

		var found bool
		for _, pr := range prs {
			if pr.Head.Ref == branch {
				prNumber = pr.Index
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("no open pull request found for branch %q", branch)
		}
	} else {
		prNumber, err = parseIssueArg(args[0])
		if err != nil {
			return fmt.Errorf("invalid pull request number: %w", err)
		}
	}

	ios.StartSpinner("Fetching pull request...")
	pr, _, err := client.GetPullRequest(owner, name, prNumber)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to get pull request: %w", err)
	}

	if web, _ := cmd.Flags().GetBool("web"); web {
		return ios.OpenInBrowser(pr.HTMLURL)
	}

	if wantJSON(cmd) {
		return outputJSON(cmd, pr)
	}

	if err := ios.StartPager(); err != nil {
		fmt.Fprintf(ios.ErrOut, "warning: failed to start pager: %v\n", err)
	}
	defer ios.StopPager()

	cs := ios.ColorScheme()
	isTTY := ios.IsStdoutTTY()

	fmt.Fprintf(ios.Out, "%s Pull Request #%d\n", cs.Bold(""), pr.Index)
	fmt.Fprintf(ios.Out, "Title: %s\n", cs.Bold(pr.Title))
	fmt.Fprintf(ios.Out, "State: %s\n", pr.State)
	fmt.Fprintf(ios.Out, "Author: %s\n", pr.Poster.UserName)
	fmt.Fprintf(ios.Out, "Branch: %s -> %s\n", pr.Head.Ref, pr.Base.Ref)
	if pr.Created != nil {
		fmt.Fprintf(ios.Out, "Created: %s\n", text.FormatDate(*pr.Created, isTTY))
	}
	if pr.Updated != nil {
		fmt.Fprintf(ios.Out, "Updated: %s\n", text.FormatDate(*pr.Updated, isTTY))
	}
	if pr.Body != "" {
		fmt.Fprintf(ios.Out, "\n%s\n", pr.Body)
	}

	return nil
}

func runPRCreate(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	title, _ := cmd.Flags().GetString("title")
	body, _ := cmd.Flags().GetString("body")
	head, _ := cmd.Flags().GetString("head")
	base, _ := cmd.Flags().GetString("base")
	assignees, _ := cmd.Flags().GetStringSlice("assignee")
	draft, _ := cmd.Flags().GetBool("draft")
	reviewers, _ := cmd.Flags().GetStringSlice("reviewer")
	labelNames, _ := cmd.Flags().GetStringSlice("label")
	milestoneName, _ := cmd.Flags().GetString("milestone")

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	// Interactive mode: prompt for missing fields when TTY
	if title == "" && ios.IsStdinTTY() {
		title, err = promptLine("Title: ")
		if err != nil {
			return err
		}
		if title == "" {
			return fmt.Errorf("title is required")
		}
	} else if title == "" {
		return fmt.Errorf("title is required (use -t flag)")
	}

	if head == "" && ios.IsStdinTTY() {
		// Default to current branch
		branch, branchErr := gitpkg.GetCurrentBranch()
		if branchErr == nil {
			head = branch
			fmt.Fprintf(ios.ErrOut, "Using current branch %q as head\n", head)
		} else {
			head, err = promptLine("Head branch: ")
			if err != nil {
				return err
			}
		}
	}
	if head == "" {
		return fmt.Errorf("head branch is required (use -H flag)")
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	if base == "" {
		ios.StartSpinner("Fetching repository info...")
		repoInfo, _, err := client.GetRepo(owner, name)
		ios.StopSpinner()
		if err != nil {
			return fmt.Errorf("failed to get repository info: %w", err)
		}
		base = repoInfo.DefaultBranch
	}

	// Resolve @me in assignees
	resolvedAssignees := make([]string, 0, len(assignees))
	for _, assignee := range assignees {
		if assignee == "@me" {
			user, _, err := client.GetMyUserInfo()
			if err != nil {
				return fmt.Errorf("failed to get current user info: %w", err)
			}
			resolvedAssignees = append(resolvedAssignees, user.UserName)
		} else {
			resolvedAssignees = append(resolvedAssignees, assignee)
		}
	}

	// Resolve label names to IDs
	var labelIDs []int64
	if len(labelNames) > 0 {
		labelIDs, err = resolveLabelIDs(client, owner, name, labelNames)
		if err != nil {
			return err
		}
	}

	// Resolve milestone name to ID
	var milestoneID int64
	if milestoneName != "" {
		milestones, _, msErr := client.ListRepoMilestones(owner, name, gitea.ListMilestoneOption{})
		if msErr != nil {
			return fmt.Errorf("failed to list milestones: %w", msErr)
		}
		found := false
		for _, ms := range milestones {
			if ms.Title == milestoneName {
				milestoneID = ms.ID
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("milestone not found: %s", milestoneName)
		}
	}

	ios.StartSpinner("Creating pull request...")
	pr, _, err := client.CreatePullRequest(owner, name, gitea.CreatePullRequestOption{
		Title:     title,
		Body:      body,
		Head:      head,
		Base:      base,
		Assignees: resolvedAssignees,
		Reviewers: reviewers,
		Labels:    labelIDs,
		Milestone: milestoneID,
	})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to create pull request: %w", err)
	}

	// Set draft status via raw API if needed
	if draft {
		_, draftErr := client.DoJSON("PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner, name, pr.Index), map[string]any{"draft": true}, nil)
		if draftErr != nil {
			return fmt.Errorf("failed to set pull request as draft: %w", draftErr)
		}
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Pull request created: #%d\n", cs.SuccessIcon(), pr.Index)
	fmt.Fprintf(ios.Out, "View at: %s\n", pr.HTMLURL)

	return nil
}

func runPRMerge(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	mergeMethod, _ := cmd.Flags().GetString("merge-method")
	yes, _ := cmd.Flags().GetBool("yes")
	deleteBranch, _ := cmd.Flags().GetBool("delete-branch")
	autoMerge, _ := cmd.Flags().GetBool("auto")

	prNumber, err := parseIssueArg(args[0])
	if err != nil {
		return fmt.Errorf("invalid pull request number: %w", err)
	}

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	var method gitea.MergeStyle
	switch strings.ToLower(mergeMethod) {
	case "merge":
		method = gitea.MergeStyleMerge
	case "rebase":
		method = gitea.MergeStyleRebase
	case "squash":
		method = gitea.MergeStyleSquash
	default:
		return fmt.Errorf("invalid merge method: %s", mergeMethod)
	}

	if !yes {
		confirmed, err := ios.ConfirmAction(fmt.Sprintf("Merge pull request #%d via %s?", prNumber, mergeMethod))
		if err != nil {
			return err
		}
		if !confirmed {
			fmt.Fprintln(ios.ErrOut, "Aborted")
			return nil
		}
	}

	ios.StartSpinner("Merging pull request...")
	_, _, err = client.MergePullRequest(owner, name, prNumber, gitea.MergePullRequestOption{
		Style:                  method,
		DeleteBranchAfterMerge: deleteBranch,
		MergeWhenChecksSucceed: autoMerge,
	})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to merge pull request: %w", err)
	}

	cs := ios.ColorScheme()
	if autoMerge {
		fmt.Fprintf(ios.Out, "%s Auto-merge enabled for PR #%d\n", cs.SuccessIcon(), prNumber)
	} else {
		fmt.Fprintf(ios.Out, "%s Pull request #%d merged successfully\n", cs.SuccessIcon(), prNumber)
	}

	return nil
}

func runPRClose(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	commentBody, _ := cmd.Flags().GetString("comment")
	prNumber, err := parseIssueArg(args[0])
	if err != nil {
		return fmt.Errorf("invalid pull request number: %w", err)
	}

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	if commentBody != "" {
		ios.StartSpinner("Adding comment...")
		_, _, err = client.CreateIssueComment(owner, name, prNumber, gitea.CreateIssueCommentOption{
			Body: commentBody,
		})
		ios.StopSpinner()
		if err != nil {
			return fmt.Errorf("failed to create comment: %w", err)
		}
	}

	ios.StartSpinner("Closing pull request...")
	stateClosed := gitea.StateClosed
	_, _, err = client.EditPullRequest(owner, name, prNumber, gitea.EditPullRequestOption{
		State: &stateClosed,
	})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to close pull request: %w", err)
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Pull request #%d closed\n", cs.SuccessIcon(), prNumber)

	return nil
}

func runPRReopen(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	prNumber, err := parseIssueArg(args[0])
	if err != nil {
		return fmt.Errorf("invalid pull request number: %w", err)
	}

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	ios.StartSpinner("Reopening pull request...")
	stateOpen := gitea.StateOpen
	_, _, err = client.EditPullRequest(owner, name, prNumber, gitea.EditPullRequestOption{
		State: &stateOpen,
	})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to reopen pull request: %w", err)
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Pull request #%d reopened\n", cs.SuccessIcon(), prNumber)

	return nil
}

func runPRCheckout(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")

	prNumber, err := parseIssueArg(args[0])
	if err != nil {
		return fmt.Errorf("invalid pull request number: %w", err)
	}

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	ios.StartSpinner("Fetching pull request...")
	pr, _, err := client.GetPullRequest(owner, name, prNumber)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to get pull request: %w", err)
	}

	headBranch := pr.Head.Ref
	headRepo := pr.Head.Repository

	// Determine if same-repo or cross-repo PR
	isSameRepo := headRepo == nil || headRepo.FullName == fmt.Sprintf("%s/%s", owner, name)

	if isSameRepo {
		// Same repo: fetch and checkout
		ios.StartSpinner("Checking out branch...")

		gitFetch := exec.Command("git", "fetch", "origin", headBranch)
		gitFetch.Stdout = ios.Out
		gitFetch.Stderr = ios.ErrOut
		if err := gitFetch.Run(); err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to fetch branch: %w", err)
		}

		// Try to checkout existing branch first
		gitCheckout := exec.Command("git", "checkout", headBranch)
		gitCheckout.Stdout = ios.Out
		gitCheckout.Stderr = ios.ErrOut
		if err := gitCheckout.Run(); err != nil {
			// Branch doesn't exist locally, create it tracking remote
			gitCheckout = exec.Command("git", "checkout", "-b", headBranch, "origin/"+headBranch)
			gitCheckout.Stdout = ios.Out
			gitCheckout.Stderr = ios.ErrOut
			if err := gitCheckout.Run(); err != nil {
				ios.StopSpinner()
				return fmt.Errorf("failed to checkout branch: %w", err)
			}
		} else {
			// Branch existed, pull latest
			gitPull := exec.Command("git", "pull")
			gitPull.Stdout = ios.Out
			gitPull.Stderr = ios.ErrOut
			_ = gitPull.Run()
		}
		ios.StopSpinner()
	} else {
		// Cross-repo (fork): add remote and checkout
		forkOwner := headRepo.Owner.UserName
		forkCloneURL := headRepo.CloneURL

		ios.StartSpinner("Checking out branch from fork...")

		// Add fork as remote (ignore error if already exists)
		gitRemoteAdd := exec.Command("git", "remote", "add", forkOwner, forkCloneURL)
		gitRemoteAdd.Stdout = ios.Out
		gitRemoteAdd.Stderr = ios.ErrOut
		_ = gitRemoteAdd.Run() // ignore error if remote already exists

		// Fetch from fork
		gitFetch := exec.Command("git", "fetch", forkOwner)
		gitFetch.Stdout = ios.Out
		gitFetch.Stderr = ios.ErrOut
		if err := gitFetch.Run(); err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to fetch from fork: %w", err)
		}

		// Checkout the branch
		gitCheckout := exec.Command("git", "checkout", "-b", headBranch, forkOwner+"/"+headBranch)
		gitCheckout.Stdout = ios.Out
		gitCheckout.Stderr = ios.ErrOut
		if err := gitCheckout.Run(); err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to checkout branch: %w", err)
		}
		ios.StopSpinner()
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Checked out PR #%d on branch %q\n", cs.SuccessIcon(), prNumber, headBranch)
	return nil
}

func runPREdit(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	prNumber, err := parseIssueArg(args[0])
	if err != nil {
		return fmt.Errorf("invalid pull request number: %w", err)
	}

	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}

	// Check that at least one flag was provided
	anyChanged := false
	for _, flag := range []string{"title", "body", "base", "add-assignee", "remove-assignee",
		"add-label", "remove-label", "add-reviewer", "remove-reviewer", "milestone"} {
		if cmd.Flags().Changed(flag) {
			anyChanged = true
			break
		}
	}
	if !anyChanged {
		return fmt.Errorf("at least one of --title, --body, --base, --add-assignee, --remove-assignee, " +
			"--add-label, --remove-label, --add-reviewer, --remove-reviewer, or --milestone must be provided")
	}

	cfg, err := config.Load()
	if err != nil {
		return err
	}

	client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
	if err != nil {
		return err
	}

	// Build EditPullRequestOption from changed flags
	editOpt := gitea.EditPullRequestOption{}
	needsEditCall := false

	if cmd.Flags().Changed("title") {
		title, _ := cmd.Flags().GetString("title")
		editOpt.Title = title
		needsEditCall = true
	}

	if cmd.Flags().Changed("body") {
		body, _ := cmd.Flags().GetString("body")
		editOpt.Body = &body
		needsEditCall = true
	}

	if cmd.Flags().Changed("base") {
		base, _ := cmd.Flags().GetString("base")
		editOpt.Base = base
		needsEditCall = true
	}

	// Handle milestone
	if cmd.Flags().Changed("milestone") {
		milestoneName, _ := cmd.Flags().GetString("milestone")
		if milestoneName == "" {
			// Clear milestone by setting to 0
			editOpt.Milestone = 0
		} else {
			milestones, _, msErr := client.ListRepoMilestones(owner, name, gitea.ListMilestoneOption{})
			if msErr != nil {
				return fmt.Errorf("failed to list milestones: %w", msErr)
			}
			var milestoneID int64
			for _, ms := range milestones {
				if ms.Title == milestoneName {
					milestoneID = ms.ID
					break
				}
			}
			if milestoneID == 0 {
				return fmt.Errorf("milestone not found: %s", milestoneName)
			}
			editOpt.Milestone = milestoneID
		}
		needsEditCall = true
	}

	// Handle assignees (add/remove requires fetching current PR)
	addAssignees, _ := cmd.Flags().GetStringSlice("add-assignee")
	removeAssignees, _ := cmd.Flags().GetStringSlice("remove-assignee")

	if len(addAssignees) > 0 || len(removeAssignees) > 0 {
		ios.StartSpinner("Fetching pull request...")
		pr, _, prErr := client.GetPullRequest(owner, name, prNumber)
		ios.StopSpinner()
		if prErr != nil {
			return fmt.Errorf("failed to get pull request: %w", prErr)
		}

		// Build current assignee set
		assigneeSet := make(map[string]bool)
		for _, a := range pr.Assignees {
			assigneeSet[a.UserName] = true
		}

		// Add new assignees
		for _, a := range addAssignees {
			assigneeSet[a] = true
		}

		// Remove assignees
		for _, a := range removeAssignees {
			delete(assigneeSet, a)
		}

		// Convert back to slice
		newAssignees := make([]string, 0, len(assigneeSet))
		for a := range assigneeSet {
			newAssignees = append(newAssignees, a)
		}

		editOpt.Assignees = newAssignees
		needsEditCall = true
	}

	ios.StartSpinner("Updating pull request...")

	// Perform the edit API call if needed
	if needsEditCall {
		_, _, err = client.EditPullRequest(owner, name, prNumber, editOpt)
		if err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to edit pull request: %w", err)
		}
	}

	// Handle labels
	addLabelNames, _ := cmd.Flags().GetStringSlice("add-label")
	removeLabelNames, _ := cmd.Flags().GetStringSlice("remove-label")

	if len(addLabelNames) > 0 {
		labelIDs, labelErr := resolveLabelIDs(client, owner, name, addLabelNames)
		if labelErr != nil {
			ios.StopSpinner()
			return labelErr
		}
		_, _, err = client.AddIssueLabels(owner, name, prNumber, gitea.IssueLabelsOption{
			Labels: labelIDs,
		})
		if err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to add labels: %w", err)
		}
	}

	if len(removeLabelNames) > 0 {
		labelIDs, labelErr := resolveLabelIDs(client, owner, name, removeLabelNames)
		if labelErr != nil {
			ios.StopSpinner()
			return labelErr
		}
		for _, labelID := range labelIDs {
			_, err = client.DeleteIssueLabel(owner, name, prNumber, labelID)
			if err != nil {
				ios.StopSpinner()
				return fmt.Errorf("failed to remove label %d: %w", labelID, err)
			}
		}
	}

	// Handle reviewers
	addReviewers, _ := cmd.Flags().GetStringSlice("add-reviewer")
	removeReviewers, _ := cmd.Flags().GetStringSlice("remove-reviewer")

	if len(addReviewers) > 0 {
		reviewerReq := map[string][]string{
			"reviewers": addReviewers,
		}
		endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", owner, name, prNumber)
		_, err := client.DoJSON(http.MethodPost, endpoint, reviewerReq, nil)
		if err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to add reviewers: %w", err)
		}
	}

	if len(removeReviewers) > 0 {
		reviewerReq := map[string][]string{
			"reviewers": removeReviewers,
		}
		endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", owner, name, prNumber)
		_, err := client.DoJSON(http.MethodDelete, endpoint, reviewerReq, nil)
		if err != nil {
			ios.StopSpinner()
			return fmt.Errorf("failed to remove reviewers: %w", err)
		}
	}

	ios.StopSpinner()

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Pull request #%d updated\n", cs.SuccessIcon(), prNumber)

	return nil
}
