/** In-memory WebRTC signaling only: no tracks, media bytes, locators, or persistence. */

import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
import {
  MAX_SCREEN_SHARE_PARTICIPANTS,
  SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
  type ScreenShareAccessGrant,
  type ScreenShareBinding,
  type ScreenShareCreateGrant,
  type ScreenShareCreateRequest,
  type ScreenShareCredential,
  type ScreenShareJoinGrant,
  type ScreenShareJoinRequest,
  type ScreenShareParticipant,
  type ScreenSharePeerMessage,
  type ScreenShareResumeGrant,
  type ScreenShareResumeRequest,
  type ScreenShareRevokeRequest,
  type ScreenShareRevoked,
  type ScreenShareRole,
  type ScreenShareSignalRelay,
  type ScreenShareSignalRequest,
  type ScreenShareStopRequest,
  type ScreenShareStopped,
} from "../../../src/media/signalingTypes.ts";

export type MediaSignalFailureCode =
  | "invalid" | "unauthorized" | "expired" | "conflict" | "capacity" | "rate-limited";
export type MediaSignalResult<T> =
  | { ok: true; value: T }
  | { ok: false; code: MediaSignalFailureCode; message: string };

export interface MediaSignalServiceOptions {
  now?: () => number;
  grantTtlMs?: number;
  maximumSessions?: number;
  maximumParticipantsPerSession?: number;
  maximumQueuedMessagesPerParticipant?: number;
  maximumSignalsPerWindow?: number;
  rateWindowMs?: number;
}

export interface MediaSignalService {
  join(request: ScreenShareCreateRequest | ScreenShareJoinRequest, subject: string): MediaSignalResult<ScreenShareCreateGrant | ScreenShareJoinGrant>;
  resume(request: ScreenShareResumeRequest, subject: string): MediaSignalResult<ScreenShareResumeGrant>;
  signal(request: ScreenShareSignalRequest, subject: string): MediaSignalResult<null>;
  leave(request: ScreenShareStopRequest | ScreenShareRevokeRequest, subject: string, admin: boolean): MediaSignalResult<null>;
  subscribe(credential: ScreenShareCredential, subject: string, listener: (message: ScreenSharePeerMessage) => void): MediaSignalResult<() => void>;
  authorizeIce(credential: ScreenShareCredential, binding: ScreenShareBinding, subject: string): MediaSignalResult<{ expiresAtMs: number }>;
  revalidate(credential: ScreenShareCredential, subject: string): MediaSignalResult<null>;
  revokeSubject(subject: string): number;
  cleanup(): number;
  sessionCount(): number;
}

interface Participant {
  id: string;
  subject: string;
  role: ScreenShareRole;
  tokenHash: Buffer;
  issuedAt: number;
  expiresAt: number;
  lastClientSequence: number;
  rateStartedAt: number;
  rateCount: number;
  queue: ScreenSharePeerMessage[];
  queueOverflowed: boolean;
  listeners: Set<(message: ScreenSharePeerMessage) => void>;
}

interface Session {
  id: string;
  binding: ScreenShareBinding;
  sequence: number;
  participants: Map<string, Participant>;
}

