package mcp

import (
	"context"
	"encoding/json"

	"github.com/dtoro/oikos/internal/core/ports"
	"github.com/dtoro/oikos/internal/adapters/postgres"
	"github.com/dtoro/oikos/internal/policy"
	"github.com/google/uuid"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func AnalysisTools(pool *db.Pool, agentID uuid.UUID, sec ports.Secrets) []toolReg {
	return []toolReg{
				{tool: &mcp.Tool{Name: "get_health_summary", Description: "Fleet health per entity — optionally filter by health state(s)",
					InputSchema: objSchema(
						prop{"health", "string", "Comma-separated health states to include (e.g. 'down,stale'). Omit for all."},
					),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					healthStr, _ := args["health"].(string)
					query := `
					SELECT e.slug, e.type, st.health, st.last_check_at
					FROM entity_status st JOIN entities e ON e.id = st.entity_id
					WHERE e.type <> 'check' AND e.state <> 'destroyed'`
					if healthStr != "" {
						query += ` AND st.health = ANY(string_to_array($1, ','))`
						return queryRows(ctx, pool, query, healthStr), nil
					}
					query += ` ORDER BY e.slug`
					return queryRows(ctx, pool, query), nil
				}},
				{tool: &mcp.Tool{Name: "get_audit_trail", Description: "Query the audit log",
					InputSchema: objSchema(prop{"entity_id", "string", "Filter by affected entity UUID"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					return queryRows(ctx, pool, `
					SELECT id, ts, actor_type, action, entity_id::text, method, path, correlation_id, session_id::text
					FROM audit_log
					WHERE ($1::text IS NULL OR entity_id::text = $1)
					ORDER BY ts DESC LIMIT 50`, nStr(args["entity_id"])), nil
				}},
				{tool: &mcp.Tool{Name: "query_metrics", Description: "Time-series metrics with bucketed avg/min/max over N hours",
					InputSchema: objSchema(prop{"hours", "integer", "Look-back window in hours (default 24)"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					hours := int(getFloat(args, "hours", 24))
					return annotateJSONResult(queryRows(ctx, pool, `
					SELECT time_bucket('1 hour', ts) AS bucket,
					       entity_id::text, metric,
					       ROUND(avg(value)::numeric, 2) AS avg,
					       ROUND(min(value)::numeric, 2) AS min,
					       ROUND(max(value)::numeric, 2) AS max
					FROM metric_samples
					WHERE ts > now() - make_interval(hours => $1)
					GROUP BY bucket, entity_id, metric
					ORDER BY bucket DESC LIMIT 100`, hours), "metric_chart"), nil
				}},
				// ─── Phase 4: new tools ──────────────────────────────────────────
		
				{tool: &mcp.Tool{Name: "get_signal_history", Description: "Query open and recent signals",
					InputSchema: objSchema(
						prop{"entity_slug", "string", "Filter by target entity slug"},
						prop{"state", "string", "Filter by signal state (raised, resolved)"},
						prop{"limit", "integer", "Max rows (default 50)"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					limit := int(getFloat(args, "limit", 50))
					return queryRows(ctx, pool, `
					SELECT s.entity_id::text, s.kind, s.severity, s.state,
					       s.occurrence_count, e.slug AS target_slug,
					       s.first_seen_at, s.last_seen_at
					FROM signals s
					LEFT JOIN entities e ON e.id = s.target_entity_id
					WHERE ($1::text IS NULL OR e.slug = $1)
					  AND ($2::text IS NULL OR s.state = $2)
					ORDER BY s.last_seen_at DESC LIMIT $3`,
						nStr(args["entity_slug"]), nStr(args["state"]), limit), nil
				}},
				{tool: &mcp.Tool{Name: "get_patterns", Description: "List learned action patterns",
					InputSchema: objSchema(
						prop{"status", "string", "Filter by status (hypothesized, validated, active)"},
						prop{"entity_type", "string", "Filter by applies_type"},
						prop{"action", "string", "Filter by action"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					return queryRows(ctx, pool, `
					SELECT p.entity_id::text, p.applies_type, p.action, p.pattern,
					       p.confidence, p.evidence_count, p.success_count, p.failure_count,
					       p.status, p.quarantined, p.version, p.last_validated_at
					FROM patterns p
					WHERE ($1::text IS NULL OR p.status = $1)
					  AND ($2::text IS NULL OR p.applies_type = $2)
					  AND ($3::text IS NULL OR p.action = $3)
					ORDER BY p.applies_type, p.action`,
						nStr(args["status"]), nStr(args["entity_type"]), nStr(args["action"])), nil
				}},
				{tool: &mcp.Tool{Name: "get_skills", Description: "List available automation skills",
					InputSchema: objSchema(
						prop{"status", "string", "Filter by status (active, inactive, deprecated)"},
					),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					return queryRows(ctx, pool, `
					SELECT s.entity_id::text, s.version, s.name, LEFT(s.procedure::text, 300) AS procedure_preview,
					       s.applies_type, s.action, s.status, s.success_rate,
					       s.changed_by::text, s.change_reason, s.last_used_at
					FROM skills s
					WHERE ($1::text IS NULL OR s.status = $1)
					ORDER BY s.name, s.version DESC`,
						nStr(args["status"])), nil
				}},
				{tool: &mcp.Tool{Name: "get_trend", Description: "Metric slope, variance, and averages for an entity over N days",
					InputSchema: objSchema(
						prop{"entity_id", "string", "Entity slug"},
						prop{"days", "integer", "Look-back window in days (default 7)"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					slug, _ := args["entity_id"].(string)
					days := int(getFloat(args, "days", 7))
					return queryRows(ctx, pool, `
					SELECT metric,
					       ROUND(avg(value)::numeric, 2) AS avg_val,
					       ROUND(stddev(value)::numeric, 2) AS std_val,
					       count(*) AS sample_count,
					       ROUND(regr_slope(value, EXTRACT(EPOCH FROM ts)::numeric)::numeric, 4) AS slope
					FROM metric_samples ms
					JOIN entities e ON e.id = ms.entity_id
					WHERE e.slug = $1 AND ts >= now() - make_interval(days => $2)
					GROUP BY metric
					ORDER BY metric`, slug, days), nil
				}},
				{tool: &mcp.Tool{Name: "get_event_timeline", Description: "Recent events filtered by severity and entity slug",
					InputSchema: objSchema(
						prop{"severity", "string", "Filter by severity (info, warn, error)"},
						prop{"entity_slug", "string", "Filter by entity slug"},
						prop{"limit", "integer", "Max rows (default 50)"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					limit := int(getFloat(args, "limit", 50))
					return queryRows(ctx, pool, `
					SELECT ev.ts, ev.type, ev.severity, ev.source, e.slug AS entity_slug,
					       ev.data::text AS message, ev.correlation_id
					FROM events ev
					LEFT JOIN entities e ON e.id = ev.entity_id
					WHERE ($1::text IS NULL OR ev.severity = $1)
					  AND ($2::text IS NULL OR e.slug = $2)
					ORDER BY ev.ts DESC LIMIT $3`,
						nStr(args["severity"]), nStr(args["entity_slug"]), limit), nil
				}},
				{tool: &mcp.Tool{Name: "get_agent_activity", Description: "Agent self-inspection: query agent activity log",
					InputSchema: objSchema(
						prop{"limit", "integer", "Max rows (default 50)"}),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					limit := int(getFloat(args, "limit", 50))
					return annotateJSONResult(queryRows(ctx, pool, `
					SELECT id, ts, agent_id::text, session_id, activity_type, tool_name,
					       entity_id::text, left(input_summary, 200) AS input_summary,
					       left(output_summary, 200) AS output_summary,
					       duration_ms, token_count, success, correlation_id
					FROM agent_activity
					WHERE agent_id = $1
					ORDER BY ts DESC LIMIT $2`, agentID, limit), "change_log"), nil
				}},
				// classify_command is the command-scoped preflight from
				// plans/2026-07-20-session-review-ten-sessions.md P0.2. The
				// existing `preflight` tool is entity/action-scoped — useless when
				// the agent is composing a `run` command and needs to know whether
				// the classifier will accept it before submitting. Without this,
				// the agent has to retry with cosmetic variations until it finds
				// one that passes (see sessions a51e2086, 8acea2e3 — three
				// duplicate rclone sessions, all bouncing off the classifier).
				// Call this BEFORE `run` whenever the classification is uncertain.
				{tool: &mcp.Tool{Name: "classify_command", Description: "Pre-flight risk classification for a shell command BEFORE calling run. Returns the risk class (read_only / reversible_low / config_mutation / destructive) that `run` would assign. Use this when you're unsure whether a command will auto-execute or need approval — e.g. `pct exec`, `curl`, compound commands, or anything that might be mistaken for mutation. If this returns read_only, the same command will auto-execute via run with no approval; if it returns config_mutation, expect to need operator approval (or pre-frame the command so it classifies lower). Declared risk can only escalate, never de-escalate.",
					InputSchema: objSchema(
						prop{"command", "string", "The exact shell command you intend to pass to run."},
						prop{"declared_risk", "string", "Optional self-assessment you would pass to run (read_only, reversible_low, config_mutation, destructive). Mirrors run's declared_risk parameter."},
					),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					command, _ := args["command"].(string)
					declaredRisk, _ := args["declared_risk"].(string)
					if command == "" {
						return textResult("error: command is required"), nil
					}
					risk := policy.ClassifyCommand(command, declaredRisk)
					note := ""
					switch risk {
					case policy.RiskReadOnly:
						note = "auto-acts on `run` (no approval needed)."
					case policy.RiskReversibleLow:
						note = "auto-acts on `run` (no approval needed)."
					case policy.RiskConfigMutation:
						note = "requires operator approval on `run` (or loose assent window active)."
					case policy.RiskDestructive:
						note = "requires explicit operator confirmation on `run` (typed \"I confirm\" phrase)."
					}
					out, _ := json.Marshal(map[string]any{
						"command":       command,
						"declared_risk": declaredRisk,
						"risk_class":    risk,
						"note":          note,
					})
					return textResult(string(out)), nil
				}},
				{tool: &mcp.Tool{Name: "get_ontology", Description: "Entity types, relationship types, and lifecycle definitions. Use this to understand the schema — what entity types exist, what relationships connect them, and what lifecycle states each type supports.",
					InputSchema: objSchema(),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					etResult := queryRowsJSONSingle(ctx, pool, `
						SELECT name, parent_type, is_abstract, domain, layer,
						       description, lifecycle_id, schema_version, status
						FROM entity_types ORDER BY name`)
		
					rtResult := queryRowsJSONSingle(ctx, pool, `
						SELECT name, inverse, source_type, target_type,
						       cardinality, description
						FROM relationship_types ORDER BY name`)
		
					lcResult := queryRowsJSONSingle(ctx, pool, `
						SELECT id, name, states, transitions::text
						FROM lifecycles ORDER BY name`)
		
					result := map[string]any{
						"entity_types":       etResult,
						"relationship_types": rtResult,
						"lifecycles":         lcResult,
					}
					b, _ := json.MarshalIndent(result, "", "  ")
					return textResult(string(b)), nil
				}},
				{tool: &mcp.Tool{Name: "http_get", Description: "Fetch a public web page or raw file (e.g. a GitHub README/raw URL) and return sanitized text. Use this to research how to deploy a service before provisioning. HTTP/HTTPS only; body is truncated to ~16KB.",
					InputSchema: objSchema(
						prop{"url", "string", "Absolute http(s) URL to fetch"},
					),
				}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
					args := argsMap(req)
					rawURL, _ := args["url"].(string)
					return httpGet(ctx, rawURL), nil
				}},
	}
}
