import {
    INodeType,
    INodeTypeDescription,
    IWebhookFunctions,
    IWebhookResponseData,
    ILoadOptionsFunctions,
    INodePropertyOptions,
} from 'n8n-workflow';
import { getMcpTools } from "../lib/mcp_tool_convert";

// ======================================================
// Cache tools per URL
// ======================================================
const toolsCache = new Map<string, any[]>();

// ======================================================
// Load OpenAPI → MCP Tools
// ======================================================
async function loadTools(openapiUrl: string, filterTag: string, forceRefresh = false): Promise<any[]> {
    const cacheKey = `${openapiUrl}::${filterTag}`;

    // Jika tidak forceRefresh, gunakan cache
    if (!forceRefresh && toolsCache.has(cacheKey)) {
        return toolsCache.get(cacheKey)!;
    }

    console.log(`[MCP] 🔄 Refreshing tools from ${openapiUrl} ...`);
    const fetched = await getMcpTools(openapiUrl, filterTag);

    // 🟢 Log jumlah & daftar tools
    console.log(`[MCP] ✅ Loaded ${fetched.length} tools`);
    if (fetched.length > 0) {
        console.log(
            `[MCP] Tools: ${fetched.map((t: any) => t.name).join(", ")}`
        );
    }

    toolsCache.set(cacheKey, fetched);
    return fetched;
}

// ======================================================
// JSON-RPC Types
// ======================================================
type JSONRPCRequest = {
    jsonrpc: "2.0";
    id: string | number;
    method: string;
    params?: any;
    credentials?: any;
};

type JSONRPCResponse = {
    jsonrpc: "2.0";
    id: string | number;
    result?: any;
    error?: {
        code: number;
        message: string;
        data?: any;
    };
};

// ======================================================
// Eksekusi Tool HTTP
// ======================================================
async function executeTool(
    tool: any,
    args: Record<string, any> = {},
    baseUrl: string,
    token?: string
) {
    const x = tool["x-props"] || {};
    const method = (x.method || "GET").toUpperCase();
    const path = x.path || `/${tool.name}`;
    const url = `${baseUrl}${path}`;

    const opts: RequestInit = {
        method,
        headers: {
            "Content-Type": "application/json",
            ...(token ? { Authorization: `Bearer ${token}` } : {}),
        },
    };

    if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
        opts.body = JSON.stringify(args || {});
    }

    const res = await fetch(url, opts);
    const contentType = res.headers.get("content-type") || "";

    const data = contentType.includes("application/json")
        ? await res.json()
        : await res.text();

    return {
        success: res.ok,
        status: res.status,
        method,
        path,
        data,
    };
}

// ======================================================
// JSON-RPC Handler (per node, per request)
// ======================================================
async function handleMCPRequest(
    request: JSONRPCRequest,
    tools: any[]
): Promise<JSONRPCResponse> {
    const { id, method, params, credentials } = request;

    switch (method) {
        case "initialize":
            return {
                jsonrpc: "2.0",
                id,
                result: {
                    protocolVersion: "2024-11-05",
                    capabilities: { tools: {} },
                    serverInfo: { name: "n8n-mcp-server", version: "1.0.0" },
                },
            };

        case "tools/list":
            // 🟢 Tambahkan jumlah dan daftar nama tools di respons
            return {
                jsonrpc: "2.0",
                id,
                result: {
                    count: tools.length,
                    names: tools.map(t => t.name),
                    tools: tools.map((t) => ({
                        name: t.name,
                        description: t.description,
                        inputSchema: t.inputSchema,
                        "x-props": t["x-props"],
                    })),
                },
            };

        case "tools/call": {
            const toolName = params?.name;
            const tool = tools.find((t) => t.name === toolName);

            if (!tool) {
                return {
                    jsonrpc: "2.0",
                    id,
                    error: { code: -32601, message: `Tool '${toolName}' not found` },
                };
            }

            try {
                const baseUrl = credentials?.baseUrl;
                const token = credentials?.token;

                const result = await executeTool(
                    tool,
                    params?.arguments || {},
                    baseUrl,
                    token
                );

                return {
                    jsonrpc: "2.0",
                    id,
                    result: {
                        content: [
                            {
                                type: "text",
                                text: JSON.stringify(result, null, 2),
                            },
                        ],
                    },
                };
            } catch (err: any) {
                return {
                    jsonrpc: "2.0",
                    id,
                    error: { code: -32603, message: err.message },
                };
            }
        }

        case "ping":
            return { jsonrpc: "2.0", id, result: {} };

        default:
            return {
                jsonrpc: "2.0",
                id,
                error: { code: -32601, message: `Method '${method}' not found` },
            };
    }
}

