import { parse } from "yaml";
import { getTestHarness, TestHarness, transportTypes } from "./utils.js";
import { Config } from "../src/model/config/config.js";
import { appConfigSchema } from "@mcpx/shared-model";

const MCPX_BASE_URL = "http://localhost:9000";

describe("App Config", () => {
  describe.each(transportTypes)("API over %s Router", (transportType) => {
    describe("config operations", () => {
      const harness = getTestHarness();

      beforeAll(async () => {
        await harness.initialize(transportType);
      });

      afterAll(async () => {
        await harness.shutdown();
      });

      it("can write config and read it back", async () => {
        const config = {
          permissions: {
            default: {
              _type: "default-allow",
              block: ["dangerous-tools"],
            },
            consumers: {
              guests: {
                _type: "default-block",
                allow: ["safe-tools"],
                consumerGroupKey: "guest-group",
              },
            },
          },
          toolGroups: [
            {
              name: "safe-tools",
              services: {
                "echo-service": ["echo"],
              },
            },
            {
              name: "dangerous-tools",
              services: {
                "calculator-service": ["powerOfTwo"],
              },
            },
          ],
        };

        // PATCH config
        const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(config),
        });
        expect(patchResponse.status).toBe(200);

        // GET should return the config
        const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`);
        expect(getResponse.status).toBe(200);

        const responseData = await getResponse.json();
        const parsedConfig = parse(responseData.yaml);

        expect(parsedConfig.permissions).toEqual({
          default: {
            _type: "default-allow",
            block: ["dangerous-tools"],
          },
          consumers: {
            guests: {
              _type: "default-block",
              allow: ["safe-tools"],
              consumerGroupKey: "guest-group",
            },
          },
          clientNames: {},
        });
      });

      it("round-trips clientNames-level permissions through app-config", async () => {
        const config = {
          permissions: {
            default: { _type: "default-allow", block: [] },
            consumers: {},
            clientNames: {
              cursor: {
                _type: "default-block",
                allow: ["safe-tools"],
              },
            },
          },
          toolGroups: [
            {
              name: "safe-tools",
              services: { "echo-service": ["echo"] },
            },
          ],
        };

        const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(config),
        });
        expect(patchResponse.status).toBe(200);

        const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`);
        expect(getResponse.status).toBe(200);

        const responseData = await getResponse.json();
        const parsedConfig = parse(responseData.yaml);

        expect(parsedConfig.permissions.clientNames).toEqual({
          cursor: { _type: "default-block", allow: ["safe-tools"] },
        });
      });

      it("handles full config with all fields correctly", async () => {
        const fullConfig = {
          permissions: {
            default: {
              _type: "default-allow",
              block: [],
            },
            consumers: {
              testers: {
                _type: "default-block",
                allow: ["test-tools"],
                consumerGroupKey: "test-group",
              },
            },
          },
          toolGroups: [
            {
              name: "test-tools",
              services: {
                "echo-service": ["echo"],
                "calculator-service": ["add"],
              },
            },
          ],
          auth: {
            enabled: false,
          },
          toolExtensions: {
            services: {
              "echo-service": {
                echo: {
                  childTools: [
                    {
                      name: "echo_custom",
                      description: {
                        action: "rewrite",
                        text: "Custom echo tool for testing",
                      },
                      overrideParams: {},
                    },
                  ],
                },
              },
            },
          },
        };

        // PATCH with full config
        const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(fullConfig),
        });
        expect(patchResponse.status).toBe(200);

        // GET and verify all fields are preserved
        const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`);
        expect(getResponse.status).toBe(200);

        const responseData = await getResponse.json();
        const parsedConfig = parse(responseData.yaml);

        expect(parsedConfig.permissions).toEqual({
          default: {
            _type: "default-allow",
            block: [],
          },
          consumers: {
            testers: {
              _type: "default-block",
              allow: ["test-tools"],
              consumerGroupKey: "test-group",
            },
          },
          clientNames: {},
        });

        expect(parsedConfig.toolGroups).toEqual(fullConfig.toolGroups);
        expect(parsedConfig.auth).toEqual(fullConfig.auth);
        expect(parsedConfig.toolExtensions).toEqual(fullConfig.toolExtensions);
      });

      it("preserves _type discriminating tags", async () => {
        const config = {
          permissions: {
            default: {
              _type: "default-allow",
              block: ["restricted"],
            },
            consumers: {
              "api-users": {
                _type: "default-block",
                allow: ["public-tools"],
                consumerGroupKey: "api-group",
              },
            },
          },
          toolGroups: [
            {
              name: "public-tools",
              services: {
                "echo-service": ["echo"],
              },
            },
            {
              name: "restricted",
              services: {
                "calculator-service": ["powerOfTwo"],
              },
            },
          ],
        };

        // PATCH config
        const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(config),
        });
        expect(patchResponse.status).toBe(200);

        // GET should preserve _type fields
        const getResponse = await fetch(`${MCPX_BASE_URL}/app-config`);
        expect(getResponse.status).toBe(200);

        const responseData = await getResponse.json();
        const parsedConfig = parse(responseData.yaml);

        // Verify _type fields are preserved
        expect(parsedConfig.permissions.default).toHaveProperty(
          "_type",
          "default-allow",
        );
        expect(parsedConfig.permissions.consumers["api-users"]).toHaveProperty(
          "_type",
          "default-block",
        );
      });
    });
  });

  describe.each(transportTypes)(
    "Validations over %s Router",
    (transportType) => {
      describe("config updates", () => {
        let testHarness: TestHarness;

        beforeAll(async () => {
          testHarness = getTestHarness();
          await testHarness.initialize(transportType);
        });

        afterAll(async () => {
          await testHarness.shutdown();
        });

        it("rejects config with invalid schema", async () => {
          const invalidConfig = {
            permissions: {
              default: {
                _type: "default-allow",
                block: "*", // This is a mistake! must be an array
              },
              consumers: {},
            },
            toolGroups: [
              {
                name: "foo",
                services: ["echoService"], // This is a mistake! must be an object with service names as keys
              },
            ],
          };

          const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(invalidConfig),
          });
          expect(patchResponse.status).toBe(400);
        });

        it("rejects config with valid schema but not-workable values", async () => {
          const invalidConfig: Config = appConfigSchema.parse({
            permissions: {
              default: {
                _type: "default-allow",
                block: ["foo-with-typo"],
              },
              consumers: {},
            },
            toolGroups: [
              {
                name: "foo",
                services: {
                  "echo-service": ["echo"],
                },
              },
            ],
            auth: { enabled: false },
            toolExtensions: { services: {} },
            targetServerAttributes: {},
          });

          const patchResponse = await fetch(`${MCPX_BASE_URL}/app-config`, {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(invalidConfig),
          });
          expect(patchResponse.status).toBe(400);

          const errorData = await patchResponse.json();
          expect(errorData.error).toContain("Config update rejected");
        });
      });
    },
  );
});
