/**
 * Fast deterministic lifecycle soak for the in-memory realtime/media services.
 * It intentionally uses fake clocks and direct service APIs: no sockets, sleeps,
 * filesystem writes, or environment-specific infrastructure.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import type { ActorPoseSnapshot, ResumeRequest } from "../../../src/realtime/types.ts";
import type {
  ScreenShareBinding,
  ScreenShareCredential,
  ScreenShareCreateRequest,
  ScreenShareJoinRequest,
} from "../../../src/media/signalingTypes.ts";
import type { IceConfigRequest } from "../../../src/media/iceTypes.ts";
import type { IceConfig } from "../config.ts";
import { createIceCredentialProvider, createMediaSignalService } from "../media/index.ts";
import { createRealtimeService } from "../realtime/index.ts";

const MEDIA_SESSIONS = 32;
const VIEWERS_PER_SHARE = 3;
const REALTIME_SESSIONS = 128;

describe("bounded in-memory service soak", () => {
  it("rotates media grants, releases listeners/queues, and drains every session", () => {
    let now = 1_000_000;
    const service = createMediaSignalService({
      now: () => now,
      maximumSessions: MEDIA_SESSIONS,
      maximumParticipantsPerSession: VIEWERS_PER_SHARE + 1,
      maximumQueuedMessagesPerParticipant: 8,
      maximumSignalsPerWindow: 16,
      grantTtlMs: 90_000,
    });
    const credentials: Array<{ credential: ScreenShareCredential; subject: string }> = [];
    const unsubscribedDeliveries: number[] = [];
    const presenters: Array<{
      binding: ScreenShareBinding;
      subject: string;
      credential: ScreenShareCredential<"presenter">;
    }> = [];

    for (let sessionIndex = 0; sessionIndex < MEDIA_SESSIONS; sessionIndex += 1) {
      const binding = mediaBinding(sessionIndex);
      const presenterSubject = `presenter-subject-${sessionIndex}`;
      const created = service.join(mediaCreate(binding, sessionIndex), presenterSubject);
      assert.ok(created.ok && created.value.type === "screen-share-create-grant");
      if (!created.ok || created.value.type !== "screen-share-create-grant") continue;
      let presenterCredential = created.value.grant.credential;
      const viewers: Array<{ subject: string; credential: ScreenShareCredential; sequence: number }> = [];

      for (let viewerIndex = 0; viewerIndex < VIEWERS_PER_SHARE; viewerIndex += 1) {
        const subject = `viewer-subject-${sessionIndex}-${viewerIndex}`;
        const joined = service.join(mediaJoin(binding, sessionIndex, viewerIndex), subject);
        assert.ok(joined.ok && joined.value.type === "screen-share-join-grant");
        if (!joined.ok || joined.value.type !== "screen-share-join-grant") continue;
        viewers.push({ subject, credential: joined.value.grant.credential, sequence: joined.value.sequence });
      }
      assert.equal(viewers.length, VIEWERS_PER_SHARE);

      const presenterResume = service.resume({
        type: "screen-share-resume-request", protocolVersion: 1,
        requestId: `presenter-resume-${sessionIndex}`, sequence: 2, timestampMs: now,
        binding, credential: presenterCredential, lastReceivedSequence: created.value.sequence,
      }, presenterSubject);
      assert.equal(presenterResume.ok, true);
      if (!presenterResume.ok) continue;
      assert.notEqual(presenterResume.value.grant.credential.grantToken, presenterCredential.grantToken);
      assert.equal(presenterResume.value.grant.credential.role, "presenter");
      if (presenterResume.value.grant.credential.role !== "presenter") continue;
      presenterCredential = presenterResume.value.grant.credential as ScreenShareCredential<"presenter">;
      credentials.push({ credential: presenterCredential, subject: presenterSubject });

      for (const [viewerIndex, viewer] of viewers.entries()) {
        const resumed = service.resume({
          type: "screen-share-resume-request", protocolVersion: 1,
          requestId: `viewer-resume-${sessionIndex}-${viewerIndex}`, sequence: 2, timestampMs: now,
          binding, credential: viewer.credential, lastReceivedSequence: viewer.sequence,
        }, viewer.subject);
        assert.equal(resumed.ok, true);
        if (!resumed.ok) continue;
        const rotated = resumed.value.grant.credential;
        credentials.push({ credential: rotated, subject: viewer.subject });
        let deliveries = 0;
        const subscription = service.subscribe(rotated, viewer.subject, () => { deliveries += 1; });
        assert.equal(subscription.ok, true);
        if (!subscription.ok) continue;
        subscription.value();
        const before = deliveries;
        const offered = service.signal({
          type: "screen-share-signal-request", protocolVersion: 1, sequence: 3 + viewerIndex, timestampMs: now,
          binding, credential: presenterCredential, targetParticipantId: rotated.participantId,
          signal: { kind: "sdp", descriptionType: "offer", sdp: "v=0" },
        }, presenterSubject);
        assert.equal(offered.ok, true);
        unsubscribedDeliveries.push(deliveries - before);
      }

      presenters.push({ binding, subject: presenterSubject, credential: presenterCredential });
      now += 1;
    }

    assert.equal(service.sessionCount(), MEDIA_SESSIONS);
    assert.equal(credentials.length, MEDIA_SESSIONS * (VIEWERS_PER_SHARE + 1));
    for (const [index, presenter] of presenters.entries()) {
      const stopped = service.leave({
        type: "screen-share-stop-request", protocolVersion: 1,
        requestId: `stop-${index}`, sequence: 3 + VIEWERS_PER_SHARE,
        timestampMs: now, binding: presenter.binding,
        credential: presenter.credential, reason: "presenter-stopped",
      }, presenter.subject, false);
      assert.equal(stopped.ok, true);
    }

    assert.ok(unsubscribedDeliveries.every((count) => count === 0), "unsubscribed listeners receive no later offers");
    assert.equal(service.sessionCount(), 0);
    assert.ok(credentials.every(({ credential, subject }) =>
      !service.revalidate(credential, subject).ok
    ), "destroyed sessions retain no usable grant for its original owner");
  });

  it("rotates realtime tokens, disconnects listeners, rate-limits, and expires all sessions", () => {
    let now = 2_000_000;
    const service = createRealtimeService({
      now: () => now,
      maximumSessions: REALTIME_SESSIONS,
      maximumSessionsPerCell: REALTIME_SESSIONS,
      maximumPoseUpdatesPerWindow: 4,
      rateWindowMs: 10_000,
      disconnectGraceMs: 500,
      sessionTtlMs: 30_000,
    });
    const sessions: Array<{ sessionId: string; token: string; actorId: string; subject: string }> = [];
    let disconnectedDeliveries = 0;

    for (let index = 0; index < REALTIME_SESSIONS; index += 1) {
      const actorId = `soak-actor-${index}`;
      const subject = `soak-subject-${index}`;
      const joined = service.join({
        requestId: `soak-join-${index}`, actorId, subject, role: "member",
        interests: [{ kind: "floor", officeId: "lumbridge-hq", floorId: "level-1" }],
      });
      assert.equal(joined.ok, true);
      if (!joined.ok) continue;
      let token = joined.value.token;
      const connected = service.connect(joined.value.sessionId, token, () => { disconnectedDeliveries += 1; });
      assert.equal(connected.ok, true);
      if (connected.ok) connected.value.disconnect();

      for (let rotation = 0; rotation < 3; rotation += 1) {
        const request: ResumeRequest = {
          type: "resume-request", protocolVersion: 1,
          requestId: `soak-resume-${index}-${rotation}`,
          sessionId: joined.value.sessionId, resumeToken: token,
          serverEpoch: joined.value.grant.serverEpoch, lastReceivedSequence: 0,
          interests: joined.value.grant.interests,
        };
        const resumed = service.resume(request, subject);
        assert.equal(resumed.ok, true);
        if (!resumed.ok) break;
        assert.notEqual(resumed.value.resumeToken, token);
        token = resumed.value.resumeToken;
      }
      sessions.push({ sessionId: joined.value.sessionId, token, actorId, subject });
    }

    assert.equal(sessions.length, REALTIME_SESSIONS);
    assert.equal(service.sessionCount(), REALTIME_SESSIONS);
    const publisher = sessions[0] as (typeof sessions)[number];
    for (let sequence = 1; sequence <= 5; sequence += 1) {
      now += 100;
      const result = service.submitPose(
        publisher.sessionId,
        publisher.token,
        actorPose(publisher.actorId, sequence, now, sequence / 100),
      );
      assert.equal(result.ok, sequence <= 4);
      if (sequence === 5 && !result.ok) assert.equal(result.code, "rate-limited");
    }
    assert.equal(disconnectedDeliveries, 0, "disconnected realtime listeners receive no broadcast");

    for (let index = 0; index < sessions.length / 2; index += 1) {
      const session = sessions[index] as (typeof sessions)[number];
      assert.equal(service.leave(session.sessionId, session.token).ok, true);
    }
    assert.equal(service.sessionCount(), REALTIME_SESSIONS / 2);
    now += 501;
    assert.equal(service.cleanup(), REALTIME_SESSIONS / 2);
    assert.equal(service.sessionCount(), 0);
    assert.ok(sessions.every((session) => !service.authenticate(session.sessionId, session.token).ok));
  });

  it("bounds ICE issuance across subject and address churn without retaining credentials", () => {
    let now = 3_000_000;
    const provider = createIceCredentialProvider(iceConfig(), { now: () => now });
    const credential: ScreenShareCredential = {
      sessionId: "soak-media-session", participantId: "soak-participant",
      role: "presenter", grantToken: "soak-private-grant-token",
    };
    const binding = mediaBinding(999);
    const request: IceConfigRequest = {
      type: "ice-config-request", protocolVersion: 1, requestId: "soak-ice",
      binding, credential,
    };
    for (let attempt = 1; attempt <= 5; attempt += 1) {
      const result = provider.issue(request, {
        subject: "same-subject",
        trustedIp: `203.0.113.${attempt}`,
      }, now + 90_000);
      assert.equal(result.ok, attempt <= 4, "subject limit survives address rotation");
    }
    now += 10_000;
    for (let attempt = 1; attempt <= 5; attempt += 1) {
      const result = provider.issue(request, {
        subject: `rotating-subject-${attempt}`,
        trustedIp: "198.51.100.10",
      }, now + 90_000);
      assert.equal(result.ok, attempt <= 4, "address limit survives subject rotation");
    }
  });
});

function mediaBinding(index: number): ScreenShareBinding {
  return { officeId: "soak-office", levelId: "level-1", roomId: "room", screenId: `screen-${index}` };
}

function mediaCreate(binding: ScreenShareBinding, index: number): ScreenShareCreateRequest {
  return {
    type: "screen-share-create-request", protocolVersion: 1, requestId: `create-${index}`,
    sequence: 1, timestampMs: 1_000_000, binding, role: "presenter",
  };
}

function mediaJoin(binding: ScreenShareBinding, session: number, viewer: number): ScreenShareJoinRequest {
  return {
    type: "screen-share-join-request", protocolVersion: 1, requestId: `join-${session}-${viewer}`,
    sequence: 1, timestampMs: 1_000_000, binding, role: "viewer", viewerOptIn: true,
  };
}

function actorPose(actorId: string, sequence: number, timestampMs: number, xM: number): ActorPoseSnapshot {
  return {
    entity: "actor", actorId, kind: "humanoid", sequence, timestampMs,
    pose: {
      space: "local", cell: { kind: "floor", officeId: "lumbridge-hq", floorId: "level-1" },
      xM, yM: 0, zM: 0, headingDeg: 0, pitchDeg: 0,
    },
    velocity: { xMps: 0.1, yMps: 0, zMps: 0, yawDegPerSec: 0 },
  };
}

function iceConfig(): IceConfig {
  return {
    configured: true,
    urls: ["turn:relay.example.test:3478"],
    sharedSecret: "soak-turn-shared-secret-with-at-least-thirty-two-bytes",
    credentialTtlSeconds: 60,
    rateAttempts: 4,
    rateWindowSeconds: 10,
  };
}