// ======================================================
// NODE MCP TRIGGER
// ======================================================
export class OpenapiMcpServer implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'OpenAPI MCP Server',
        name: 'openapiMcpServer',
        group: ['trigger'],
        version: 1,
        description: 'Runs an MCP Server inside n8n',
        icon: 'file:icon.svg',
        defaults: {
            name: 'OpenAPI MCP Server'
        },
        credentials: [
            { name: "openapiMcpServerCredentials", required: true },
        ],
        inputs: [],
        outputs: ['main'],
        webhooks: [
            {
                name: 'default',
                httpMethod: 'POST',
                responseMode: 'onReceived',
                path: '={{$parameter["path"]}}',
            },
        ],
        properties: [
            {
                displayName: "Path",
                name: "path",
                type: "string",
                default: "mcp",
            },
            {
                displayName: "OpenAPI URL",
                name: "openapiUrl",
                type: "string",
                default: "",
                placeholder: "https://example.com/openapi.json",
            },
            {
                displayName: "Default Filter",
                name: "defaultFilter",
                type: "string",
                default: "",
                placeholder: "mcp | tag",
            },
            // 🟢 Tambahan agar terlihat jumlah tools di UI
            {
                displayName: 'Available Tools (auto-refresh)',
                name: 'toolList',
                type: 'options',
                typeOptions: {
                    loadOptionsMethod: 'refreshToolList',
                    refreshOnOpen: true, // setiap node dibuka auto refresh
                },
                default: '',
                description: 'Daftar tools yang berhasil dimuat dari OpenAPI',
            },
        ],
    };

    // ==================================================
    // LoadOptions untuk tampil di dropdown
    // ==================================================
    methods = {
        loadOptions: {
            // 🟢 otomatis refetch setiap kali node dibuka
            async refreshToolList(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
                const openapiUrl = this.getNodeParameter("openapiUrl", 0) as string;
                const filterTag = this.getNodeParameter("defaultFilter", 0) as string;

                if (!openapiUrl) {
                    return [{ name: "❌ No OpenAPI URL provided", value: "" }];
                }

                const tools = await loadTools(openapiUrl, filterTag, true); // force refresh

                return tools.map((t) => ({
                    name: t.name,
                    value: t.name,
                    description: t.description,
                }));
            },
        },
    };

    // ==================================================
    // WEBHOOK HANDLER
    // ==================================================
    async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
        const openapiUrl = this.getNodeParameter("openapiUrl", 0) as string;
        const filterTag = this.getNodeParameter("defaultFilter", 0) as string;

        // 🟢 selalu refresh (agar node terbaru)
        const tools = await loadTools(openapiUrl, filterTag, true);

        const creds = await this.getCredentials("openapiMcpServerCredentials") as {
            baseUrl: string;
            token: string;
        };

        const body = this.getBodyData();

        if (Array.isArray(body)) {
            const responses = body.map((r) =>
                handleMCPRequest({ ...r, credentials: creds }, tools)
            );
            return {
                webhookResponse: await Promise.all(responses),
            };
        }

        const single = await handleMCPRequest(
            { ...(body as JSONRPCRequest), credentials: creds },
            tools
        );

        return {
            webhookResponse: single,
        };
    }
}
