package commitintel

import (
	"fmt"
	"log/slog"
	"strings"

	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/contracts/events"
	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/contracts/graph"
)

const maxSummaryLength = 500

// ImpactAnalyzer derives commit intelligence from graph deltas.
type ImpactAnalyzer interface {
	AnalyzeDelta(delta graph.Delta, event events.GraphDeltaReadyEvent, analysisID string) (CommitAnalysis, error)
}

// DeltaImpactAnalyzer provides rules-based commit analysis from graph deltas.
type DeltaImpactAnalyzer struct{}

func NewDeltaImpactAnalyzer() *DeltaImpactAnalyzer {
	return &DeltaImpactAnalyzer{}
}

func (a *DeltaImpactAnalyzer) AnalyzeDelta(
	delta graph.Delta,
	event events.GraphDeltaReadyEvent,
	analysisID string,
) (CommitAnalysis, error) {
	if len(delta.FileDeltas) == 0 {
		slog.Warn("graph delta has no file_deltas; using event counts only",
			"event_id", event.EventID,
			"repo_id", event.RepoID,
			"base_commit_sha", event.BaseCommitSHA,
			"target_commit_sha", event.TargetCommitSHA,
		)
	}

	changedFiles := changedFilesFromDelta(delta)
	impacted := impactedSymbolsFromDelta(delta)
	summary := buildSummary(delta, event)

	return CommitAnalysis{
		AnalysisID:      analysisID,
		RepoID:          event.RepoID,
		CommitSHA:       event.TargetCommitSHA,
		ImpactedSymbols: impacted,
		ChangedFiles:    changedFiles,
		Summary:         summary,
		Status:          "completed",
	}, nil
}

func changedFilesFromDelta(delta graph.Delta) []string {
	if len(delta.ChangedFiles) > 0 {
		return append([]string(nil), delta.ChangedFiles...)
	}

	seen := make(map[string]struct{})
	files := make([]string, 0, len(delta.FileDeltas))
	for _, fileDelta := range delta.FileDeltas {
		path := strings.TrimSpace(fileDelta.FilePath)
		if path == "" {
			continue
		}
		if _, ok := seen[path]; ok {
			continue
		}
		seen[path] = struct{}{}
		files = append(files, path)
	}
	return files
}

func impactedSymbolsFromDelta(delta graph.Delta) []string {
	seen := make(map[string]struct{})
	symbols := make([]string, 0)

	for _, fileDelta := range delta.FileDeltas {
		for _, node := range append(fileDelta.AddedNodes, fileDelta.RemovedNodes...) {
			if node.Kind != graph.NodeKindSymbol {
				continue
			}
			name := strings.TrimSpace(node.Name)
			if name == "" {
				continue
			}
			if _, ok := seen[name]; ok {
				continue
			}
			seen[name] = struct{}{}
			symbols = append(symbols, name)
		}
	}
	return symbols
}

func buildSummary(delta graph.Delta, event events.GraphDeltaReadyEvent) string {
	added := event.AddedNodes
	removed := event.RemovedNodes
	changedEdges := event.ChangedEdges

	if added == 0 && removed == 0 && changedEdges == 0 {
		added, removed, changedEdges = delta.Counts()
	}

	fileCount := len(changedFilesFromDelta(delta))
	if fileCount == 0 && len(delta.FileDeltas) > 0 {
		fileCount = len(delta.FileDeltas)
	}

	base := shortSHA(event.BaseCommitSHA)
	target := shortSHA(event.TargetCommitSHA)

	var summary string
	if len(delta.FileDeltas) == 0 {
		summary = fmt.Sprintf(
			"commit %s..%s: limited delta detail (added=%d removed=%d edge_changes=%d)",
			base, target, added, removed, changedEdges,
		)
	} else {
		summary = fmt.Sprintf(
			"%d symbols added, %d removed across %d files (%s..%s)",
			added, removed, fileCount, base, target,
		)
	}

	if len(summary) > maxSummaryLength {
		return summary[:maxSummaryLength]
	}
	return summary
}

func shortSHA(sha string) string {
	sha = strings.TrimSpace(sha)
	if len(sha) <= 8 {
		return sha
	}
	return sha[:8]
}
