package mcp

import (
	"context"
	"sort"
	"strings"

	"github.com/mark3labs/mcp-go/mcp"

	"github.com/zzet/gortex/internal/graph"
)

// handleAnalyzeK8sResources surfaces the KindResource graph layer
// produced by the K8s manifest extractor. Returns one row per Resource
// node with kind, namespace, name, file, line, and the count of
// outgoing infra edges (depends_on / configures / mounts / exposes /
// uses_env). Useful for "which deployments live in prod" / "which
// ConfigMaps no workload references" / "what does this Resource
// reach?" queries.
//
// Filters:
//   - k8s_kind:  K8s kind filter (Deployment / Service / Ingress / …)
//   - namespace: substring match on namespace
//   - name:      substring match on resource name
func (s *Server) handleAnalyzeK8sResources(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := req.GetArguments()
	kindFilter := strings.TrimSpace(stringArg(args, "k8s_kind"))
	nsFilter := strings.ToLower(strings.TrimSpace(stringArg(args, "namespace")))
	nameFilter := strings.ToLower(strings.TrimSpace(stringArg(args, "name")))

	type resourceRow struct {
		ID         string `json:"id"`
		K8sKind    string `json:"k8s_kind"`
		Namespace  string `json:"namespace"`
		Name       string `json:"name"`
		File       string `json:"file"`
		Line       int    `json:"line"`
		DependsOn  int    `json:"depends_on"`
		Configures int    `json:"configures"`
		Mounts     int    `json:"mounts"`
		Exposes    int    `json:"exposes"`
		UsesEnv    int    `json:"uses_env"`
	}

	// Tally per-resource edge fan-out in a single pass so we don't
	// re-walk AllEdges() for every resource.
	type counts struct {
		dependsOn, configures, mounts, exposes, usesEnv int
	}
	tally := make(map[string]*counts)
	bump := func(id string, edge graph.EdgeKind) {
		c, ok := tally[id]
		if !ok {
			c = &counts{}
			tally[id] = c
		}
		switch edge {
		case graph.EdgeDependsOn:
			c.dependsOn++
		case graph.EdgeConfigures:
			c.configures++
		case graph.EdgeMounts:
			c.mounts++
		case graph.EdgeExposes:
			c.exposes++
		case graph.EdgeUsesEnv:
			c.usesEnv++
		}
	}
	for _, e := range s.graph.AllEdges() {
		switch e.Kind {
		case graph.EdgeDependsOn, graph.EdgeConfigures, graph.EdgeMounts,
			graph.EdgeExposes, graph.EdgeUsesEnv:
			bump(e.From, e.Kind)
		}
	}

	var rows []*resourceRow
	for _, n := range s.scopedNodes(ctx) {
		if n.Kind != graph.KindResource {
			continue
		}
		k8sKind, _ := n.Meta["k8s_kind"].(string)
		ns, _ := n.Meta["namespace"].(string)
		if kindFilter != "" && !strings.EqualFold(k8sKind, kindFilter) {
			continue
		}
		if nsFilter != "" && !strings.Contains(strings.ToLower(ns), nsFilter) {
			continue
		}
		if nameFilter != "" && !strings.Contains(strings.ToLower(n.Name), nameFilter) {
			continue
		}
		c := tally[n.ID]
		if c == nil {
			c = &counts{}
		}
		rows = append(rows, &resourceRow{
			ID: n.ID, K8sKind: k8sKind, Namespace: ns, Name: n.Name,
			File: n.FilePath, Line: n.StartLine,
			DependsOn:  c.dependsOn,
			Configures: c.configures,
			Mounts:     c.mounts,
			Exposes:    c.exposes,
			UsesEnv:    c.usesEnv,
		})
	}
	sort.Slice(rows, func(i, j int) bool {
		if rows[i].K8sKind != rows[j].K8sKind {
			return rows[i].K8sKind < rows[j].K8sKind
		}
		if rows[i].Namespace != rows[j].Namespace {
			return rows[i].Namespace < rows[j].Namespace
		}
		return rows[i].Name < rows[j].Name
	})
	return s.respondJSONOrTOON(ctx, req, map[string]any{
		"resources": rows,
		"total":     len(rows),
	})
}

