// @vitest-environment jsdom

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";

import WorkbenchKafkaDebugPanel from "./WorkbenchKafkaDebugPanel.vue";

describe("WorkbenchKafkaDebugPanel", () => {
  const originalEventSource = globalThis.EventSource;

  beforeEach(() => {
    FakeEventSource.instances = [];
    globalThis.EventSource = FakeEventSource as unknown as typeof EventSource;
  });

  afterEach(() => {
    globalThis.EventSource = originalEventSource;
  });

  it("replays only after an explicit click, completes on terminal, and clears locally", async () => {
    const wrapper = mount(WorkbenchKafkaDebugPanel, {
      props: {
        traceId: "trc_component_debug",
        replayId: "rpl_component_debug",
        outputBarrier: { partition: 0, firstOffset: "10", lastOffset: "11" }
      }
    });
    expect(FakeEventSource.instances).toHaveLength(0);

    await wrapper.get('[data-testid="workbench-kafka-debug-replay"]').trigger("click");
    const source = FakeEventSource.instances[0]!;
    expect(source.url).toContain("stream=hwlab-debug");
    expect(source.url).toContain("fromBeginning=true");
    expect(source.url).toContain("traceId=trc_component_debug");
    expect(source.url).toContain("replayId=rpl_component_debug");
    expect(source.url).toContain("partition=0");
    expect(source.url).toContain("firstOffset=10");
    expect(source.url).toContain("lastOffset=11");

    source.emit("hwlab.kafka.connected", { consumerReady: true, debugIsolation: true, deliverySemantics: "debug-replay", liveOnly: false, replay: true, topic: "hwlab.event.debug.v1", groupId: "debug-group-unique" });
    source.emit("hwlab.kafka.event", debugRecord("assistant", { type: "assistant", assistantText: "debug answer", status: "running" }));
    source.emit("hwlab.kafka.event", debugRecord("terminal", { type: "result", eventType: "terminal", terminal: true, status: "completed" }));
    source.emit("hwlab.kafka.replay-complete", {
      reason: "terminal",
      terminalObserved: true,
      count: 2,
      result: {
        contractVersion: "workbench-kafka-debug-replay-v2",
        replayId: "rpl_component_debug",
        code: "terminal_complete",
        terminalObserved: true,
        counts: { scanned: 2, matched: 2, delivered: 2, clientReceived: null, decoded: null, applied: null }
      }
    });
    await nextTick();

    expect(wrapper.get('[data-testid="workbench-kafka-debug-panel"]').attributes("data-replay-status")).toBe("completed");
    expect(wrapper.get('[data-testid="workbench-kafka-debug-counts"]').text()).toContain("2/2");
    expect(wrapper.get('[data-testid="workbench-kafka-debug-layer-counts"]').text()).toContain("scanned=2");
    expect(wrapper.get('[data-testid="workbench-kafka-debug-layer-counts"]').text()).toContain("received=2 · decoded=2 · applied=2");
    expect(wrapper.get(".message-card").attributes("data-status")).toBe("completed");
    expect(wrapper.get(".message-card").text()).toContain("debug answer");
    expect(source.closed).toBe(true);

    await wrapper.get('[data-testid="workbench-kafka-debug-clear"]').trigger("click");
    expect(wrapper.find(".message-card").exists()).toBe(false);
    expect(wrapper.get('[data-testid="workbench-kafka-debug-counts"]').text()).toContain("0/0");

    await wrapper.get('[data-testid="workbench-kafka-debug-replay"]').trigger("click");
    const incompleteSource = FakeEventSource.instances[1]!;
    incompleteSource.emit("hwlab.kafka.connected", { consumerReady: true, debugIsolation: true, deliverySemantics: "debug-replay", liveOnly: false, replay: true });
    incompleteSource.emit("hwlab.kafka.replay-complete", { reason: "timeout", terminalObserved: false, count: 0 });
    await nextTick();
    expect(wrapper.get('[data-testid="workbench-kafka-debug-panel"]').attributes("data-replay-status")).toBe("incomplete");
    expect(wrapper.text()).toContain("重放未观测到 terminal（timeout）");
    expect(incompleteSource.closed).toBe(true);
  });

  it("shows the product raw HWLAB Event buffer without opening another EventSource", async () => {
    const firstRaw = '{"schema":"hwlab.event.v1","eventId":"evt_raw_1"}';
    const invalidRaw = "{invalid-json";
    const clearRawIngress = vi.fn();
    const wrapper = mount(WorkbenchKafkaDebugPanel, {
      props: {
        traceId: "trc_component_raw",
        replayEnabled: true,
        rawEnabled: true,
        onClearRawIngress: clearRawIngress,
        rawIngress: {
          scopeKey: "ses_component_raw",
          receivedCount: 2,
          decodedCount: 1,
          rejectedCount: 1,
          evictedCount: 0,
          retainedBytes: firstRaw.length + invalidRaw.length,
          rows: [
            { arrivalSeq: 1, receivedAt: "2026-07-10T00:00:00.000Z", eventName: "hwlab.event.v1", rawText: firstRaw, decodeStatus: "accepted", byteLength: firstRaw.length },
            { arrivalSeq: 2, receivedAt: "2026-07-10T00:00:01.000Z", eventName: "hwlab.event.v1", rawText: invalidRaw, decodeStatus: "invalid-json", byteLength: invalidRaw.length }
          ]
        }
      }
    });

    expect(FakeEventSource.instances).toHaveLength(0);
    await wrapper.get('[data-testid="workbench-kafka-debug-tab-raw"]').trigger("click");
    expect(FakeEventSource.instances).toHaveLength(0);
    expect(wrapper.get('[data-testid="workbench-raw-hwlab-counts"]').text()).toContain("received=2");
    expect((wrapper.get('[data-testid="workbench-raw-hwlab-text"]').element as HTMLTextAreaElement).value).toBe(`${firstRaw}\n${invalidRaw}`);

    await wrapper.get('[data-testid="workbench-raw-hwlab-clear"]').trigger("click");
    expect(clearRawIngress).toHaveBeenCalledTimes(1);
    expect(FakeEventSource.instances).toHaveLength(0);
  });

  it("downgrades server terminal_complete when the browser received fewer events", async () => {
    const wrapper = mount(WorkbenchKafkaDebugPanel, { props: { traceId: "trc_component_transport_gap" } });
    await wrapper.get('[data-testid="workbench-kafka-debug-replay"]').trigger("click");
    const source = FakeEventSource.instances[0]!;
    source.emit("hwlab.kafka.connected", { consumerReady: true, debugIsolation: true, deliverySemantics: "debug-replay", liveOnly: false, replay: true });
    source.emit("hwlab.kafka.event", debugRecord("assistant", { type: "assistant", assistantText: "partial", status: "running" }, "trc_component_transport_gap"));
    source.emit("hwlab.kafka.replay-complete", {
      reason: "terminal",
      terminalObserved: true,
      result: {
        contractVersion: "workbench-kafka-debug-replay-v2",
        code: "terminal_complete",
        ok: true,
        terminalObserved: true,
        counts: { delivered: 2, clientReceived: null, decoded: null, applied: null }
      }
    });
    await nextTick();

    expect(wrapper.get('[data-testid="workbench-kafka-debug-panel"]').attributes("data-replay-status")).toBe("incomplete");
    expect(wrapper.text()).toContain("transport_failed");
    expect(wrapper.get('[data-testid="workbench-kafka-debug-layer-counts"]').text()).toContain("received=1 · decoded=1 · applied=1");
    expect(source.closed).toBe(true);
  });
});

