import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { errorHandler } from "../middleware/index.js";
import { contentJobRoutes } from "../routes/content-jobs.js";

// ---------------------------------------------------------------------------
// Hoisted mocks
// ---------------------------------------------------------------------------

const { getByIdMock, subscribeCompanyLiveEventsMock } = vi.hoisted(() => ({
  getByIdMock: vi.fn(),
  subscribeCompanyLiveEventsMock: vi.fn(),
}));

vi.mock("../services/content-job-store.js", () => ({
  contentJobStore: vi.fn(() => ({
    create: vi.fn(),
    getById: getByIdMock,
    listByCompany: vi.fn(),
    transition: vi.fn(),
  })),
}));

vi.mock("../services/content-job-runner.js", () => ({
  contentJobRunner: { dispatch: vi.fn() },
}));

vi.mock("../services/live-events.js", () => ({
  subscribeCompanyLiveEvents: subscribeCompanyLiveEventsMock,
}));

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeJob(overrides: Record<string, unknown> = {}) {
  return {
    id: "job-1",
    companyId: "company-1",
    jobType: "test",
    status: "queued",
    input: {},
    sourceTaskId: null,
    resultAssetId: null,
    errorMessage: null,
    startedAt: null,
    finishedAt: null,
    createdAt: new Date("2026-01-01T00:00:00.000Z"),
    updatedAt: new Date("2026-01-01T00:00:00.000Z"),
    ...overrides,
  };
}

function createApp() {
  const app = express();
  app.use(express.json());
  app.use((req, _res, next) => {
    (req as any).actor = {
      type: "board",
      source: "local_implicit",
      userId: "user-1",
      companyIds: ["company-1"],
    };
    next();
  });
  app.use("/api", contentJobRoutes({} as any, {} as any));
  app.use(errorHandler);
  return app;
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("content-jobs SSE endpoint", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Default: return a noop unsubscribe function
    subscribeCompanyLiveEventsMock.mockReturnValue(vi.fn());
  });

  it("returns text/event-stream content type", async () => {
    // For a terminal job the stream ends immediately — supertest can handle it
    getByIdMock.mockResolvedValue(makeJob({ status: "done" }));

    const res = await request(createApp())
      .get("/api/companies/company-1/content-jobs/job-1/events")
      .buffer(true);

    expect(res.headers["content-type"]).toMatch(/text\/event-stream/);
  });

  it("sends initial status event immediately for a queued job (ends stream for done job)", async () => {
    getByIdMock.mockResolvedValue(makeJob({ status: "done" }));

    const res = await request(createApp())
      .get("/api/companies/company-1/content-jobs/job-1/events")
      .buffer(true);

    expect(res.text).toContain("event: status");
    expect(res.text).toContain("job-1");
  });

  it("SSE for a terminal job (done) ends the stream after initial event", async () => {
    getByIdMock.mockResolvedValue(makeJob({ status: "done" }));

    const res = await request(createApp())
      .get("/api/companies/company-1/content-jobs/job-1/events")
      .buffer(true);

    expect(res.status).toBe(200);
    // Stream was ended by the server — should not have subscribed to live events
    expect(subscribeCompanyLiveEventsMock).not.toHaveBeenCalled();
  });

  it("SSE for a terminal job (failed) ends the stream after initial event", async () => {
    getByIdMock.mockResolvedValue(makeJob({ status: "failed", errorMessage: "oops" }));

    const res = await request(createApp())
      .get("/api/companies/company-1/content-jobs/job-1/events")
      .buffer(true);

    expect(res.status).toBe(200);
    expect(res.text).toContain("event: status");
    expect(subscribeCompanyLiveEventsMock).not.toHaveBeenCalled();
  });

  it("SSE for a nonexistent job sends error event", async () => {
    getByIdMock.mockResolvedValue(null);

    const res = await request(createApp())
      .get("/api/companies/company-1/content-jobs/nonexistent-uuid/events")
      .buffer(true);

    expect(res.text).toContain("event: error");
    expect(res.text).toContain("Job not found");
  });
});