export function createMediaSignalService(options: MediaSignalServiceOptions = {}): MediaSignalService {
  const now = options.now ?? Date.now;
  const ttl = positive(options.grantTtlMs, 90_000);
  const maximumSessions = count(options.maximumSessions, 128);
  const maximumParticipants = Math.min(
    MAX_SCREEN_SHARE_PARTICIPANTS,
    count(options.maximumParticipantsPerSession, MAX_SCREEN_SHARE_PARTICIPANTS),
  );
  const maximumQueued = count(options.maximumQueuedMessagesPerParticipant, 64);
  const maximumSignals = count(options.maximumSignalsPerWindow, 120);
  const rateWindowMs = positive(options.rateWindowMs, 10_000);
  const sessions = new Map<string, Session>();

  function peerList(session: Session, except: string): ScreenShareParticipant[] {
    return [...session.participants.values()]
      .filter((participant) => participant.id !== except)
      .map(({ id: participantId, role }) => ({ participantId, role }));
  }

  function nextSequence(session: Session): number {
    session.sequence = Math.min(Number.MAX_SAFE_INTEGER, session.sequence + 1);
    return session.sequence;
  }

  function stopNotice(session: Session, reason: ScreenShareStopped["reason"]): ScreenShareStopped {
    return { type: "screen-share-stopped", protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
      sequence: nextSequence(session), timestampMs: now(), sessionId: session.id,
      binding: session.binding, reason };
  }

  function credential(session: Session, participant: Participant, token: string): ScreenShareCredential {
    return { sessionId: session.id, participantId: participant.id, role: participant.role, grantToken: token };
  }

  function grant(session: Session, participant: Participant, token: string): ScreenShareAccessGrant {
    return { credential: credential(session, participant, token), issuedAtMs: participant.issuedAt, expiresAtMs: participant.expiresAt };
  }

  function emit(participant: Participant, message: ScreenSharePeerMessage): void {
    if (participant.listeners.size > 0) {
      for (const listener of participant.listeners) listener(message);
      return;
    }
    if (participant.queue.length >= maximumQueued) {
      // Resume is deliberately discontinuous and carries a complete peer
      // snapshot, so dropping the oldest relay is safer than letting a sender
      // evict another participant by filling its queue.
      participant.queue.shift();
      participant.queueOverflowed = true;
    }
    participant.queue.push(message);
  }

  function participantSnapshot(session: Session): void {
    const timestampMs = now();
    for (const participant of [...session.participants.values()]) {
      emit(participant, {
        type: "screen-share-participants",
        protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
        sequence: nextSequence(session),
        timestampMs,
        sessionId: session.id,
        binding: session.binding,
        participants: peerList(session, participant.id),
        leaseExpiresAtMs: participant.expiresAt,
      });
    }
  }

  function destroySession(session: Session, notice?: ScreenShareStopped | ScreenShareRevoked): void {
    // Terminal messages are best effort for connected peers. Queuing one and
    // then immediately destroying the queue would be dishonest, while routing
    // through `emit` on a full queue could recurse into participant removal.
    if (notice) {
      for (const participant of session.participants.values()) {
        for (const listener of participant.listeners) listener(notice);
      }
    }
    sessions.delete(session.id);
    for (const participant of session.participants.values()) {
      participant.tokenHash.fill(0);
      participant.queue.length = 0;
      participant.listeners.clear();
    }
    session.participants.clear();
  }

  function removeParticipant(
    session: Session,
    participant: Participant,
    reason: ScreenShareRevoked["reason"] = "viewer-left",
  ): void {
    const notice: ScreenShareRevoked = {
      type: "screen-share-revoked",
      protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
      sequence: nextSequence(session),
      timestampMs: now(),
      sessionId: session.id,
      binding: session.binding,
      scope: "participant",
      targetParticipantId: participant.id,
      reason,
      reconnectAllowed: false,
    };
    for (const listener of participant.listeners) listener(notice);
    session.participants.delete(participant.id);
    participant.tokenHash.fill(0);
    participant.queue.length = 0;
    participant.listeners.clear();
    participantSnapshot(session);
  }

  function authenticate(raw: ScreenShareCredential, subject: string): MediaSignalResult<{ session: Session; participant: Participant }> {
    const session = sessions.get(raw.sessionId);
    const participant = session?.participants.get(raw.participantId);
    if (!session || !participant || participant.subject !== subject || participant.role !== raw.role ||
        !tokenMatches(participant.tokenHash, raw.grantToken)) {
      return { ok: false, code: "unauthorized", message: "Media signaling credential is invalid." };
    }
    if (now() >= participant.expiresAt) {
      if (participant.role === "presenter") {
        destroySession(session, stopNotice(session, "grant-expired"));
      } else removeParticipant(session, participant, "authorization-revoked");
      return { ok: false, code: "expired", message: "Media signaling grant expired." };
    }
    return { ok: true, value: { session, participant } };
  }

  function validateRequest(
    session: Session,
    participant: Participant,
    binding: ScreenShareBinding,
    sequence: number,
  ): MediaSignalResult<null> {
    if (!sameBinding(session.binding, binding)) return { ok: false, code: "unauthorized", message: "Media binding does not match the grant." };
    if (!clientSequenceHasSuccessor(sequence) || sequence <= participant.lastClientSequence) {
      return { ok: false, code: "conflict", message: "Media client sequence is not monotonic." };
    }
    return { ok: true, value: null };
  }

  function newParticipant(subject: string, role: ScreenShareRole, clientSequence: number): { participant: Participant; token: string } {
    const token = opaque(32);
    const issuedAt = now();
    return { token, participant: {
      id: opaque(18), subject, role, tokenHash: hash(token), issuedAt, expiresAt: issuedAt + ttl,
      lastClientSequence: clientSequence, rateStartedAt: issuedAt, rateCount: 0, queue: [], listeners: new Set(),
      queueOverflowed: false,
    } };
  }

  function join(request: ScreenShareCreateRequest | ScreenShareJoinRequest, subject: string): MediaSignalResult<ScreenShareCreateGrant | ScreenShareJoinGrant> {
    if (!clientSequenceHasSuccessor(request.sequence)) {
      return { ok: false, code: "invalid", message: "Media client sequence cannot advance." };
    }
    cleanup();
    if (request.type === "screen-share-create-request") {
      if (sessions.size >= maximumSessions) return { ok: false, code: "capacity", message: "Media signaling is at capacity." };
      if ([...sessions.values()].some((candidate) => sameBinding(candidate.binding, request.binding))) {
        return { ok: false, code: "conflict", message: "That screen already has an active presenter." };
      }
      const session: Session = { id: opaque(18), binding: copyBinding(request.binding), sequence: 0, participants: new Map() };
      const made = newParticipant(subject, "presenter", request.sequence);
      session.participants.set(made.participant.id, made.participant);
      sessions.set(session.id, session);
      return { ok: true, value: {
        type: "screen-share-create-grant", protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
        requestId: request.requestId, sequence: nextSequence(session), timestampMs: now(), binding: session.binding,
        grant: grant(session, made.participant, made.token) as ScreenShareAccessGrant<"presenter">,
        nextClientSequence: request.sequence + 1, participants: [],
      } };
    }
    const session = [...sessions.values()].find((candidate) => sameBinding(candidate.binding, request.binding));
    if (!session) return { ok: false, code: "conflict", message: "That screen has no active presenter." };
    if (session.participants.size >= maximumParticipants) return { ok: false, code: "capacity", message: "Media screen is at capacity." };
    if ([...session.participants.values()].some((participant) => participant.subject === subject)) {
      return { ok: false, code: "conflict", message: "Member is already active on that screen." };
    }
    const made = newParticipant(subject, "viewer", request.sequence);
    const existing = peerList(session, made.participant.id);
    session.participants.set(made.participant.id, made.participant);
    participantSnapshot(session);
    return { ok: true, value: {
      type: "screen-share-join-grant", protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
      requestId: request.requestId, sequence: nextSequence(session), timestampMs: now(), binding: session.binding,
      grant: grant(session, made.participant, made.token) as ScreenShareAccessGrant<"viewer">,
      nextClientSequence: request.sequence + 1, participants: existing,
    } };
  }

  function resume(request: ScreenShareResumeRequest, subject: string): MediaSignalResult<ScreenShareResumeGrant> {
    const found = authenticate(request.credential, subject);
    if (!found.ok) return found;
    const { session, participant } = found.value;
    if (request.lastReceivedSequence > session.sequence) {
      return { ok: false, code: "conflict", message: "Media server sequence is ahead of this session." };
    }
    const valid = validateRequest(session, participant, request.binding, request.sequence);
    if (!valid.ok) return valid;
    participant.lastClientSequence = request.sequence;
    const token = opaque(32);
    participant.tokenHash.fill(0);
    participant.tokenHash = hash(token);
    participant.issuedAt = now();
    participant.expiresAt = now() + ttl;
    const continuous = !participant.queueOverflowed;
    const queued = participant.queue.filter((message) => message.sequence > request.lastReceivedSequence);
    const grantSequence = nextSequence(session);
    const resumedAt = now();
    // The resume grant is written before subscribe drains this queue. Rebase
    // retained messages onto fresh server sequences so a strict client cursor
    // can accept them after the grant instead of treating them as stale.
    participant.queue = queued.map((message) => ({
      ...message,
      sequence: nextSequence(session),
      timestampMs: resumedAt,
    }));
    participant.queueOverflowed = false;
    return { ok: true, value: {
      type: "screen-share-resume-grant", protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
      requestId: request.requestId, sequence: grantSequence, timestampMs: resumedAt, binding: session.binding,
      grant: grant(session, participant, token), nextClientSequence: request.sequence + 1,
      participants: peerList(session, participant.id), continuous,
    } };
  }

  function signal(request: ScreenShareSignalRequest, subject: string): MediaSignalResult<null> {
    const found = authenticate(request.credential, subject);
    if (!found.ok) return found;
    const { session, participant } = found.value;
    const valid = validateRequest(session, participant, request.binding, request.sequence);
    if (!valid.ok) return valid;
    if (now() - participant.rateStartedAt >= rateWindowMs) { participant.rateStartedAt = now(); participant.rateCount = 0; }
    if (++participant.rateCount > maximumSignals) return { ok: false, code: "rate-limited", message: "Media signaling rate exceeded." };
    const target = session.participants.get(request.targetParticipantId);
    if (!target || target.role === participant.role) return { ok: false, code: "unauthorized", message: "Media signaling target is not permitted." };
    if (request.signal.kind === "sdp") {
      const permitted = participant.role === "presenter" ? request.signal.descriptionType === "offer" : request.signal.descriptionType === "answer";
      if (!permitted) return { ok: false, code: "unauthorized", message: "Media SDP role is not permitted." };
    }
    participant.lastClientSequence = request.sequence;
    const relay: ScreenShareSignalRelay = {
      type: "screen-share-signal-relay", protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
      sequence: nextSequence(session), timestampMs: now(), sessionId: session.id, binding: session.binding,
      fromParticipantId: participant.id, targetParticipantId: target.id, signal: request.signal,
    };
    emit(target, relay);
    return { ok: true, value: null };
  }

  function leave(request: ScreenShareStopRequest | ScreenShareRevokeRequest, subject: string, admin: boolean): MediaSignalResult<null> {
    const found = authenticate(request.credential, subject);
    if (!found.ok) return found;
    const { session, participant } = found.value;
    const valid = validateRequest(session, participant, request.binding, request.sequence);
    if (!valid.ok) return valid;
    if (request.type === "screen-share-stop-request") {
      if (participant.role !== "presenter") return { ok: false, code: "unauthorized", message: "Only a presenter may stop a share." };
      participant.lastClientSequence = request.sequence;
      destroySession(session, stopNotice(session, request.reason));
      return { ok: true, value: null };
    }
    if (request.reason === "moderator-action" && !admin) return { ok: false, code: "unauthorized", message: "Moderator action requires an administrator." };
    if (request.scope === "session") {
      if (participant.role !== "presenter" && !admin) return { ok: false, code: "unauthorized", message: "Only a presenter or administrator may revoke a session." };
      participant.lastClientSequence = request.sequence;
      destroySession(session, {
        type: "screen-share-revoked", protocolVersion: SCREEN_SHARE_SIGNALING_PROTOCOL_VERSION,
        sequence: nextSequence(session), timestampMs: now(), sessionId: session.id, binding: session.binding,
        scope: "session", targetParticipantId: null, reason: request.reason, reconnectAllowed: false,
      });
      return { ok: true, value: null };
    }
    const targetId = request.targetParticipantId ?? participant.id;
    const target = session.participants.get(targetId);
    if (!target) return { ok: false, code: "invalid", message: "Media participant is unavailable." };
    if (target.id !== participant.id && participant.role !== "presenter" && !admin) {
      return { ok: false, code: "unauthorized", message: "Viewer may only leave its own session." };
    }
    if (target.role === "presenter") return { ok: false, code: "unauthorized", message: "Presenter must stop the session." };
    participant.lastClientSequence = request.sequence;
    removeParticipant(session, target, request.reason);
    return { ok: true, value: null };
  }

  function subscribe(raw: ScreenShareCredential, subject: string, listener: (message: ScreenSharePeerMessage) => void): MediaSignalResult<() => void> {
    const found = authenticate(raw, subject);
    if (!found.ok) return found;
    const participant = found.value.participant;
    for (const message of participant.queue.splice(0)) listener(message);
    participant.listeners.add(listener);
    return { ok: true, value: () => participant.listeners.delete(listener) };
  }

  function revalidate(raw: ScreenShareCredential, subject: string): MediaSignalResult<null> {
    const found = authenticate(raw, subject);
    return found.ok ? { ok: true, value: null } : found;
  }

  function authorizeIce(
    raw: ScreenShareCredential,
    binding: ScreenShareBinding,
    subject: string,
  ): MediaSignalResult<{ expiresAtMs: number }> {
    const found = authenticate(raw, subject);
    if (!found.ok) return found;
    if (!sameBinding(found.value.session.binding, binding)) {
      return { ok: false, code: "unauthorized", message: "Media binding does not match the grant." };
    }
    return { ok: true, value: { expiresAtMs: found.value.participant.expiresAt } };
  }

  function revokeSubject(subject: string): number {
    let removed = 0;
    for (const session of [...sessions.values()]) {
      const matching = [...session.participants.values()].filter((participant) => participant.subject === subject);
      const presenter = matching.find((participant) => participant.role === "presenter");
      if (presenter) {
        destroySession(session, stopNotice(session, "server-shutdown"));
        removed += 1;
        continue;
      }
      for (const participant of matching) {
        removeParticipant(session, participant, "authorization-revoked");
        removed += 1;
      }
    }
    return removed;
  }

  function cleanup(): number {
    let removed = 0;
    for (const session of [...sessions.values()]) {
      const expired = [...session.participants.values()].filter((participant) => now() >= participant.expiresAt);
      const presenter = expired.find((participant) => participant.role === "presenter");
      if (presenter) {
        destroySession(session, stopNotice(session, "grant-expired"));
        removed += 1;
        continue;
      }
      for (const participant of expired) {
        removeParticipant(session, participant, "authorization-revoked");
        removed += 1;
      }
    }
    return removed;
  }

  return { join, resume, signal, leave, subscribe, authorizeIce, revalidate, revokeSubject, cleanup, sessionCount: () => sessions.size };
}

function opaque(bytes: number): string { return randomBytes(bytes).toString("base64url"); }
function hash(token: string): Buffer { return createHash("sha256").update(token).digest(); }
function tokenMatches(expected: Buffer, token: string): boolean {
  if (typeof token !== "string" || token.length < 16 || token.length > 512) return false;
  const actual = hash(token); return actual.length === expected.length && timingSafeEqual(actual, expected);
}
function positive(value: number | undefined, fallback: number): number {
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
}
function count(value: number | undefined, fallback: number): number { return Math.max(1, Math.floor(positive(value, fallback))); }
function clientSequenceHasSuccessor(value: number): boolean {
  return Number.isSafeInteger(value) && value >= 0 && value < Number.MAX_SAFE_INTEGER;
}
function copyBinding(binding: ScreenShareBinding): ScreenShareBinding { return { ...binding }; }
function sameBinding(a: ScreenShareBinding, b: ScreenShareBinding): boolean {
  return a.officeId === b.officeId && a.levelId === b.levelId && a.roomId === b.roomId && a.screenId === b.screenId;
}