// handleAnalyzeImages surfaces the KindImage graph layer. Returns
// one row per Image node with consumer counts (Dockerfile stages
// that build FROM it, K8s Resources that reference it). Useful for
// "which images are stale" / "which Resource pulls nginx:latest"
// queries.
//
// Filters:
//   - role:      base | stage (default: both)
//   - ref:       substring match on the image reference (registry/path)
//   - tag:       exact tag match (latest / 1.25 / sha256:…)
func (s *Server) handleAnalyzeImages(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := req.GetArguments()
	roleFilter := strings.ToLower(strings.TrimSpace(stringArg(args, "role")))
	refFilter := strings.ToLower(strings.TrimSpace(stringArg(args, "ref")))
	tagFilter := strings.TrimSpace(stringArg(args, "tag"))

	type imageRow struct {
		ID        string `json:"id"`
		Name      string `json:"name"`
		Role      string `json:"role,omitempty"`
		Ref       string `json:"ref,omitempty"`
		Tag       string `json:"tag,omitempty"`
		File      string `json:"file"`
		Line      int    `json:"line"`
		Consumers int    `json:"consumers"`
	}

	consumers := make(map[string]int)
	for _, e := range s.graph.AllEdges() {
		if e.Kind != graph.EdgeDependsOn {
			continue
		}
		consumers[e.To]++
	}

	var rows []*imageRow
	for _, n := range s.scopedNodes(ctx) {
		if n.Kind != graph.KindImage {
			continue
		}
		role, _ := n.Meta["role"].(string)
		ref, _ := n.Meta["ref"].(string)
		tag, _ := n.Meta["tag"].(string)
		if roleFilter != "" && role != roleFilter {
			continue
		}
		if refFilter != "" && !strings.Contains(strings.ToLower(ref), refFilter) {
			continue
		}
		if tagFilter != "" && tag != tagFilter {
			continue
		}
		rows = append(rows, &imageRow{
			ID: n.ID, Name: n.Name, Role: role, Ref: ref, Tag: tag,
			File: n.FilePath, Line: n.StartLine,
			Consumers: consumers[n.ID],
		})
	}
	sort.Slice(rows, func(i, j int) bool {
		if rows[i].Role != rows[j].Role {
			return rows[i].Role < rows[j].Role
		}
		if rows[i].Ref != rows[j].Ref {
			return rows[i].Ref < rows[j].Ref
		}
		return rows[i].Tag < rows[j].Tag
	})
	return s.respondJSONOrTOON(ctx, req, map[string]any{
		"images": rows,
		"total":  len(rows),
	})
}

// handleAnalyzeKustomize surfaces the KindKustomization graph layer.
// Returns one row per overlay with its base count and resource count
// (computed from outgoing EdgeDependsOn / EdgeReferences fan-out).
//
// Filters:
//   - dir:  substring match on the overlay's directory.
func (s *Server) handleAnalyzeKustomize(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := req.GetArguments()
	dirFilter := strings.ToLower(strings.TrimSpace(stringArg(args, "dir")))

	type overlayRow struct {
		ID        string `json:"id"`
		Dir       string `json:"dir"`
		File      string `json:"file"`
		Line      int    `json:"line"`
		DependsOn int    `json:"depends_on"`
		Resources int    `json:"resources"`
	}

	type counts struct{ deps, res int }
	tally := make(map[string]*counts)
	bump := func(id string, edge graph.EdgeKind) {
		c, ok := tally[id]
		if !ok {
			c = &counts{}
			tally[id] = c
		}
		switch edge {
		case graph.EdgeDependsOn:
			c.deps++
		case graph.EdgeReferences:
			c.res++
		}
	}
	for _, e := range s.graph.AllEdges() {
		switch e.Kind {
		case graph.EdgeDependsOn, graph.EdgeReferences:
			bump(e.From, e.Kind)
		}
	}

	var rows []*overlayRow
	for _, n := range s.scopedNodes(ctx) {
		if n.Kind != graph.KindKustomization {
			continue
		}
		dir, _ := n.Meta["dir"].(string)
		if dir == "" {
			dir = n.Name
		}
		if dirFilter != "" && !strings.Contains(strings.ToLower(dir), dirFilter) {
			continue
		}
		c := tally[n.ID]
		if c == nil {
			c = &counts{}
		}
		rows = append(rows, &overlayRow{
			ID: n.ID, Dir: dir,
			File: n.FilePath, Line: n.StartLine,
			DependsOn: c.deps, Resources: c.res,
		})
	}
	sort.Slice(rows, func(i, j int) bool { return rows[i].Dir < rows[j].Dir })
	return s.respondJSONOrTOON(ctx, req, map[string]any{
		"kustomizations": rows,
		"total":          len(rows),
	})
}
