import * as z from "zod/v4";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Registry } from "../registry/index.js";
import type { HarnessClient } from "../client/harness-client.js";
import { jsonResult, errorResult } from "../utils/response-formatter.js";
import { isUserError, isUserFixableApiError, toMcpError, enrichErrorWithHint, HarnessApiError } from "../utils/errors.js";
import { applyUrlDefaults } from "../utils/url-parser.js";
import { asString, coerceRecord } from "../utils/type-guards.js";
import { resolveLogContent } from "../utils/log-resolver.js";
import { buildLogPrefixFromExecution } from "../utils/log-prefix.js";
import { resourceTypeSchema } from "./input-schemas.js";
import { getOutputSchema } from "./output-schemas.js";

export function registerGetTool(server: McpServer, registry: Registry, client: HarnessClient): void {
  const gettableTypes = registry.getTypesForOperation("get");

  server.registerTool(
    "harness_get",
    {
      description: "Get a Harness resource by ID. Accepts a Harness URL to auto-extract identifiers. For failure analysis, prefer harness_diagnose.",
      inputSchema: {
        resource_type: resourceTypeSchema(gettableTypes).optional().describe("Resource type to retrieve. Auto-detected from url."),
        resource_id: z.string().describe("Primary resource identifier. Auto-detected from url.").optional(),
        url: z.string().describe("Harness UI URL — auto-extracts org, project, type, and ID").optional(),
        resource_scope: z.enum(["account", "org", "project"]).optional().describe("Scope to query. Use account for account-level resources and to omit org/project defaults; org injects only org; project injects org+project. Auto-detected from url."),
        org_id: z.string().describe("Organization identifier (overrides default)").optional(),
        project_id: z.string().describe("Project identifier (overrides default)").optional(),
        params: z.record(z.string(), z.unknown()).describe("Additional identifiers for nested resources. Call harness_describe for fields per resource_type.").optional(),
      },
      outputSchema: getOutputSchema,
      annotations: {
        title: "Get Harness Resource",
        readOnlyHint: true,
        destructiveHint: false,
        openWorldHint: true,
      },
    },
    async (args) => {
      try {
        const { params, ...rest } = args;
        const input = applyUrlDefaults(rest as Record<string, unknown>, args.url, { includeResourceScope: true });
        const coercedParams = coerceRecord(params);
        if (coercedParams) Object.assign(input, coercedParams);
        const resourceType = asString(input.resource_type);
        if (!resourceType) {
          return errorResult("resource_type is required. Provide it explicitly or via a Harness URL.");
        }
        const resourceId = asString(input.resource_id);

        const def = registry.getResource(resourceType);

        // Map resource_id to the resource's own identifier field.
        // For multi-identifier resources (e.g. pull_request: ["repo_id", "pr_number"]),
        // the last field is the resource-specific ID; earlier fields are parent context
        // (like repo_id) that come from URL parsing or explicit params.
        const identFields = def.identifierFields;
        const primaryField = identFields.length > 1
          ? identFields[identFields.length - 1]!
          : identFields[0];
        // For execution_log, resource_id is the execution ID — map it to
        // execution_id so buildLogPrefixFromExecution resolves the real log key
        // from the execution graph.  Don't map it to "prefix" (the identifier
        // field) because a raw execution ID is not a valid log-service prefix.
        if (resourceType === "execution_log" && resourceId && !asString(input.execution_id)) {
          input.execution_id = resourceId;
        }
        const shouldMapResourceId =
          primaryField &&
          resourceId &&
          !input[primaryField] &&
          resourceType !== "execution_log";
        if (shouldMapResourceId) {
          input[primaryField] = resourceId;
        }

        // When fetching a global template, override accountIdentifier to the global account.
        if (resourceType === "template" && (input.global === true || input.global === "true")) {
          input.account_id = "__GLOBAL_TEMPLATES_ACCOUNT_ID__";
          delete input.global;
        }

        // execution_log: resolve full log content instead of returning a download URL
        if (resourceType === "execution_log") {
          try {
            let prefix = asString(input.prefix);
            if (!prefix) {
              // Auto-build prefix from execution_id if available
              const executionId = asString(input.execution_id);
              if (!executionId) {
                return errorResult("prefix or execution_id is required for execution_log. Provide a log prefix or an execution ID to auto-build it.");
              }
              prefix = await buildLogPrefixFromExecution(client, registry, executionId, input);
            }
            const logText = await resolveLogContent(client, prefix);
            return jsonResult({ log_content: logText });
          } catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            return errorResult(`Failed to fetch execution logs: ${msg}. Try harness_diagnose with include_logs=true for better failure analysis.`);
          }
        }

        const result = await registry.dispatch(client, resourceType, "get", input);
        return jsonResult(result);
      } catch (err) {
        if (isUserError(err)) return errorResult(err.message);
        if (isUserFixableApiError(err)) {
          const rt = asString(args.resource_type);
          let hint: string | undefined;
          if (err instanceof HarnessApiError && err.statusCode === 404 && rt) {
            try { hint = registry.getResource(rt).diagnosticHint; } catch { /* unknown type */ }
          }
          return errorResult(enrichErrorWithHint(err.message, hint));
        }
        throw toMcpError(err);
      }
    },
  );
}
