// github_diff.go provides change tracking for dry-run output.

package setup

import (
	"fmt"
	"sort"
	"strings"

	"github.com/host-uk/core/pkg/cli"
	"github.com/host-uk/core/pkg/i18n"
)

// ChangeType indicates the type of change being made.
type ChangeType string

// Change type constants for GitHub configuration diffs.
const (
	// ChangeCreate indicates a new resource to be created.
	ChangeCreate ChangeType = "create"
	// ChangeUpdate indicates an existing resource to be updated.
	ChangeUpdate ChangeType = "update"
	// ChangeDelete indicates a resource to be deleted.
	ChangeDelete ChangeType = "delete"
	// ChangeSkip indicates a resource that requires no changes.
	ChangeSkip ChangeType = "skip"
)

// ChangeCategory groups changes by type.
type ChangeCategory string

// Change category constants for grouping GitHub configuration changes.
const (
	// CategoryLabel indicates label-related changes.
	CategoryLabel ChangeCategory = "label"
	// CategoryWebhook indicates webhook-related changes.
	CategoryWebhook ChangeCategory = "webhook"
	// CategoryProtection indicates branch protection changes.
	CategoryProtection ChangeCategory = "protection"
	// CategorySecurity indicates security settings changes.
	CategorySecurity ChangeCategory = "security"
)

// Change represents a single change to be made.
type Change struct {
	Category    ChangeCategory
	Type        ChangeType
	Name        string
	Description string
	Details     map[string]string // Key-value details about the change
}

// ChangeSet tracks all changes for a repository.
type ChangeSet struct {
	Repo    string
	Changes []Change
}

// NewChangeSet creates a new change set for a repository.
func NewChangeSet(repo string) *ChangeSet {
	return &ChangeSet{
		Repo:    repo,
		Changes: make([]Change, 0),
	}
}

// Add adds a change to the set.
func (cs *ChangeSet) Add(category ChangeCategory, changeType ChangeType, name, description string) {
	cs.Changes = append(cs.Changes, Change{
		Category:    category,
		Type:        changeType,
		Name:        name,
		Description: description,
		Details:     make(map[string]string),
	})
}

// AddWithDetails adds a change with additional details.
func (cs *ChangeSet) AddWithDetails(category ChangeCategory, changeType ChangeType, name, description string, details map[string]string) {
	cs.Changes = append(cs.Changes, Change{
		Category:    category,
		Type:        changeType,
		Name:        name,
		Description: description,
		Details:     details,
	})
}

// HasChanges returns true if there are any non-skip changes.
func (cs *ChangeSet) HasChanges() bool {
	for _, c := range cs.Changes {
		if c.Type != ChangeSkip {
			return true
		}
	}
	return false
}

// Count returns the number of changes by type.
func (cs *ChangeSet) Count() (creates, updates, deletes, skips int) {
	for _, c := range cs.Changes {
		switch c.Type {
		case ChangeCreate:
			creates++
		case ChangeUpdate:
			updates++
		case ChangeDelete:
			deletes++
		case ChangeSkip:
			skips++
		}
	}
	return
}

// CountByCategory returns changes grouped by category.
func (cs *ChangeSet) CountByCategory() map[ChangeCategory]int {
	counts := make(map[ChangeCategory]int)
	for _, c := range cs.Changes {
		if c.Type != ChangeSkip {
			counts[c.Category]++
		}
	}
	return counts
}

// Print outputs the change set to the console.
func (cs *ChangeSet) Print(verbose bool) {
	creates, updates, deletes, skips := cs.Count()

	// Print header
	fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.Label("repo")), repoNameStyle.Render(cs.Repo))

	if !cs.HasChanges() {
		fmt.Printf("  %s\n", dimStyle.Render(i18n.T("cmd.setup.github.no_changes")))
		return
	}

	// Print summary
	var parts []string
	if creates > 0 {
		parts = append(parts, successStyle.Render(fmt.Sprintf("+%d", creates)))
	}
	if updates > 0 {
		parts = append(parts, warningStyle.Render(fmt.Sprintf("~%d", updates)))
	}
	if deletes > 0 {
		parts = append(parts, errorStyle.Render(fmt.Sprintf("-%d", deletes)))
	}
	if skips > 0 && verbose {
		parts = append(parts, dimStyle.Render(fmt.Sprintf("=%d", skips)))
	}
	fmt.Printf("  %s\n", strings.Join(parts, " "))

	// Print details if verbose
	if verbose {
		cs.printByCategory(CategoryLabel, "Labels")
		cs.printByCategory(CategoryWebhook, "Webhooks")
		cs.printByCategory(CategoryProtection, "Branch protection")
		cs.printByCategory(CategorySecurity, "Security")
	}
}

