package cmd

import (
	"fmt"
	"strings"

	"forgejo.zerova.net/public/fj/internal/api"
	"forgejo.zerova.net/public/fj/internal/config"
	"github.com/spf13/cobra"
)

var prDiffCmd = &cobra.Command{
	Use:   "diff <number>",
	Short: "Show the diff for a pull request",
	Long:  "Fetch and display the diff for a pull request.",
	Example: `  # View the diff for PR #123
  fj pr diff 123

  # Colorized diff output
  fj pr diff 123 --color always

  # Show only changed file names
  fj pr diff 123 --name-only

  # Show diffstat summary
  fj pr diff 123 --stat`,
	Args: cobra.ExactArgs(1),
	RunE: runPRDiff,
}

func init() {
	prCmd.AddCommand(prDiffCmd)

	prDiffCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
	prDiffCmd.Flags().String("color", "auto", "Colorize the diff output: auto, always, never")
	prDiffCmd.Flags().Bool("name-only", false, "Show only changed file names")
	prDiffCmd.Flags().Bool("stat", false, "Show diffstat summary")
}

func runPRDiff(cmd *cobra.Command, args []string) error {
	repo, _ := cmd.Flags().GetString("repo")
	colorMode, _ := cmd.Flags().GetString("color")
	nameOnly, _ := cmd.Flags().GetBool("name-only")
	stat, _ := cmd.Flags().GetBool("stat")

	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
	}

	diffURL := fmt.Sprintf("https://%s/api/v1/repos/%s/%s/pulls/%d.diff",
		client.Hostname(), owner, name, prNumber)

	ios.StartSpinner("Fetching diff...")
	diff, err := client.GetRawLog(diffURL)
	ios.StopSpinner()
	if err != nil {
		return fmt.Errorf("failed to get pull request diff: %w", err)
	}

	if nameOnly {
		return printNameOnly(diff)
	}

	if stat {
		return printDiffStat(diff)
	}

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

	useColor := shouldColorize(colorMode)
	if useColor {
		return printColorizedDiff(diff)
	}

	fmt.Fprint(ios.Out, diff)
	return nil
}

// shouldColorize determines whether to colorize output based on the color flag.
func shouldColorize(mode string) bool {
	switch strings.ToLower(mode) {
	case "always":
		return true
	case "never":
		return false
	default: // "auto"
		return ios.ColorEnabled()
	}
}

// printNameOnly extracts and prints changed file names from the diff.
func printNameOnly(diff string) error {
	seen := make(map[string]bool)
	for _, line := range strings.Split(diff, "\n") {
		if strings.HasPrefix(line, "+++ b/") {
			name := strings.TrimPrefix(line, "+++ b/")
			if name != "" && !seen[name] {
				seen[name] = true
				fmt.Fprintln(ios.Out, name)
			}
		}
	}
	return nil
}

// fileStat holds per-file diff statistics.
type fileStat struct {
	name      string
	additions int
	deletions int
}

// printDiffStat parses the diff and prints a diffstat summary.
func printDiffStat(diff string) error {
	var stats []fileStat
	var current *fileStat
	inHeader := true

	for _, line := range strings.Split(diff, "\n") {
		if strings.HasPrefix(line, "diff --git ") {
			if current != nil {
				stats = append(stats, *current)
			}
			current = &fileStat{}
			inHeader = true
			continue
		}
		if current == nil {
			continue
		}
		if strings.HasPrefix(line, "+++ b/") {
			current.name = strings.TrimPrefix(line, "+++ b/")
			continue
		}
		if strings.HasPrefix(line, "@@") {
			inHeader = false
			continue
		}
		if inHeader {
			continue
		}
		if strings.HasPrefix(line, "+") {
			current.additions++
		} else if strings.HasPrefix(line, "-") {
			current.deletions++
		}
	}
	if current != nil && current.name != "" {
		stats = append(stats, *current)
	}

	if len(stats) == 0 {
		fmt.Fprintln(ios.Out, "0 files changed")
		return nil
	}

	cs := ios.ColorScheme()

	// Find the longest file name for alignment
	maxNameLen := 0
	maxChanges := 0
	for _, s := range stats {
		if len(s.name) > maxNameLen {
			maxNameLen = len(s.name)
		}
		total := s.additions + s.deletions
		if total > maxChanges {
			maxChanges = total
		}
	}

	// Cap the bar width
	barWidth := maxChanges
	if barWidth > 50 {
		barWidth = 50
	}

	totalAdditions := 0
	totalDeletions := 0

	for _, s := range stats {
		total := s.additions + s.deletions
		totalAdditions += s.additions
		totalDeletions += s.deletions

		var bar string
		if maxChanges > 0 {
			scaledAdd := s.additions
			scaledDel := s.deletions
			if maxChanges > 50 {
				scaledAdd = s.additions * 50 / maxChanges
				scaledDel = s.deletions * 50 / maxChanges
				if s.additions > 0 && scaledAdd == 0 {
					scaledAdd = 1
				}
				if s.deletions > 0 && scaledDel == 0 {
					scaledDel = 1
				}
			}
			bar = cs.Green(strings.Repeat("+", scaledAdd)) + cs.Red(strings.Repeat("-", scaledDel))
		}

		fmt.Fprintf(ios.Out, " %-*s | %4d %s\n", maxNameLen, s.name, total, bar)
	}

	fmt.Fprintf(ios.Out, " %d file", len(stats))
	if len(stats) != 1 {
		fmt.Fprint(ios.Out, "s")
	}
	fmt.Fprintf(ios.Out, " changed, %d insertion", totalAdditions)
	if totalAdditions != 1 {
		fmt.Fprint(ios.Out, "s")
	}
	fmt.Fprintf(ios.Out, "(+), %d deletion", totalDeletions)
	if totalDeletions != 1 {
		fmt.Fprint(ios.Out, "s")
	}
	fmt.Fprintln(ios.Out, "(-)")

	return nil
}

// printColorizedDiff prints the diff with ANSI color codes using ColorScheme.
func printColorizedDiff(diff string) error {
	cs := ios.ColorScheme()
	for _, line := range strings.Split(diff, "\n") {
		switch {
		case strings.HasPrefix(line, "diff --git "):
			fmt.Fprintln(ios.Out, cs.Bold(line))
		case strings.HasPrefix(line, "index "),
			strings.HasPrefix(line, "--- "),
			strings.HasPrefix(line, "+++ "),
			strings.HasPrefix(line, "new file"),
			strings.HasPrefix(line, "deleted file"),
			strings.HasPrefix(line, "similarity index"),
			strings.HasPrefix(line, "rename from"),
			strings.HasPrefix(line, "rename to"):
			fmt.Fprintln(ios.Out, cs.Bold(line))
		case strings.HasPrefix(line, "@@"):
			fmt.Fprintln(ios.Out, cs.Cyan(line))
		case strings.HasPrefix(line, "+"):
			fmt.Fprintln(ios.Out, cs.Green(line))
		case strings.HasPrefix(line, "-"):
			fmt.Fprintln(ios.Out, cs.Red(line))
		default:
			fmt.Fprintln(ios.Out, line)
		}
	}
	return nil
}
