package cmd

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

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

var repoCmd = &cobra.Command{
	Use:   "repo",
	Short: "Manage repositories",
	Long:  "View and manage repositories.",
}

var repoViewCmd = &cobra.Command{
	Use:   "view [owner/name]",
	Short: "View repository details",
	Long:  "Display detailed information about a repository.",
	Args:  cobra.MaximumNArgs(1),
	RunE:  runRepoView,
}

var repoListCmd = &cobra.Command{
	Use:   "list",
	Short: "List your repositories",
	Long:  "List repositories owned by the authenticated user.",
	RunE:  runRepoList,
}

var repoCloneCmd = &cobra.Command{
	Use:   "clone <owner/name> [destination]",
	Short: "Clone a repository",
	Long:  "Clone a repository locally. If destination is not specified, the repository name is used.",
	Args:  cobra.RangeArgs(1, 2),
	RunE:  runRepoClone,
}

var repoForkCmd = &cobra.Command{
	Use:   "fork <owner/name>",
	Short: "Fork a repository",
	Long:  "Create a fork of a repository.",
	Args:  cobra.ExactArgs(1),
	RunE:  runRepoFork,
}

var repoCreateCmd = &cobra.Command{
	Use:   "create <name>",
	Short: "Create a new repository",
	Long: `Create a new repository on the Forgejo instance.

Name can be "reponame" (creates under your account) or "org/reponame"
(creates under an organization). Repositories are public by default; use --private to create a private one.`,
	Args: cobra.ExactArgs(1),
	RunE: runRepoCreate,
}

var repoEditCmd = &cobra.Command{
	Use:   "edit [owner/name]",
	Short: "Edit repository settings",
	Long:  "Edit settings of an existing repository such as visibility, description, homepage, and default branch.",
	Example: `  # Make a repository private
  fj repo edit owner/repo --private

  # Make a repository public
  fj repo edit owner/repo --public

  # Update description and homepage
  fj repo edit owner/repo -d "New description" --homepage https://example.com

  # Change default branch
  fj repo edit --default-branch develop

  # Rename a repository
  fj repo edit owner/repo --name new-name

  # Edit current repo (auto-detected from git context)
  fj repo edit --public`,
	Args: cobra.MaximumNArgs(1),
	RunE: runRepoEdit,
}

var repoRenameCmd = &cobra.Command{
	Use:   "rename <new-name>",
	Short: "Rename a repository",
	Long:  "Rename an existing repository. This is a shorthand for `fj repo edit --name <new-name>`.",
	Example: `  # Rename current repo
  fj repo rename new-name

  # Rename a specific repo
  fj repo rename new-name -R owner/old-name`,
	Args: cobra.ExactArgs(1),
	RunE: runRepoRename,
}

func init() {
	rootCmd.AddCommand(repoCmd)
	repoCmd.AddCommand(repoCloneCmd)
	repoCmd.AddCommand(repoCreateCmd)
	repoCmd.AddCommand(repoEditCmd)
	repoCmd.AddCommand(repoRenameCmd)
	repoCmd.AddCommand(repoForkCmd)
	repoCmd.AddCommand(repoListCmd)
	repoCmd.AddCommand(repoViewCmd)

	repoCreateCmd.Flags().StringP("description", "d", "", "Description of the repository")
	repoCreateCmd.Flags().Bool("private", false, "Make the new repository private")
	repoCreateCmd.Flags().Bool("public", false, "Make the new repository public")
	repoCreateCmd.Flags().Bool("add-readme", false, "Add a README file to the new repository")
	repoCreateCmd.Flags().StringP("gitignore", "g", "", "Gitignore template (e.g. Go, Python)")
	repoCreateCmd.Flags().StringP("license", "l", "", "License template (e.g. MIT, Apache-2.0)")
	repoCreateCmd.Flags().String("homepage", "", "Repository home page URL")
	repoCreateCmd.Flags().BoolP("clone", "c", false, "Clone the new repository to the current directory")
	repoCreateCmd.Flags().StringP("team", "t", "", "Team name to be granted access (org repos only)")
	repoCreateCmd.MarkFlagsMutuallyExclusive("public", "private")

	addJSONFlags(repoViewCmd, "Output repository as JSON")
	repoViewCmd.Flags().BoolP("web", "w", false, "Open in web browser")

	addJSONFlags(repoListCmd, "Output repositories as JSON")
	repoListCmd.Flags().IntP("limit", "L", 0, "Maximum number of repositories to list (0 = no limit)")

	repoCloneCmd.Flags().StringP("protocol", "p", "https", "Clone protocol: https or ssh")

	repoEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	repoEditCmd.Flags().String("name", "", "Rename the repository")
	repoEditCmd.Flags().StringP("description", "d", "", "Repository description")
	repoEditCmd.Flags().String("homepage", "", "Repository home page URL")
	repoEditCmd.Flags().String("default-branch", "", "Default branch name")
	repoEditCmd.Flags().Bool("private", false, "Make the repository private")
	repoEditCmd.Flags().Bool("public", false, "Make the repository public")
	addJSONFlags(repoEditCmd, "Output updated repository as JSON")
	repoEditCmd.MarkFlagsMutuallyExclusive("public", "private")

	repoRenameCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	addJSONFlags(repoRenameCmd, "Output updated repository as JSON")
}

