import { describe, expect, it } from "vitest";
import type {
  HostedContributionDefinition,
  HostedSystemPromptTransformRequest,
} from "@best-agent/protocol";
import { createHostedContributionBundle, type HostedToolBridge } from "./runtime-contribution.js";

const bridge: HostedToolBridge = {
  discoverTools: async (request) => ({ toolNames: request.toolNames }),
  getCapabilities: async () => [],
  invoke: async () => "ok",
  transformSystemPrompt: async (request: HostedSystemPromptTransformRequest) => ({
    systemPrompt: request.systemPrompt,
  }),
};

describe("hosted contribution runtime", () => {
  it("uses an explicit no-input schema when a hosted tool omits inputSchema", () => {
    const bundle = createHostedContributionBundle(
      [
        {
          name: "plugin",
          tools: [{ name: "noop", description: "No input." }],
        },
      ],
      bridge,
    );

    expect(bundle.tools[0]?.inputSchema).toEqual({
      additionalProperties: false,
      properties: {},
      required: [],
      type: "object",
    });
  });

  it("preserves supported hosted tool JSON schemas", () => {
    const bundle = createHostedContributionBundle(
      [
        {
          name: "plugin",
          tools: [
            {
              name: "summarize",
              description: "Summarize a path.",
              inputSchema: {
                additionalProperties: false,
                properties: {
                  path: { type: "string" },
                  tags: { items: { type: "string" }, type: "array" },
                },
                required: ["path", "tags"],
                type: "object",
              },
            },
          ],
        },
      ],
      bridge,
    );

    expect(bundle.tools[0]?.inputSchema).toEqual({
      additionalProperties: false,
      properties: {
        path: { type: "string" },
        tags: { items: { type: "string" }, type: "array" },
      },
      required: ["path", "tags"],
      type: "object",
    });
  });

  it("rejects hosted tool input schemas without object roots", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "summarize",
            description: "Summarize.",
            inputSchema: { type: "string" },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /inputSchema root must set type: "object"/,
    );
  });

  it("rejects unsupported hosted tool JSON schema keywords", () => {
    const contributions: HostedContributionDefinition[] = JSON.parse(`[
      {
        "name": "plugin",
        "tools": [
          {
            "name": "search",
            "description": "Search.",
            "inputSchema": {
              "type": "object",
              "oneOf": []
            }
          }
        ]
      }
    ]`);

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /inputSchema\.oneOf is not supported/,
    );
  });

  it("rejects object schema keywords without object type", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "summarize",
            description: "Summarize.",
            inputSchema: {
              additionalProperties: false,
              properties: { path: { type: "string" } },
              required: ["path"],
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /must set type: "object"/,
    );
  });

  it("rejects hosted object schemas without additionalProperties false", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "summarize",
            description: "Summarize.",
            inputSchema: {
              properties: { path: { type: "string" } },
              required: ["path"],
              type: "object",
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /additionalProperties: false/,
    );
  });

  it("rejects hosted object schemas with additionalProperties true", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "summarize",
            description: "Summarize.",
            inputSchema: {
              additionalProperties: true,
              properties: { path: { type: "string" } },
              required: ["path"],
              type: "object",
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /additionalProperties: false/,
    );
  });

  it("rejects required hosted schema fields missing from properties", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "summarize",
            description: "Summarize.",
            inputSchema: {
              additionalProperties: false,
              properties: { path: { type: "string" } },
              required: ["missing"],
              type: "object",
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /required contains "missing"/,
    );
  });

  it("rejects hosted property schemas without type or enum", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "summarize",
            description: "Summarize.",
            inputSchema: {
              additionalProperties: false,
              properties: {
                path: {},
              },
              required: ["path"],
              type: "object",
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /properties\.path.*type or enum/,
    );
  });

  it("rejects hosted array schemas with items but no array type", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "batch",
            description: "Batch.",
            inputSchema: {
              items: { type: "string" },
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /must set type: "array"/,
    );
  });

  it("rejects hosted array schemas without items", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "batch",
            description: "Batch.",
            inputSchema: {
              type: "array",
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /array schemas must set items/,
    );
  });

  it("rejects hosted array schemas at the input root", () => {
    const contributions: HostedContributionDefinition[] = [
      {
        name: "plugin",
        tools: [
          {
            name: "batch",
            description: "Batch.",
            inputSchema: {
              items: { type: "string" },
              type: "array",
            },
          },
        ],
      },
    ];

    expect(() => createHostedContributionBundle(contributions, bridge)).toThrow(
      /inputSchema root must set type: "object"/,
    );
  });
});
