import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, join, relative, resolve } from "node:path";
import { buildRouter } from "@wrnexus/router";

export type SdkLanguage = "typescript" | "javascript" | "java" | "go" | "python";
export interface ApiOperation {
  id: string;
  method: string;
  path: string;
  source: string;
  summary?: string;
  description?: string;
  webhook?: { event: string; payloadSchema?: string; signatureHeader?: string };
}
export interface ApiArtifacts {
  operations: ApiOperation[];
  files: string[];
}

function methods(file: string): string[] {
  const source = readFileSync(file, "utf8");
  const values = [
    ...source.matchAll(
      /export\s+(?:async\s+)?(?:const|function)\s+(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/g,
    ),
  ].map((match) => match[1]!);
  if (file.endsWith(".wrn"))
    for (const match of source.matchAll(/\bapi\s+(GET|POST|PUT|PATCH|DELETE)\s+/g))
      values.push(match[1]!);
  return [...new Set(values.length ? values : ["GET"])];
}
function openapiPath(path: string): string {
  return path
    .replace(/\[\.\.\.([A-Za-z_$][\w$]*)\]/g, "{$1}")
    .replace(/\[([A-Za-z_$][\w$]*)\]/g, "{$1}")
    .replace(/:([A-Za-z_$][\w$]*)/g, "{$1}")
    .replace(/\*([A-Za-z_$][\w$]*)/g, "{$1}");
}
function operationId(method: string, path: string): string {
  const words =
    `${method.toLowerCase()}-${path.replace(/^\/api\/?/, "").replace(/[^A-Za-z0-9]+/g, "-") || "root"}`
      .split("-")
      .filter(Boolean);
  return (
    words[0]! +
    words
      .slice(1)
      .map((word) => word[0]!.toUpperCase() + word.slice(1))
      .join("")
  );
}

export function inspectApi(appRoot: string): ApiOperation[] {
  const root = resolve(appRoot);
  const router = buildRouter(join(root, "app"));
  return router.api.flatMap((route) => {
    const sourceText = readFileSync(route.file, "utf8");
    const field = (name: string) =>
      new RegExp(`${name}\\s*:\\s*["']([^"']+)["']`).exec(sourceText)?.[1];
    const webhook = /\b(?:defineWebhook\s*\(|webhook\s*=)\s*\{/.test(sourceText)
      ? {
          event: field("event") ?? operationId("event", route.raw),
          payloadSchema: field("payloadSchema"),
          signatureHeader: field("signatureHeader"),
        }
      : undefined;
    return methods(route.file).map((method) => ({
      id: operationId(method, route.raw),
      method,
      path: openapiPath(route.raw),
      source: relative(root, route.file).replace(/\\/g, "/"),
      summary: field("summary"),
      description: field("description"),
      webhook,
    }));
  });
}

export function createOpenApi(operations: ApiOperation[], title = "WRNexus API") {
  const paths: Record<string, Record<string, unknown>> = {};
  for (const operation of operations) {
    const parameters = [...operation.path.matchAll(/\{([^}]+)\}/g)].map((match) => ({
      name: match[1],
      in: "path",
      required: true,
      schema: { type: "string" },
    }));
    (paths[operation.path] ??= {})[operation.method.toLowerCase()] = {
      operationId: operation.id,
      summary: operation.summary ?? `${operation.method} ${operation.path}`,
      ...(operation.description ? { description: operation.description } : {}),
      tags: ["API"],
      parameters,
      responses: {
        "200": {
          description: "Successful response",
          content: { "application/json": { schema: {} } },
        },
        "400": { description: "Invalid request" },
        "500": { description: "Internal error" },
      },
      "x-wrnexus-source": operation.source,
    };
  }
  const webhooks = Object.fromEntries(
    operations
      .filter((operation) => operation.webhook)
      .map((operation) => [
        operation.webhook!.event,
        {
          post: {
            summary: operation.summary ?? operation.webhook!.event,
            description: operation.description,
            parameters: operation.webhook!.signatureHeader
              ? [
                  {
                    name: operation.webhook!.signatureHeader,
                    in: "header",
                    required: true,
                    schema: { type: "string" },
                  },
                ]
              : [],
            requestBody: {
              required: true,
              content: {
                "application/json": {
                  schema: operation.webhook!.payloadSchema
                    ? { $ref: operation.webhook!.payloadSchema }
                    : {},
                },
              },
            },
            responses: { "200": { description: "Webhook accepted" } },
            "x-wrnexus-source": operation.source,
          },
        },
      ]),
  );
  return {
    openapi: "3.1.0",
    info: { title, version: "0.8.0" },
    paths,
    ...(Object.keys(webhooks).length ? { webhooks } : {}),
  };
}

function sdk(language: SdkLanguage, operations: ApiOperation[]): string {
  const route = (operation: ApiOperation) =>
    operation.path.replace(/\{([^}]+)\}/g, "${encodeURIComponent(params.$1)}");
  if (language === "typescript" || language === "javascript")
    return `${language === "typescript" ? "export type RequestOptions = { baseUrl?: string; headers?: HeadersInit };\ntype ApiEnvelope = { data?: unknown; error?: { message?: string } };\n" : ""}const request = async (${language === "typescript" ? "method: string, path: string, body: unknown, options: RequestOptions = {}" : "method, path, body, options = {}"}) => { const response = await globalThis.fetch((options.baseUrl || "") + path, { method, headers: { "content-type": "application/json", ...(options.headers || {}) }, body: body === undefined ? undefined : JSON.stringify(body) }); const value${language === "typescript" ? ": ApiEnvelope" : ""} = await response.json(); if (!response.ok) throw Object.assign(new Error(value?.error?.message || "API request failed"), { status: response.status, body: value }); return value.data ?? value; };\n${operations.map((operation) => `export const ${operation.id} = (params${language === "typescript" ? ": Record<string, string> = {}" : " = {}"}, body${language === "typescript" ? ": unknown" : ""}, options${language === "typescript" ? ": RequestOptions = {}" : " = {}"}) => { ${operation.path.includes("{") ? "" : "void params; "}return request("${operation.method}", \`${route(operation)}\`, body, options); };`).join("\n")}\n`;
  if (language === "python")
    return `import json, urllib.request\n\nclass WrnexusApi:\n    def __init__(self, base_url): self.base_url = base_url.rstrip('/')\n    def request(self, method, path, body=None):\n        data = None if body is None else json.dumps(body).encode()\n        request = urllib.request.Request(self.base_url + path, data=data, method=method, headers={'content-type':'application/json'})\n        with urllib.request.urlopen(request) as response: return json.load(response)\n${operations.map((operation) => `    def ${operation.id}(self, path, body=None): return self.request('${operation.method}', path, body)`).join("\n")}\n`;
  if (language === "go")
    return `package wrnexussdk\n\nimport ("bytes"; "encoding/json"; "fmt"; "net/http")\ntype Client struct { BaseURL string; HTTP *http.Client }\nfunc (c *Client) Request(method, path string, body any) (map[string]any, error) { data,_:=json.Marshal(body); req,_:=http.NewRequest(method,c.BaseURL+path,bytes.NewReader(data)); req.Header.Set("content-type","application/json"); client:=c.HTTP;if client==nil{client=http.DefaultClient};res,err:=client.Do(req);if err!=nil{return nil,err};defer res.Body.Close();if res.StatusCode>=400{return nil,fmt.Errorf("API status %d",res.StatusCode)};var out map[string]any;err=json.NewDecoder(res.Body).Decode(&out);return out,err }\n`;
  return `package dev.wrnexus.sdk;\nimport java.net.URI; import java.net.http.*;\npublic final class WrnexusApi { private final String baseUrl; private final HttpClient http = HttpClient.newHttpClient(); public WrnexusApi(String baseUrl){this.baseUrl=baseUrl;} public String request(String method,String path,String json)throws Exception{var request=HttpRequest.newBuilder(URI.create(baseUrl+path)).header("content-type","application/json").method(method,HttpRequest.BodyPublishers.ofString(json==null?"":json)).build();var response=http.send(request,HttpResponse.BodyHandlers.ofString());if(response.statusCode()>=400)throw new IllegalStateException("API status "+response.statusCode());return response.body();} }\n`;
}

function write(path: string, value: string, files: string[]): void {
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, value);
  files.push(path);
}
const escapeHtml = (value: unknown) =>
  String(value ?? "").replace(
    /[&<>"']/g,
    (character) =>
      ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character]!,
  );
export function generateApiArtifacts(
  appRoot: string,
  languages: SdkLanguage[] = ["typescript"],
): ApiArtifacts {
  const root = resolve(appRoot);
  const output = join(root, "generated", "api");
  const operations = inspectApi(root);
  const files: string[] = [];
  const spec = createOpenApi(operations, `${basename(root)} API`);
  write(join(output, "openapi.json"), JSON.stringify(spec, null, 2) + "\n", files);
  write(
    join(output, "postman.json"),
    JSON.stringify(
      {
        info: {
          name: spec.info.title,
          schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
        },
        item: operations.map((operation) => ({
          name: operation.id,
          request: { method: operation.method, url: `{{baseUrl}}${operation.path}` },
        })),
        variable: [{ key: "baseUrl", value: "http://localhost:3000" }],
      },
      null,
      2,
    ) + "\n",
    files,
  );
  write(
    join(output, "index.html"),
    `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>${escapeHtml(spec.info.title)}</title><style>body{font:16px system-ui;max-width:960px;margin:auto;padding:2rem}code,pre{background:#f4f4f5;padding:.2rem .4rem}article{border-bottom:1px solid #ddd;padding:1rem 0}</style><h1>${escapeHtml(spec.info.title)}</h1><p>OpenAPI 3.1 · ${operations.length} operations · <a href="openapi.json">specification</a></p>${operations.map((operation) => `<article><h2><code>${escapeHtml(operation.method)}</code> ${escapeHtml(operation.path)}</h2><p>${escapeHtml(operation.summary ?? operation.id)}</p>${operation.description ? `<p>${escapeHtml(operation.description)}</p>` : ""}${operation.webhook ? `<p>Webhook event: <code>${escapeHtml(operation.webhook.event)}</code>${operation.webhook.signatureHeader ? ` · signature: <code>${escapeHtml(operation.webhook.signatureHeader)}</code>` : ""}</p>` : ""}<small>${escapeHtml(operation.source)}</small></article>`).join("")}`,
    files,
  );
  write(
    join(output, "examples.md"),
    `# API examples\n\n${operations.map((operation) => `## ${operation.id}\n\n\`\`\`bash\ncurl -X ${operation.method} "http://localhost:3000${operation.path}"\n\`\`\`\n`).join("\n")}`,
    files,
  );
  const extensions = {
    typescript: "ts",
    javascript: "js",
    java: "java",
    go: "go",
    python: "py",
  } as const;
  for (const language of languages)
    write(
      join(output, "sdk", language, `wrnexus-api.${extensions[language]}`),
      sdk(language, operations),
      files,
    );
  return { operations, files };
}

export function runApiCommand(appRoot: string, kind: "api" | "sdk", args: string[]): ApiArtifacts {
  const supported: SdkLanguage[] = ["typescript", "javascript", "java", "go", "python"];
  const language = args.find((value) => supported.includes(value as SdkLanguage)) as
    SdkLanguage | undefined;
  if (kind === "sdk" && !language)
    throw new Error("WRN-SDK-LANGUAGE: choose typescript, javascript, java, go, or python.");
  const result = generateApiArtifacts(appRoot, kind === "sdk" ? [language!] : supported);
  console.log(
    `✓ Generated ${result.operations.length} API operations and ${result.files.length} artifacts in generated/api`,
  );
  return result;
}