func runRepoView(cmd *cobra.Command, args []string) error {
	var repo string
	if len(args) > 0 {
		repo = args[0]
	}

	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 repository...")
	repository, _, err := client.GetRepo(owner, name)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to get repository: %w", err)
	}

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

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

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

	fmt.Fprintf(ios.Out, "Repository: %s\n", cs.Bold(fmt.Sprintf("%s/%s", repository.Owner.UserName, repository.Name)))
	fmt.Fprintf(ios.Out, "Description: %s\n", repository.Description)
	fmt.Fprintf(ios.Out, "URL: %s\n", repository.HTMLURL)
	fmt.Fprintf(ios.Out, "Clone URL (HTTPS): %s\n", repository.CloneURL)
	fmt.Fprintf(ios.Out, "Clone URL (SSH): %s\n", repository.SSHURL)
	fmt.Fprintf(ios.Out, "Default Branch: %s\n", repository.DefaultBranch)
	fmt.Fprintf(ios.Out, "Stars: %d\n", repository.Stars)
	fmt.Fprintf(ios.Out, "Forks: %d\n", repository.Forks)
	fmt.Fprintf(ios.Out, "Open Issues: %d\n", repository.OpenIssues)
	fmt.Fprintf(ios.Out, "Private: %v\n", repository.Private)
	fmt.Fprintf(ios.Out, "Created: %s\n", text.FormatDate(repository.Created, isTTY))
	fmt.Fprintf(ios.Out, "Updated: %s\n", text.FormatDate(repository.Updated, isTTY))

	return nil
}

func runRepoList(cmd *cobra.Command, args []string) error {
	cfg, err := config.Load()
	if err != nil {
		return err
	}

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

	ios.StartSpinner("Fetching repositories...")
	user, _, err := client.GetMyUserInfo()
	if err != nil {
		ios.StopSpinner()
		return fmt.Errorf("failed to get user info: %w", err)
	}

	repos, _, err := client.ListUserRepos(user.UserName, gitea.ListReposOptions{})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to list repositories: %w", err)
	}

	limit, _ := cmd.Flags().GetInt("limit")
	if limit > 0 && len(repos) > limit {
		repos = repos[:limit]
	}

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

	if len(repos) == 0 {
		fmt.Fprintln(ios.Out, "No repositories found")
		return nil
	}

	tp := ios.NewTablePrinter()
	tp.AddHeader("NAME", "VISIBILITY", "DESCRIPTION")
	for _, repo := range repos {
		visibility := "public"
		if repo.Private {
			visibility = "private"
		}
		desc := text.Truncate(repo.Description, 50)
		tp.AddRow(fmt.Sprintf("%s/%s", repo.Owner.UserName, repo.Name), visibility, desc)
	}
	return tp.Render()
}

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

	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 repository info...")
	repository, _, err := client.GetRepo(owner, name)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to get repository: %w", err)
	}

	var cloneURL string
	if protocol == "ssh" {
		cloneURL = repository.SSHURL
	} else {
		cloneURL = repository.CloneURL
	}

	// Determine destination path
	var destination string
	if len(args) > 1 {
		destination = args[1]
	} else {
		destination = name
	}

	fmt.Fprintf(ios.Out, "Cloning %s/%s to %s...\n", owner, name, destination)

	// Create parent directory if it doesn't exist
	if dir := filepath.Dir(destination); dir != "." {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("failed to create destination directory: %w", err)
		}
	}

	ios.StartSpinner("Cloning repository...")
	// Execute git clone
	gitCmd := exec.Command("git", "clone", cloneURL, destination)
	gitCmd.Stdout = ios.Out
	gitCmd.Stderr = ios.ErrOut
	gitCmd.Stdin = ios.In

	if err := gitCmd.Run(); err != nil {
		ios.StopSpinner()
		return fmt.Errorf("failed to clone repository: %w", err)
	}
	ios.StopSpinner()

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Repository cloned successfully to %s\n", cs.SuccessIcon(), destination)
	return nil
}

