import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { errorHandler } from "../middleware/index.js";
import { issueRoutes } from "../routes/issues.js";

const mockFeedbackService = vi.hoisted(() => ({
  getFeedbackTraceById: vi.fn(),
  getFeedbackTraceBundle: vi.fn(),
  listIssueVotesForUser: vi.fn(),
  listFeedbackTraces: vi.fn(),
  saveIssueVote: vi.fn(),
}));

const mockIssueService = vi.hoisted(() => ({
  getById: vi.fn(),
  getByIdentifier: vi.fn(),
  update: vi.fn(),
  addComment: vi.fn(),
  findMentionedAgents: vi.fn(),
}));

const mockFeedbackExportService = vi.hoisted(() => ({
  flushPendingFeedbackTraces: vi.fn(async () => ({ attempted: 1, sent: 1, failed: 0 })),
}));

vi.mock("../services/index.js", () => ({
  accessService: () => ({
    canUser: vi.fn(),
    hasPermission: vi.fn(),
  }),
  agentService: () => ({
    getById: vi.fn(),
  }),
  documentService: () => ({}),
  executionWorkspaceService: () => ({}),
  feedbackService: () => mockFeedbackService,
  goalService: () => ({}),
  heartbeatService: () => ({
    wakeup: vi.fn(async () => undefined),
    reportRunActivity: vi.fn(async () => undefined),
    getRun: vi.fn(async () => null),
    getActiveRunForAgent: vi.fn(async () => null),
    cancelRun: vi.fn(async () => null),
  }),
  instanceSettingsService: () => ({
    get: vi.fn(async () => ({
      id: "instance-settings-1",
      general: {
        censorUsernameInLogs: false,
        feedbackDataSharingPreference: "prompt",
      },
    })),
    listCompanyIds: vi.fn(async () => ["company-1"]),
  }),
  issueApprovalService: () => ({}),
  issueService: () => mockIssueService,
  logActivity: vi.fn(async () => undefined),
  projectService: () => ({}),
  routineService: () => ({
    syncRunStatusForIssue: vi.fn(async () => undefined),
  }),
  workProductService: () => ({}),
}));

function createApp(actor: Record<string, unknown>) {
  const app = express();
  app.use(express.json());
  app.use((req, _res, next) => {
    (req as any).actor = actor;
    next();
  });
  app.use("/api", issueRoutes({} as any, {} as any, { feedbackExportService: mockFeedbackExportService }));
  app.use(errorHandler);
  return app;
}

describe("issue feedback trace routes", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("flushes a newly shared feedback trace immediately after saving the vote", async () => {
    const targetId = "11111111-1111-4111-8111-111111111111";
    mockIssueService.getById.mockResolvedValue({
      id: "issue-1",
      companyId: "company-1",
      identifier: "PAP-1",
    });
    mockFeedbackService.saveIssueVote.mockResolvedValue({
      vote: {
        targetType: "issue_comment",
        targetId,
        vote: "up",
        reason: null,
      },
      traceId: "trace-1",
      consentEnabledNow: false,
      persistedSharingPreference: null,
      sharingEnabled: true,
    });
    const app = createApp({
      type: "board",
      userId: "user-1",
      source: "session",
      isInstanceAdmin: true,
      companyIds: ["company-1"],
    });

    const res = await request(app)
      .post("/api/issues/issue-1/feedback-votes")
      .send({
        targetType: "issue_comment",
        targetId,
        vote: "up",
        allowSharing: true,
      });

    expect(res.status).toBe(201);
    expect(mockFeedbackExportService.flushPendingFeedbackTraces).toHaveBeenCalledWith({
      companyId: "company-1",
      traceId: "trace-1",
      limit: 1,
    });
  });

  it("rejects non-board callers before fetching a feedback trace", async () => {
    const app = createApp({
      type: "agent",
      agentId: "agent-1",
      companyId: "company-1",
      source: "agent_key",
      runId: "run-1",
    });

    const res = await request(app).get("/api/feedback-traces/trace-1");

    expect(res.status).toBe(403);
    expect(mockFeedbackService.getFeedbackTraceById).not.toHaveBeenCalled();
  });

  it("returns 404 when a board user lacks access to the trace company", async () => {
    mockFeedbackService.getFeedbackTraceById.mockResolvedValue({
      id: "trace-1",
      companyId: "company-2",
    });
    const app = createApp({
      type: "board",
      userId: "user-1",
      source: "session",
      isInstanceAdmin: false,
      companyIds: ["company-1"],
    });

    const res = await request(app).get("/api/feedback-traces/trace-1");

    expect(res.status).toBe(404);
  });

  it("returns 404 for bundle fetches when a board user lacks access to the trace company", async () => {
    mockFeedbackService.getFeedbackTraceBundle.mockResolvedValue({
      id: "trace-1",
      companyId: "company-2",
      issueId: "issue-1",
      files: [],
    });
    const app = createApp({
      type: "board",
      userId: "user-1",
      source: "session",
      isInstanceAdmin: false,
      companyIds: ["company-1"],
    });

    const res = await request(app).get("/api/feedback-traces/trace-1/bundle");

    expect(res.status).toBe(404);
  });
});