func (cs *ChangeSet) printByCategory(category ChangeCategory, title string) {
	var categoryChanges []Change
	for _, c := range cs.Changes {
		if c.Category == category && c.Type != ChangeSkip {
			categoryChanges = append(categoryChanges, c)
		}
	}

	if len(categoryChanges) == 0 {
		return
	}

	fmt.Printf("\n  %s:\n", dimStyle.Render(title))
	for _, c := range categoryChanges {
		icon := getChangeIcon(c.Type)
		style := getChangeStyle(c.Type)
		fmt.Printf("    %s %s", style.Render(icon), c.Name)
		if c.Description != "" {
			fmt.Printf(" %s", dimStyle.Render(c.Description))
		}
		fmt.Println()

		// Print details (sorted for deterministic output)
		keys := make([]string, 0, len(c.Details))
		for k := range c.Details {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, k := range keys {
			fmt.Printf("      %s: %s\n", dimStyle.Render(k), c.Details[k])
		}
	}
}

func getChangeIcon(t ChangeType) string {
	switch t {
	case ChangeCreate:
		return "+"
	case ChangeUpdate:
		return "~"
	case ChangeDelete:
		return "-"
	default:
		return "="
	}
}

func getChangeStyle(t ChangeType) *cli.AnsiStyle {
	switch t {
	case ChangeCreate:
		return successStyle
	case ChangeUpdate:
		return warningStyle
	case ChangeDelete:
		return errorStyle
	default:
		return dimStyle
	}
}

// Aggregate combines multiple change sets into a summary.
type Aggregate struct {
	Sets []*ChangeSet
}

// NewAggregate creates a new aggregate.
func NewAggregate() *Aggregate {
	return &Aggregate{
		Sets: make([]*ChangeSet, 0),
	}
}

// Add adds a change set to the aggregate.
func (a *Aggregate) Add(cs *ChangeSet) {
	a.Sets = append(a.Sets, cs)
}

// TotalChanges returns the total number of changes across all sets.
func (a *Aggregate) TotalChanges() (creates, updates, deletes, skips int) {
	for _, cs := range a.Sets {
		c, u, d, s := cs.Count()
		creates += c
		updates += u
		deletes += d
		skips += s
	}
	return
}

// ReposWithChanges returns the number of repos that have changes.
func (a *Aggregate) ReposWithChanges() int {
	count := 0
	for _, cs := range a.Sets {
		if cs.HasChanges() {
			count++
		}
	}
	return count
}

// PrintSummary outputs the aggregate summary.
func (a *Aggregate) PrintSummary() {
	creates, updates, deletes, _ := a.TotalChanges()
	reposWithChanges := a.ReposWithChanges()

	fmt.Println()
	fmt.Printf("%s\n", dimStyle.Render(i18n.Label("summary")))
	fmt.Printf("  %s: %d\n", i18n.T("cmd.setup.github.repos_checked"), len(a.Sets))

	if reposWithChanges == 0 {
		fmt.Printf("  %s\n", dimStyle.Render(i18n.T("cmd.setup.github.all_up_to_date")))
		return
	}

	fmt.Printf("  %s: %d\n", i18n.T("cmd.setup.github.repos_with_changes"), reposWithChanges)
	if creates > 0 {
		fmt.Printf("  %s: %s\n", i18n.T("cmd.setup.github.to_create"), successStyle.Render(fmt.Sprintf("%d", creates)))
	}
	if updates > 0 {
		fmt.Printf("  %s: %s\n", i18n.T("cmd.setup.github.to_update"), warningStyle.Render(fmt.Sprintf("%d", updates)))
	}
	if deletes > 0 {
		fmt.Printf("  %s: %s\n", i18n.T("cmd.setup.github.to_delete"), errorStyle.Render(fmt.Sprintf("%d", deletes)))
	}
}