class FakeEventSource {
  static instances: FakeEventSource[] = [];
  readonly url: string;
  closed = false;
  onerror: ((event: Event) => void) | null = null;
  onmessage: ((event: MessageEvent) => void) | null = null;
  private listeners = new Map<string, Set<(event: MessageEvent) => void>>();

  constructor(url: string | URL) {
    this.url = String(url);
    FakeEventSource.instances.push(this);
  }

  addEventListener(name: string, listener: (event: MessageEvent) => void) {
    const listeners = this.listeners.get(name) ?? new Set();
    listeners.add(listener);
    this.listeners.set(name, listeners);
  }

  removeEventListener(name: string, listener: (event: MessageEvent) => void) {
    this.listeners.get(name)?.delete(listener);
  }

  close() {
    this.closed = true;
  }

  emit(name: string, payload: Record<string, unknown>) {
    const event = new MessageEvent(name, { data: JSON.stringify(payload) });
    for (const listener of this.listeners.get(name) ?? []) listener(event);
  }
}

function debugRecord(eventType: string, event: Record<string, unknown>, traceId = "trc_component_debug") {
  return {
    stream: "hwlab-debug",
    topic: "hwlab.event.debug.v1",
    offset: eventType === "terminal" ? "2" : "1",
    value: {
      schema: "hwlab.event.debug.v1",
      eventType,
      traceId,
      hwlabSessionId: "ses_component_debug",
      event: { traceId, sessionId: "ses_component_debug", sourceEventId: `evt_${eventType}`, ...event }
    }
  };
}
