package cmd

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"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 milestoneCmd = &cobra.Command{
	Use:   "milestone",
	Short: "Manage milestones",
	Long:  "Create, view, list, edit, and delete milestones.",
}

var milestoneListCmd = &cobra.Command{
	Use:   "list [flags]",
	Short: "List milestones",
	Long:  "List milestones in a repository.",
	Example: `  # List open milestones
  fj milestone list

  # List all milestones for a specific repo
  fj milestone list -R owner/repo --state all

  # Output as JSON
  fj milestone list --json`,
	RunE: runMilestoneList,
}

var milestoneViewCmd = &cobra.Command{
	Use:   "view <title-or-id>",
	Short: "View a milestone",
	Long:  "Display detailed information about a milestone.",
	Example: `  # View by ID
  fj milestone view 1

  # View by title
  fj milestone view "v1.0"

  # Open in browser
  fj milestone view "v1.0" --web

  # Output as JSON
  fj milestone view "v1.0" --json`,
	Args: cobra.ExactArgs(1),
	RunE: runMilestoneView,
}

var milestoneCreateCmd = &cobra.Command{
	Use:   "create <title>",
	Short: "Create a milestone",
	Long:  "Create a new milestone.",
	Example: `  # Create a simple milestone
  fj milestone create "v1.0"

  # Create with description and due date
  fj milestone create "v2.0" -d "Second release" --due 2026-06-01

  # Output as JSON
  fj milestone create "v1.0" --json`,
	Args: cobra.ExactArgs(1),
	RunE: runMilestoneCreate,
}

var milestoneEditCmd = &cobra.Command{
	Use:   "edit <title-or-id>",
	Short: "Edit a milestone",
	Long:  "Edit an existing milestone's title, description, due date, or state.",
	Example: `  # Rename a milestone
  fj milestone edit "v1.0" --title "v1.1"

  # Close a milestone
  fj milestone edit "v1.0" --state closed

  # Update due date
  fj milestone edit 1 --due 2026-12-31`,
	Args: cobra.ExactArgs(1),
	RunE: runMilestoneEdit,
}

var milestoneDeleteCmd = &cobra.Command{
	Use:   "delete <title-or-id>",
	Short: "Delete a milestone",
	Long:  "Delete an existing milestone.",
	Example: `  # Delete by title
  fj milestone delete "v1.0"

  # Delete by ID
  fj milestone delete 1

  # Delete without confirmation
  fj milestone delete "v1.0" -y`,
	Args: cobra.ExactArgs(1),
	RunE: runMilestoneDelete,
}

func init() {
	rootCmd.AddCommand(milestoneCmd)
	milestoneCmd.AddCommand(milestoneListCmd)
	milestoneCmd.AddCommand(milestoneViewCmd)
	milestoneCmd.AddCommand(milestoneCreateCmd)
	milestoneCmd.AddCommand(milestoneEditCmd)
	milestoneCmd.AddCommand(milestoneDeleteCmd)

	milestoneListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	milestoneListCmd.Flags().String("state", "open", "Filter by state: open, closed, all")
	addJSONFlags(milestoneListCmd, "Output milestones as JSON")

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

	milestoneCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	milestoneCreateCmd.Flags().StringP("description", "d", "", "Description of the milestone")
	milestoneCreateCmd.Flags().String("due", "", "Due date in YYYY-MM-DD format")
	addJSONFlags(milestoneCreateCmd, "Output created milestone as JSON")

	milestoneEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	milestoneEditCmd.Flags().String("title", "", "New title for the milestone")
	milestoneEditCmd.Flags().StringP("description", "d", "", "New description for the milestone")
	milestoneEditCmd.Flags().String("due", "", "Due date in YYYY-MM-DD format")
	milestoneEditCmd.Flags().String("state", "", "New state: open or closed")
	addJSONFlags(milestoneEditCmd, "Output updated milestone as JSON")

	milestoneDeleteCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	milestoneDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
}

// resolveMilestone resolves a title-or-id argument to a milestone.
// If the argument is numeric, it fetches by ID. Otherwise, it lists
// milestones and finds a match by title.
func resolveMilestone(client *api.Client, owner, name, arg string) (*gitea.Milestone, error) {
	if id, err := strconv.ParseInt(arg, 10, 64); err == nil {
		ms, _, err := client.GetMilestone(owner, name, id)
		if err != nil {
			return nil, fmt.Errorf("failed to get milestone %d: %w", id, err)
		}
		return ms, nil
	}

	milestones, _, err := client.ListRepoMilestones(owner, name, gitea.ListMilestoneOption{
		State: gitea.StateAll,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to list milestones: %w", err)
	}

	for _, ms := range milestones {
		if strings.EqualFold(ms.Title, arg) {
			return ms, nil
		}
	}

	return nil, fmt.Errorf("milestone not found: %s", arg)
}

func parseDueDate(dateStr string) (*time.Time, error) {
	t, err := time.Parse("2006-01-02", dateStr)
	if err != nil {
		return nil, fmt.Errorf("invalid due date %q: expected YYYY-MM-DD format", dateStr)
	}
	return &t, nil
}

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

	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 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)
	}

	ios.StartSpinner("Fetching milestones...")
	milestones, _, err := client.ListRepoMilestones(owner, name, gitea.ListMilestoneOption{
		State: stateType,
	})
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to list milestones: %w", err)
	}

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

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

	tp := ios.NewTablePrinter()
	tp.AddHeader("ID", "TITLE", "STATE", "DUE DATE", "OPEN/CLOSED ISSUES")
	for _, ms := range milestones {
		due := ""
		if ms.Deadline != nil {
			due = ms.Deadline.Format("2006-01-02")
		}
		tp.AddRow(
			fmt.Sprintf("%d", ms.ID),
			ms.Title,
			string(ms.State),
			due,
			fmt.Sprintf("%d/%d", ms.OpenIssues, ms.ClosedIssues),
		)
	}
	return tp.Render()
}