func runRepoFork(cmd *cobra.Command, args []string) error {
	repo := args[0]

	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("Forking repository...")
	fork, _, err := client.CreateFork(owner, name, gitea.CreateForkOption{})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to fork repository: %w", err)
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Repository forked successfully\n", cs.SuccessIcon())
	fmt.Fprintf(ios.Out, "View at: %s\n", fork.HTMLURL)
	fmt.Fprintf(ios.Out, "Clone URL: %s\n", fork.CloneURL)

	return nil
}

func runRepoCreate(cmd *cobra.Command, args []string) error {
	inputName := args[0]
	org, repoName, isOrg, err := parseCreateName(inputName)
	if err != nil {
		return err
	}

	private, _ := cmd.Flags().GetBool("private")
	public, _ := cmd.Flags().GetBool("public")
	description, _ := cmd.Flags().GetString("description")
	addReadme, _ := cmd.Flags().GetBool("add-readme")
	gitignore, _ := cmd.Flags().GetString("gitignore")
	license, _ := cmd.Flags().GetString("license")
	homepage, _ := cmd.Flags().GetString("homepage")
	doClone, _ := cmd.Flags().GetBool("clone")
	team, _ := cmd.Flags().GetString("team")

	// --public explicitly sets private=false (default behavior, but makes intent clear)
	if public {
		private = false
	}

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

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

	opt := gitea.CreateRepoOption{
		Name:        repoName,
		Description: description,
		Private:     private,
		AutoInit:    addReadme,
		Gitignores:  gitignore,
		License:     license,
	}

	ios.StartSpinner("Creating repository...")
	var repo *gitea.Repository
	if isOrg {
		repo, _, err = client.CreateOrgRepo(org, opt)
	} else {
		repo, _, err = client.CreateRepo(opt)
	}
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to create repository: %w", err)
	}

	if homepage != "" {
		ownerName := org
		if !isOrg {
			// For personal repos, get the owner from the created repo or fall back to API
			if repo.Owner != nil {
				ownerName = repo.Owner.UserName
			} else {
				user, _, userErr := client.GetMyUserInfo()
				if userErr != nil {
					fmt.Fprintf(ios.ErrOut, "warning: repository created but could not determine owner for homepage: %v\n", userErr)
					homepage = "" // skip EditRepo
				} else {
					ownerName = user.UserName
				}
			}
		}
		if homepage != "" {
			_, _, err = client.EditRepo(ownerName, repo.Name, gitea.EditRepoOption{
				Website: &homepage,
			})
			if err != nil {
				fmt.Fprintf(ios.ErrOut, "warning: repository created but failed to set homepage: %v\n", err)
			}
		}
	}

	if team != "" {
		if !isOrg {
			fmt.Fprintln(ios.ErrOut, "warning: --team is only meaningful for organization repositories")
		} else {
			_, err = client.AddRepoTeam(org, repo.Name, team)
			if err != nil {
				fmt.Fprintf(ios.ErrOut, "warning: repository created but failed to add team %q: %v\n", team, err)
			}
		}
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Repository created: %s\n", cs.SuccessIcon(), repo.HTMLURL)

	if doClone {
		cloneURL := repo.CloneURL
		if hostCfg, hostErr := cfg.GetHost("", getDetectedHost(), getCwd()); hostErr == nil {
			if hostCfg.GitProtocol == "ssh" {
				cloneURL = repo.SSHURL
			}
		}
		fmt.Fprintf(ios.Out, "Cloning into %s...\n", repo.Name)
		gitCmd := exec.Command("git", "clone", cloneURL, repo.Name)
		gitCmd.Stdout = ios.Out
		gitCmd.Stderr = ios.ErrOut
		gitCmd.Stdin = ios.In
		if err := gitCmd.Run(); err != nil {
			return fmt.Errorf("failed to clone repository: %w", err)
		}
	}

	return nil
}

