import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
  authorizeMediaSurface,
  createMediaSurfaceLifecycle,
  isMediaSurface,
  parseMediaSurface,
  parseMediaViewerContext,
  transitionMediaSurface,
  type MediaSurface,
  type MediaSurfaceSource,
} from "../media/index.ts";
import type { VideoTexture } from "three";

const PRIVATE_SOURCE: MediaSurfaceSource = {
  kind: "live-stream",
  locator: "opaque:private-stream-token",
  privacy: "private",
  hasAudio: true,
  autoplay: false,
  muted: true,
};

function surface(over: Partial<MediaSurface> = {}): MediaSurface {
  return {
    screenId: "screen-1",
    officeId: "office-1",
    levelId: "level-1",
    roomId: "room-1",
    acl: {
      audience: "office-members",
      viewerIds: [],
      presenterIds: ["presenter-1"],
      moderatorIds: ["moderator-1"],
    },
    source: PRIVATE_SOURCE,
    state: { status: "presenting", presenterId: "presenter-1", revision: 1 },
    ...over,
  };
}

describe("media surface validation", () => {
  it("accepts a strict JSON round trip and rejects extra/non-JSON fields", () => {
    const parsed = parseMediaSurface(JSON.parse(JSON.stringify(surface())));
    assert.deepEqual(parsed, surface());
    assert.equal(isMediaSurface({ ...surface(), surprise: true }), false);
    assert.equal(isMediaSurface({ ...surface(), screenId: "bad id" }), false);
    assert.equal(isMediaSurface(Object.assign(Object.create(null), surface())), false);
  });

  it("enforces state/source consistency and safe playback literals", () => {
    assert.throws(() => parseMediaSurface({ ...surface(), source: null }), /require a source/);
    assert.throws(() => parseMediaSurface({
      ...surface(),
      source: { ...PRIVATE_SOURCE, autoplay: true },
    }), /autoplay/);
    assert.throws(() => parseMediaSurface({
      ...surface(),
      source: { ...PRIVATE_SOURCE, muted: false },
    }), /muted/);
    assert.throws(() => parseMediaSurface({
      ...surface(),
      acl: { ...surface().acl, audience: "public" },
    }), /private source/);
  });

  it("strictly validates the server-created viewer context", () => {
    assert.deepEqual(parseMediaViewerContext({
      viewerId: "viewer-1", officeIds: ["office-1"], optedIn: true,
    }), { viewerId: "viewer-1", officeIds: ["office-1"], optedIn: true });
    assert.throws(() => parseMediaViewerContext({
      viewerId: null, officeIds: [], optedIn: true, clientClaimsAdmin: true,
    }), /exactly/);
  });
});

describe("server-enforceable authorization", () => {
  it("never returns a private source to an anonymous public viewer", () => {
    const decision = authorizeMediaSurface(surface(), { viewerId: null, officeIds: [], optedIn: true });
    assert.equal(decision.authorized, false);
    assert.equal(decision.canView, false);
    assert.equal(decision.reason, "authentication_required");
    assert.equal(decision.surface.source, null);
    assert.ok(!JSON.stringify(decision).includes(PRIVATE_SOURCE.locator));
  });

  it("requires both trusted membership and explicit opt-in before releasing source", () => {
    const context = { viewerId: "viewer-1", officeIds: ["office-1"], optedIn: false };
    const waiting = authorizeMediaSurface(surface(), context);
    assert.equal(waiting.authorized, true);
    assert.equal(waiting.reason, "opt_in_required");
    assert.equal(waiting.surface.source, null);
    const ready = authorizeMediaSurface(surface(), { ...context, optedIn: true });
    assert.equal(ready.canView, true);
    assert.equal(ready.surface.source?.locator, PRIVATE_SOURCE.locator);
    assert.deepEqual(ready.surface.playback, { autoplay: false, muted: true });
  });
});

describe("presenter lifecycle", () => {
  it("stops and revokes by clearing the sensitive source", () => {
    const stopped = transitionMediaSurface(surface(), { type: "stop", presenterId: "presenter-1" });
    assert.equal(stopped.state.status, "stopped");
    assert.equal(stopped.source, null);
    assert.equal(stopped.state.revision, 2);
    const revoked = transitionMediaSurface(surface(), { type: "revoke", actorId: "moderator-1" });
    assert.equal(revoked.state.status, "revoked");
    assert.equal(revoked.source, null);
    assert.throws(() => transitionMediaSurface(surface(), { type: "revoke", actorId: "stranger" }));
  });

  it("retains authorization through a deterministic disconnect/reconnect", () => {
    const reconnecting = transitionMediaSurface(surface(), { type: "disconnect", presenterId: "presenter-1" });
    assert.equal(reconnecting.state.status, "reconnecting");
    const resumed = transitionMediaSurface(reconnecting, { type: "reconnect", presenterId: "presenter-1" });
    assert.equal(resumed.state.status, "presenting");
    assert.equal(resumed.state.revision, 3);
    assert.throws(() => transitionMediaSurface(reconnecting, { type: "reconnect", presenterId: "other" }));
  });
});

describe("caller-owned video/texture lifecycle", () => {
  it("never plays, stops a track, or disposes a texture and releases references", () => {
    let pauses = 0;
    let stops = 0;
    let disposals = 0;
    const video = {
      autoplay: true,
      muted: false,
      pause: () => { pauses += 1; },
    } as unknown as HTMLVideoElement;
    const texture = { dispose: () => { disposals += 1; } } as unknown as VideoTexture;
    const track = { stop: () => { stops += 1; } } as unknown as MediaStreamTrack;
    const lifecycle = createMediaSurfaceLifecycle();
    lifecycle.bind({ video, texture, track });
    assert.equal(video.autoplay, false);
    assert.equal(video.muted, true);
    assert.equal(lifecycle.texture(), null);
    lifecycle.apply(authorizeMediaSurface(surface(), {
      viewerId: "viewer-1", officeIds: ["office-1"], optedIn: true,
    }));
    assert.equal(lifecycle.texture(), texture);
    lifecycle.dispose();
    lifecycle.dispose();
    assert.equal(lifecycle.texture(), null);
    assert.ok(pauses >= 2);
    assert.equal(stops, 0);
    assert.equal(disposals, 0);
  });
});