func runMilestoneView(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
	}

	ios.StartSpinner("Fetching milestone...")
	ms, err := resolveMilestone(client, owner, name, args[0])
	ios.StopSpinner()
	if err != nil {
		return err
	}

	if web, _ := cmd.Flags().GetBool("web"); web {
		// Milestones don't have HTMLURL in the API, construct it
		cfg2, _ := config.Load()
		host, _ := cfg2.GetHost("", getDetectedHost(), getCwd())
		url := fmt.Sprintf("https://%s/%s/%s/milestone/%d", host.Hostname, owner, name, ms.ID)
		return ios.OpenInBrowser(url)
	}

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

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

	fmt.Fprintf(ios.Out, "ID: %d\n", ms.ID)
	fmt.Fprintf(ios.Out, "Title: %s\n", cs.Bold(ms.Title))
	fmt.Fprintf(ios.Out, "State: %s\n", ms.State)
	if ms.Description != "" {
		fmt.Fprintf(ios.Out, "Description: %s\n", ms.Description)
	}
	if ms.Deadline != nil {
		fmt.Fprintf(ios.Out, "Due Date: %s\n", ms.Deadline.Format("2006-01-02"))
	}
	fmt.Fprintf(ios.Out, "Open Issues: %d\n", ms.OpenIssues)
	fmt.Fprintf(ios.Out, "Closed Issues: %d\n", ms.ClosedIssues)
	fmt.Fprintf(ios.Out, "Created: %s\n", text.FormatDate(ms.Created, isTTY))
	if ms.Updated != nil {
		fmt.Fprintf(ios.Out, "Updated: %s\n", text.FormatDate(*ms.Updated, isTTY))
	}
	if ms.Closed != nil {
		fmt.Fprintf(ios.Out, "Closed: %s\n", text.FormatDate(*ms.Closed, isTTY))
	}

	return nil
}

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

	title := 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
	}

	opt := gitea.CreateMilestoneOption{
		Title:       title,
		Description: description,
	}

	if dueStr != "" {
		deadline, err := parseDueDate(dueStr)
		if err != nil {
			return err
		}
		opt.Deadline = deadline
	}

	ios.StartSpinner("Creating milestone...")
	ms, _, err := client.CreateMilestone(owner, name, opt)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to create milestone: %w", err)
	}

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

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Milestone created: %s\n", cs.SuccessIcon(), ms.Title)

	return nil
}

func runMilestoneEdit(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
	}

	ios.StartSpinner("Fetching milestone...")
	ms, err := resolveMilestone(client, owner, name, args[0])
	ios.StopSpinner()
	if err != nil {
		return err
	}

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

	if cmd.Flags().Changed("title") {
		t, _ := cmd.Flags().GetString("title")
		opt.Title = t
		changed = true
	}

	if cmd.Flags().Changed("description") {
		d, _ := cmd.Flags().GetString("description")
		opt.Description = &d
		changed = true
	}

	if cmd.Flags().Changed("due") {
		dueStr, _ := cmd.Flags().GetString("due")
		deadline, err := parseDueDate(dueStr)
		if err != nil {
			return err
		}
		opt.Deadline = deadline
		changed = true
	}

	if cmd.Flags().Changed("state") {
		stateStr, _ := cmd.Flags().GetString("state")
		switch strings.ToLower(stateStr) {
		case "open":
			s := gitea.StateOpen
			opt.State = &s
		case "closed":
			s := gitea.StateClosed
			opt.State = &s
		default:
			return fmt.Errorf("invalid state: %s (must be 'open' or 'closed')", stateStr)
		}
		changed = true
	}

	if !changed {
		return fmt.Errorf("no changes specified; use flags like --title, --description, --due, or --state")
	}

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

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

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Milestone updated: %s\n", cs.SuccessIcon(), updated.Title)

	return nil
}

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

	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 milestone...")
	ms, err := resolveMilestone(client, owner, name, args[0])
	ios.StopSpinner()
	if err != nil {
		return err
	}

	if !yes {
		confirmed, err := ios.ConfirmAction(fmt.Sprintf("Delete milestone %q?", ms.Title))
		if err != nil {
			return err
		}
		if !confirmed {
			fmt.Fprintln(ios.ErrOut, "Aborted")
			return nil
		}
	}

	ios.StartSpinner("Deleting milestone...")
	_, err = client.DeleteMilestone(owner, name, ms.ID)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to delete milestone: %w", err)
	}

	cs := ios.ColorScheme()
	fmt.Fprintf(ios.Out, "%s Milestone deleted: %s\n", cs.SuccessIcon(), ms.Title)

	return nil
}