// parseCreateName parses the name argument for repo creation.
// Returns org, repoName, and whether it targets an org.
// Input: "reponame" → ("", "reponame", false)
// Input: "org/reponame" → ("org", "reponame", true)
func parseCreateName(name string) (org, repoName string, isOrg bool, err error) {
	parts := strings.SplitN(name, "/", 2)
	if len(parts) == 2 {
		if parts[0] == "" || parts[1] == "" {
			return "", "", false, fmt.Errorf("invalid repository name %q: org and repo name must not be empty", name)
		}
		return parts[0], parts[1], true, nil
	}
	if name == "" {
		return "", "", false, fmt.Errorf("repository name must not be empty")
	}
	return "", name, false, nil
}

func runRepoEdit(cmd *cobra.Command, args []string) error {
	var repo string
	if len(args) > 0 {
		repo = args[0]
	}
	if r, _ := cmd.Flags().GetString("repo"); r != "" {
		repo = r
	}

	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
	}

	opt := gitea.EditRepoOption{}
	changed := false

	if cmd.Flags().Changed("name") {
		n, _ := cmd.Flags().GetString("name")
		opt.Name = &n
		changed = true
	}
	if cmd.Flags().Changed("description") {
		d, _ := cmd.Flags().GetString("description")
		opt.Description = &d
		changed = true
	}
	if cmd.Flags().Changed("homepage") {
		h, _ := cmd.Flags().GetString("homepage")
		opt.Website = &h
		changed = true
	}
	if cmd.Flags().Changed("default-branch") {
		b, _ := cmd.Flags().GetString("default-branch")
		opt.DefaultBranch = &b
		changed = true
	}
	if cmd.Flags().Changed("private") {
		p := true
		opt.Private = &p
		changed = true
	}
	if cmd.Flags().Changed("public") {
		p := false
		opt.Private = &p
		changed = true
	}

	if !changed {
		return fmt.Errorf("no changes specified; use flags like --name, --public, --private, --description, --homepage, or --default-branch")
	}

	ios.StartSpinner("Updating repository...")
	repository, _, err := client.EditRepo(owner, name, opt)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to edit repository: %w", err)
	}

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

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Repository updated: %s\n", cs.SuccessIcon(), repository.HTMLURL)
	if opt.Name != nil {
		fmt.Fprintf(ios.Out, "Renamed to: %s\n", repository.FullName)
	}
	if opt.Private != nil {
		if *opt.Private {
			fmt.Fprintln(ios.Out, "Visibility: private")
		} else {
			fmt.Fprintln(ios.Out, "Visibility: public")
		}
	}
	if opt.Description != nil {
		fmt.Fprintf(ios.Out, "Description: %s\n", *opt.Description)
	}
	if opt.Website != nil {
		fmt.Fprintf(ios.Out, "Homepage: %s\n", *opt.Website)
	}
	if opt.DefaultBranch != nil {
		fmt.Fprintf(ios.Out, "Default branch: %s\n", *opt.DefaultBranch)
	}

	return nil
}

func runRepoRename(cmd *cobra.Command, args []string) error {
	var repo string
	if r, _ := cmd.Flags().GetString("repo"); r != "" {
		repo = r
	}

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

	newName := args[0]

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

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

	opt := gitea.EditRepoOption{
		Name: &newName,
	}

	ios.StartSpinner("Renaming repository...")
	repository, _, err := client.EditRepo(owner, name, opt)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to rename repository: %w", err)
	}

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

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Renamed %s/%s to %s\n", cs.SuccessIcon(), owner, name, repository.FullName)
	return nil
}
