import { PermissionKey, SSE_EVENT_TYPES, SystemRole } from "@capakraken/shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
  cancelPendingEvents,
  eventBus,
  permissionAudience,
  resourceAudience,
  type SseEvent,
  userAudience,
} from "../sse/event-bus.js";
import { deriveUserSseSubscription } from "../sse/subscription-policy.js";

vi.mock("ioredis", () => {
  const RedisMock = vi.fn().mockImplementation(() => ({
    on: vi.fn(),
    subscribe: vi.fn().mockResolvedValue(undefined),
    publish: vi.fn().mockImplementation(() => {
      throw new Error("Redis unavailable (test)");
    }),
  }));
  return { Redis: RedisMock };
});

describe("sse subscription policy", () => {
  let consoleWarnSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    vi.useFakeTimers();
    consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
  });

  afterEach(() => {
    cancelPendingEvents();
    consoleWarnSpy.mockRestore();
    vi.useRealTimers();
  });

  it("derives canonical user, resource, and permission audiences server-side", () => {
    const subscription = deriveUserSseSubscription({
      userId: "user_1",
      systemRole: SystemRole.USER,
      resourceId: "res_1",
      permissionOverrides: {
        granted: [PermissionKey.VIEW_PLANNING, PermissionKey.MANAGE_ALLOCATIONS],
      },
    });

    expect(subscription.includeUnscoped).toBe(false);
    expect(subscription.permissions).toEqual(new Set([
      PermissionKey.VIEW_PLANNING,
      PermissionKey.MANAGE_ALLOCATIONS,
    ]));
    expect(subscription.audiences).toEqual([
      permissionAudience(PermissionKey.MANAGE_ALLOCATIONS),
      permissionAudience(PermissionKey.VIEW_PLANNING),
      resourceAudience("res_1"),
      userAudience("user_1"),
    ]);
  });

  it("does not deliver planning events to a standard user without the matching audience", () => {
    const received: SseEvent[] = [];
    const unsubscribe = eventBus.subscribe(
      (event) => {
        received.push(event);
      },
      deriveUserSseSubscription({
        userId: "user_1",
        systemRole: SystemRole.USER,
        resourceId: "res_self",
      }),
    );

    eventBus.emit(
      SSE_EVENT_TYPES.ALLOCATION_UPDATED,
      { id: "allocation_1", resourceId: "res_other" },
      [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS), resourceAudience("res_other")],
    );

    vi.advanceTimersByTime(50);

    expect(received).toHaveLength(0);
    unsubscribe();
  });

  it("does not leak personal notifications to a manager subscribed with elevated permissions", () => {
    const received: SseEvent[] = [];
    const unsubscribe = eventBus.subscribe(
      (event) => {
        received.push(event);
      },
      deriveUserSseSubscription({
        userId: "manager_1",
        systemRole: SystemRole.MANAGER,
      }),
    );

    eventBus.emit(
      SSE_EVENT_TYPES.NOTIFICATION_CREATED,
      { notificationId: "notification_1", userId: "user_2" },
      [userAudience("user_2")],
    );

    vi.advanceTimersByTime(50);

    expect(received).toHaveLength(0);
    unsubscribe();
  });

  it("delivers a multi-audience planning event only to matching manager and affected resource subscribers", () => {
    const managerReceived: SseEvent[] = [];
    const affectedUserReceived: SseEvent[] = [];
    const unrelatedUserReceived: SseEvent[] = [];

    const unsubscribeManager = eventBus.subscribe(
      (event) => {
        managerReceived.push(event);
      },
      deriveUserSseSubscription({
        userId: "manager_1",
        systemRole: SystemRole.MANAGER,
      }),
    );

    const unsubscribeAffectedUser = eventBus.subscribe(
      (event) => {
        affectedUserReceived.push(event);
      },
      deriveUserSseSubscription({
        userId: "user_1",
        systemRole: SystemRole.USER,
        resourceId: "res_1",
      }),
    );

    const unsubscribeUnrelatedUser = eventBus.subscribe(
      (event) => {
        unrelatedUserReceived.push(event);
      },
      deriveUserSseSubscription({
        userId: "user_2",
        systemRole: SystemRole.USER,
        resourceId: "res_2",
      }),
    );

    eventBus.emit(
      SSE_EVENT_TYPES.ALLOCATION_UPDATED,
      { id: "allocation_1", resourceId: "res_1" },
      [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS), resourceAudience("res_1")],
    );

    vi.advanceTimersByTime(50);

    expect(managerReceived).toHaveLength(1);
    expect(affectedUserReceived).toHaveLength(1);
    expect(unrelatedUserReceived).toHaveLength(0);

    unsubscribeManager();
    unsubscribeAffectedUser();
    unsubscribeUnrelatedUser();
  });
});
