import { noOpLogger } from "@mcpx/toolkit-core/logging";
import { DEFAULT_CONFIG } from "../config.js";
import { Config } from "../model/config/config.js";
import { PermissionManager } from "./permissions.js";

describe("PermissionManager#hasPermission", () => {
  describe("when not initialized", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-allow",
          block: [],
        },
        consumers: {},
        clientNames: {},
      },
      toolGroups: [],
    };
    const permissionManager = new PermissionManager(noOpLogger);
    // Not calling commitConfig to simulate not initialized state
    it("throws an error", async () => {
      await permissionManager.prepareConfig(config);
      expect(() =>
        permissionManager.hasPermission({
          serviceName: "what",
          toolName: "ever",
          consumerTag: "duh",
        }),
      ).toThrow();
    });
  });
  describe("when all is blocked", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-block",
          allow: [],
        },
        consumers: {},
        clientNames: {},
      },
      toolGroups: [],
    };

    const permissionManager = new PermissionManager(noOpLogger);

    it("returns false", async () => {
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "what",
          toolName: "ever",
          consumerTag: "duh",
        }),
      ).toBe(false);
    });
  });

  describe("when all is allowed", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-allow",
          block: [],
        },
        consumers: {},
        clientNames: {},
      },
      toolGroups: [],
    };

    const permissionManager = new PermissionManager(noOpLogger);

    it("returns true", async () => {
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "what",
          toolName: "ever",
          consumerTag: "duh",
        }),
      ).toBe(true);
    });
  });

  describe("when a consumer is allowed", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-block",
          allow: [],
        },
        consumers: {
          developers: {
            _type: "default-allow",
            block: [],
          },
        },
        clientNames: {},
      },
      toolGroups: [],
    };

    const permissionManager = new PermissionManager(noOpLogger);

    it("returns true", async () => {
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "what",
          toolName: "ever",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });
  });

  describe("when a consumer is blocked", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-allow",
          block: [],
        },
        consumers: {
          developers: {
            _type: "default-block",
            allow: [],
          },
        },
        clientNames: {},
      },
      toolGroups: [],
    };

    const permissionManager = new PermissionManager(noOpLogger);

    it("returns false", async () => {
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "what",
          toolName: "ever",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });
  });

  describe("when a consumer is allowed via profile", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-block",
          allow: [],
        },
        consumers: {
          developers: {
            _type: "default-block",
            allow: ["read"],
          },
        },
        clientNames: {},
      },
      toolGroups: [
        { name: "read", services: { slack: ["read-message"] } },
        { name: "write", services: { slack: ["send-message"] } },
      ],
    };

    it("returns true for tool included in allowed profile", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "read-message",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });

    it("returns false for tool from another profile not included in allowed profiles", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "send-message",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });

    it("returns false for any other tool", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "another-tool",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });

    it("returns false for any other service", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "another-service",
          toolName: "another-tool",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });

    it("returns false for any other consumer", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "read-message",
          consumerTag: "another-consumer",
        }),
      ).toBe(false);
    });
  });

  describe("when a consumer is blocked via profile", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-allow",
          block: [],
        },
        consumers: {
          developers: {
            _type: "default-allow",
            block: ["write"],
          },
        },
        clientNames: {},
      },
      toolGroups: [
        { name: "read", services: { slack: ["read-message"] } },
        { name: "write", services: { slack: ["send-message"] } },
      ],
    };

    it("returns true for any not-blocked tool", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "read-message",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });

    it("returns false for blocked tool", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "send-message",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });

    it("returns true for any other tool not mentioned", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "another-tool",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });

    it("returns true for any other service", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "another-service",
          toolName: "another-tool",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });

    it("returns true for any other consumer", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "read-message",
          consumerTag: "another-consumer",
        }),
      ).toBe(true);
    });
  });

  describe("when a consumer is allowed all", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-block",
          allow: [],
        },
        consumers: {
          developers: {
            _type: "default-block",
            allow: ["all-slack"],
          },
        },
        clientNames: {},
      },
      toolGroups: [{ name: "all-slack", services: { slack: "*" } }],
    };

    it("returns true for any tool from allowed profile", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "whatever-tool",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });

    it("returns false for any other tool from another service", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "another-service",
          toolName: "whatever-tool",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });

    it("returns false for any other tool from another consumer", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();
      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "whatever-tool",
          consumerTag: "another-consumer",
        }),
      ).toBe(false);
    });
  });

  describe("when a consumer is blocked all", () => {
    const config: Config = {
      ...DEFAULT_CONFIG,
      permissions: {
        default: {
          _type: "default-allow",
          block: [],
        },
        consumers: {
          developers: {
            _type: "default-allow",
            block: ["all-slack"],
          },
        },
        clientNames: {},
      },
      toolGroups: [{ name: "all-slack", services: { slack: "*" } }],
    };

    it("returns false for any tool from blocked profile", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();

      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "whatever-tool",
          consumerTag: "developers",
        }),
      ).toBe(false);
    });

    it("returns true for any other tool from another service", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();

      expect(
        permissionManager.hasPermission({
          serviceName: "another-service",
          toolName: "whatever-tool",
          consumerTag: "developers",
        }),
      ).toBe(true);
    });

    it("returns true for any other tool from another consumer", async () => {
      const permissionManager = new PermissionManager(noOpLogger);
      await permissionManager.prepareConfig(config);
      await permissionManager.commitConfig();

      expect(
        permissionManager.hasPermission({
          serviceName: "slack",
          toolName: "whatever-tool",
          consumerTag: "another-consumer",
        }),
      ).toBe(true);
    });
  });
  describe("when applying multiple tool groups", () => {
    describe("merging allow and allow", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-block",
            allow: [],
          },
          consumers: {
            developers: {
              _type: "default-block",
              allow: ["read", "write"],
            },
          },
          clientNames: {},
        },
        toolGroups: [
          { name: "read", services: { slack: ["read-message"] } },
          {
            name: "write",
            services: { slack: ["read-message", "send-message"] },
          },
        ],
      };

      it("returns true for all tools in both groups", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();

        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            consumerTag: "developers",
          }),
        ).toBe(true);
        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "send-message",
            consumerTag: "developers",
          }),
        ).toBe(true);
      });
    });
    describe("merging block and block", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-allow",
            block: [],
          },
          consumers: {
            developers: {
              _type: "default-allow",
              block: ["read", "write"],
            },
          },
          clientNames: {},
        },
        toolGroups: [
          { name: "read", services: { slack: ["read-message"] } },
          { name: "write", services: { slack: ["send-message"] } },
        ],
      };

      it("returns false for all tools in both groups", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();

        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            consumerTag: "developers",
          }),
        ).toBe(false);
        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "send-message",
            consumerTag: "developers",
          }),
        ).toBe(false);
      });
    });
    describe("merging allow_all and allow", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-block",
            allow: [],
          },
          consumers: {
            developers: {
              _type: "default-block",
              allow: ["all-slack", "basic-level"],
            },
          },
          clientNames: {},
        },
        toolGroups: [
          {
            name: "basic-level",
            services: { slack: ["read-message"], linear: ["read-ticket"] },
          },
          { name: "all-slack", services: { slack: "*" } },
        ],
      };

      it("returns true for all tools from the allow_all group", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          // Assert on the specified tool from the service that was also allow_all
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            consumerTag: "developers",
          }),
        ).toBe(true);
        expect(
          // Assert on whatever tool from that service
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "whatever-tool",
            consumerTag: "developers",
          }),
        ).toBe(true);
      });
      it("returns true for specific tools from the allow group", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "linear",
            toolName: "read-ticket",
            consumerTag: "developers",
          }),
        ).toBe(true);
      });
      it("returns false for any other tool", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "another-service",
            toolName: "another-tool",
            consumerTag: "developers",
          }),
        ).toBe(false);
      });
    });
    describe("merging block_all and block", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-allow",
            block: [],
          },
          consumers: {
            developers: {
              _type: "default-allow",
              block: ["basic-level", "all-slack"],
            },
          },
          clientNames: {},
        },
        toolGroups: [
          {
            name: "basic-level",
            services: { slack: ["read-message"], linear: ["read-ticket"] },
          },
          { name: "all-slack", services: { slack: "*" } },
        ],
      };

      it("returns false for all tools from the block_all group", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          // Assert on the specified tool from the service that was also block_all
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            consumerTag: "developers",
          }),
        ).toBe(false);
        expect(
          // Assert on whatever tool from that service
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "whatever-tool",
            consumerTag: "developers",
          }),
        ).toBe(false);
      });
      it("returns false for specific tools from the block group", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "linear",
            toolName: "read-ticket",
            consumerTag: "developers",
          }),
        ).toBe(false);
      });
      it("returns true for any other tool", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "another-service",
            toolName: "another-tool",
            consumerTag: "developers",
          }),
        ).toBe(true);
      });
    });
  });

  describe("clientName resolution", () => {
    describe("when only clientName is provided and matches a clientNames entry", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-allow",
            block: [],
          },
          consumers: {},
          clientNames: {
            cursor: {
              _type: "default-block",
              allow: [],
            },
          },
        },
        toolGroups: [],
      };

      it("uses the clientNames entry, not the default", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            clientName: "cursor",
          }),
        ).toBe(false);
      });
    });

    describe("when both consumerTag and clientName have entries", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-block",
            allow: [],
          },
          consumers: {
            "cursor-dev": {
              _type: "default-allow",
              block: [],
            },
          },
          clientNames: {
            cursor: {
              _type: "default-block",
              allow: [],
            },
          },
        },
        toolGroups: [],
      };

      it("consumerTag wins over clientName (more specific)", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            clientName: "cursor",
            consumerTag: "cursor-dev",
          }),
        ).toBe(true);
      });
    });

    describe("when consumerTag has no entry but clientName does", () => {
      const config: Config = {
        ...DEFAULT_CONFIG,
        permissions: {
          default: {
            _type: "default-block",
            allow: [],
          },
          consumers: {},
          clientNames: {
            cursor: {
              _type: "default-allow",
              block: [],
            },
          },
        },
        toolGroups: [],
      };

      it("falls through to clientName", async () => {
        const permissionManager = new PermissionManager(noOpLogger);
        await permissionManager.prepareConfig(config);
        await permissionManager.commitConfig();
        expect(
          permissionManager.hasPermission({
            serviceName: "slack",
            toolName: "read-message",
            clientName: "cursor",
            consumerTag: "unknown-tag",
          }),
        ).toBe(true);
      });
    });
  });
});
