package mcp

import (
	"context"
	"fmt"
	"strings"

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

	"github.com/zzet/gortex/internal/analysis"
	"github.com/zzet/gortex/internal/contracts"
	"github.com/zzet/gortex/internal/docs"
	"github.com/zzet/gortex/internal/wiki"
)

// registerWikiTools registers `generate_wiki` and `generate_docs`.
// Wired from server.go alongside the other tool registries.
func (s *Server) registerWikiTools() {
	s.addTool(
		mcp.NewTool("generate_wiki",
			mcp.WithDescription("Generate a markdown wiki of the indexed graph: per-community pages, process pages with Mermaid sequenceDiagrams, contracts, hotspots, cycles, semantic. Output is written under output_dir (default 'wiki'); the per-repo index page content is returned inline for quick preview."),
			mcp.WithString("output_dir", mcp.Description("Output directory (default: wiki)")),
			mcp.WithString("format", mcp.Description("markdown (default) | html")),
			mcp.WithString("repo", mcp.Description("Per-repo slug under wiki/ (default: 'repo')")),
			mcp.WithString("project", mcp.Description("Project label (multi-repo mode hint)")),
			mcp.WithString("workspace", mcp.Description("Restrict emitted nodes to this WorkspaceID")),
			mcp.WithNumber("min_community", mcp.Description("Minimum community size to document (default: 3)")),
			mcp.WithNumber("max_communities", mcp.Description("Max communities to document (default: 20)")),
			mcp.WithBoolean("no_processes", mcp.Description("Skip process pages")),
			mcp.WithBoolean("no_contracts", mcp.Description("Skip contracts page")),
			mcp.WithBoolean("no_docs", mcp.Description("Skip docs bundle inside the wiki")),
			mcp.WithBoolean("force", mcp.Description("Suppress 'already exists' diagnostics (writer is always idempotent)")),
			mcp.WithBoolean("wikilinks", mcp.Description("Use [[wikilink]] style links")),
		),
		s.handleGenerateWiki,
	)

	s.addTool(
		mcp.NewTool("generate_docs",
			mcp.WithDescription("Generate a 'living docs' bundle: recent file changes, per-author ownership, stale code older than 365 days, blame summary. Returns markdown (default) or JSON inline; pass output_path to write to disk instead."),
			mcp.WithString("output_path", mcp.Description("Path to write the bundle to; omit to return inline")),
			mcp.WithString("format", mcp.Description("markdown (default) | json")),
			mcp.WithString("since", mcp.Description("Window for recent changes (Go duration string, e.g. 24h, 7d). Default 168h.")),
			mcp.WithNumber("top", mcp.Description("Cap each section's row count (default: 20)")),
			mcp.WithString("include", mcp.Description("Comma-separated section list: recent,ownership,stale,blame")),
			mcp.WithString("path_prefix", mcp.Description("Filter ownership/stale rows to this file prefix")),
			mcp.WithString("workspace", mcp.Description("Restrict nodes to this WorkspaceID")),
			mcp.WithBoolean("run_blame", mcp.Description("Re-run git blame across indexed repos before rendering")),
		),
		s.handleGenerateDocs,
	)
}

func (s *Server) handleGenerateWiki(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := req.GetArguments()
	opts := wiki.Options{
		OutputDir:      stringArgOrDefault(args, "output_dir", "wiki"),
		Format:         stringArgOrDefault(args, "format", "markdown"),
		Wikilinks:      boolArgValue(args, "wikilinks"),
		Repo:           stringArgOrDefault(args, "repo", "repo"),
		Project:        stringArg(args, "project"),
		WorkspaceID:    stringArg(args, "workspace"),
		MinCommunity:   intArgOrDefault(args, "min_community", 3),
		MaxCommunities: intArgOrDefault(args, "max_communities", 20),
		NoProcesses:    boolArgValue(args, "no_processes"),
		NoContracts:    boolArgValue(args, "no_contracts"),
		NoDocs:         boolArgValue(args, "no_docs"),
		Force:          boolArgValue(args, "force"),
	}

	g := s.graph
	if g == nil {
		return mcp.NewToolResultError("wiki: graph is not initialised"), nil
	}
	communities := analysis.DetectCommunities(g)
	processes := analysis.DiscoverProcesses(g)
	hotspots := analysis.FindHotspots(g, communities, 0)
	cycles := analysis.DetectCycles(g, communities, "")

	var contractList []contracts.Contract
	if reg := s.effectiveContractRegistry(); reg != nil {
		contractList = reg.All()
	}

	// Optional docs bundle inside the wiki.
	var docsMarkdown string
	if !opts.NoDocs {
		bundle, err := docs.Generate(docs.Deps{Graph: g, History: s.docsHistoryProvider()}, docs.Options{
			WorkspaceID: opts.WorkspaceID,
		})
		if err == nil {
			docsMarkdown = docs.RenderMarkdown(bundle)
		}
	}

	gen := wiki.New(wiki.Inputs{
		Graph:       g,
		Communities: communities,
		Processes:   processes,
		Hotspots:    hotspots,
		Cycles:      cycles,
		Contracts:   contractList,
		DocsBundle:  docsMarkdown,
	}, opts)
	result, _, err := gen.Generate(ctx)
	if err != nil {
		return mcp.NewToolResultError(err.Error()), nil
	}
	return s.respondJSONOrTOON(ctx, req, map[string]any{
		"output_dir":            result.OutputDir,
		"files":                 result.Files,
		"index_md_content":      result.IndexMarkdown,
		"repo_index_md_content": result.RepoIndexMarkdown,
		"file_count":            len(result.Files),
	})
}

func (s *Server) handleGenerateDocs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := req.GetArguments()

	g := s.graph
	if g == nil {
		return mcp.NewToolResultError("docs: graph is not initialised"), nil
	}

	include := stringArg(args, "include")
	var sections []string
	if include != "" {
		for _, part := range strings.Split(include, ",") {
			p := strings.TrimSpace(strings.ToLower(part))
			if p != "" {
				sections = append(sections, p)
			}
		}
	}
	sinceDur, _ := parseDurationArg(args, "since")

	opts := docs.Options{
		Since:        sinceDur,
		Top:          intArgOrDefault(args, "top", 20),
		Sections:     sections,
		PathPrefix:   stringArg(args, "path_prefix"),
		WorkspaceID:  stringArg(args, "workspace"),
		IncludeBlame: boolArgValue(args, "run_blame"),
	}

	bundle, err := docs.Generate(docs.Deps{Graph: g, History: s.docsHistoryProvider()}, opts)
	if err != nil {
		return mcp.NewToolResultError(err.Error()), nil
	}

	format := stringArgOrDefault(args, "format", "markdown")
	output := ""
	if format == "json" {
		data, jerr := docs.RenderJSON(bundle)
		if jerr != nil {
			return mcp.NewToolResultError(jerr.Error()), nil
		}
		output = string(data)
	} else {
		output = docs.RenderMarkdown(bundle)
	}

	outputPath := stringArg(args, "output_path")
	if outputPath != "" {
		if err := writeWikiFile(outputPath, []byte(output)); err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("write %q: %v", outputPath, err)), nil
		}
		return s.respondJSONOrTOON(ctx, req, map[string]any{
			"output_path": outputPath,
			"bytes":       len(output),
			"sections":    bundle.Sections,
		})
	}
	return s.respondJSONOrTOON(ctx, req, map[string]any{
		"content":  output,
		"bytes":    len(output),
		"sections": bundle.Sections,
		"format":   format,
	})
}
