import { Router } from "express";
import type { Db } from "@paperclipai/db";
import { assertBoard, assertCompanyAccess } from "./authz.js";
import { chatService } from "../services/chat.js";
import { sendPushToAll } from "../services/pushService.js";
import { issueService } from "../services/issues.js";
import { z } from "zod";
import {
  createConversationSchema,
  updateConversationSchema,
  createMessageSchema,
  handoffSchema,
  searchMessagesSchema,
  branchConversationSchema,
} from "@paperclipai/shared";

export function chatRoutes(db: Db): Router {
  const router = Router();
  const svc = chatService(db);
  const issueSvc = issueService(db);

  // GET /api/companies/:companyId/conversations
  router.get("/companies/:companyId/conversations", async (req, res) => {
    assertBoard(req);
    assertCompanyAccess(req, req.params.companyId!);
    const { cursor, limit, includeArchived, search, agentId } = req.query;
    const result = await svc.listConversations(req.params.companyId!, {
      cursor: cursor as string | undefined,
      limit: limit ? Number(limit) : undefined,
      includeArchived: includeArchived === "true",
      search: search as string | undefined,
      agentId: agentId as string | undefined,
    });
    res.json(result);
  });

  // POST /api/companies/:companyId/conversations
  router.post("/companies/:companyId/conversations", async (req, res) => {
    assertBoard(req);
    assertCompanyAccess(req, req.params.companyId!);
    const data = createConversationSchema.parse(req.body);
    const conversation = await svc.createConversation(req.params.companyId!, data);
    res.status(201).json(conversation);
  });

  // GET /api/conversations/:id
  router.get("/conversations/:id", async (req, res) => {
    assertBoard(req);
    const conversation = await svc.getConversation(req.params.id!);
    res.json(conversation);
  });

  // PATCH /api/conversations/:id
  router.patch("/conversations/:id", async (req, res) => {
    assertBoard(req);
    const data = updateConversationSchema.parse(req.body);
    const conversation = await svc.updateConversation(req.params.id!, data);
    res.json(conversation);
  });

  // DELETE /api/conversations/:id
  router.delete("/conversations/:id", async (req, res) => {
    assertBoard(req);
    await svc.softDeleteConversation(req.params.id!);
    res.status(204).end();
  });

  // GET /api/conversations/:id/messages
  router.get("/conversations/:id/messages", async (req, res) => {
    assertBoard(req);
    const { cursor, limit } = req.query;
    const result = await svc.listMessages(req.params.id!, {
      cursor: cursor as string | undefined,
      limit: limit ? Number(limit) : undefined,
    });
    res.json(result);
  });

  // POST /api/conversations/:id/messages
  router.post("/conversations/:id/messages", async (req, res) => {
    assertBoard(req);
    const data = createMessageSchema.parse(req.body);
    const message = await svc.addMessage(req.params.id!, data);
    res.status(201).json(message);
  });

  // POST /api/conversations/:id/stream -- SSE streaming endpoint (CHAT-01, PERF-02)
  router.post("/conversations/:id/stream", async (req, res) => {
    assertBoard(req);
    const { content, agentId } = req.body;
    if (!content || typeof content !== "string") {
      res.status(400).json({ error: "content is required" });
      return;
    }

    // Set SSE headers and flush BEFORE any generation (PERF-02)
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");
    res.setHeader("X-Accel-Buffering", "no");
    res.flushHeaders();
    res.write(":ok\n\n");

    const abort = new AbortController();
    req.on("close", () => abort.abort());

    try {
      let fullContent = "";
      for await (const token of svc.streamEcho(content, abort.signal)) {
        if (!res.writable) break;
        fullContent += token;
        res.write(`data: ${JSON.stringify({ token })}\n\n`);
      }
      if (res.writable && !abort.signal.aborted) {
        const message = await svc.addMessage(req.params.id!, {
          role: "assistant",
          content: fullContent.trim(),
          agentId: agentId || undefined,
        });
        res.write(`data: ${JSON.stringify({ done: true, messageId: message.id, content: fullContent.trim() })}\n\n`);

        // Fire push notification for offline subscribers (PWA-06)
        const conversation = await svc.getConversation(req.params.id!);
        sendPushToAll(db, conversation.companyId, {
          title: "New agent response",
          body: fullContent.trim().slice(0, 100),
          data: { url: `/chat/${conversation.id}` },
        }).catch(() => {}); // non-blocking
      }
    } catch (err) {
      if (res.writable && !abort.signal.aborted) {
        res.write(`data: ${JSON.stringify({ error: "Stream error" })}\n\n`);
      }
    } finally {
      res.end();
    }
  });

  // PATCH /api/conversations/:id/messages/:msgId -- Edit message content
  router.patch("/conversations/:id/messages/:msgId", async (req, res) => {
    assertBoard(req);
    const { content } = req.body;
    if (!content || typeof content !== "string") {
      res.status(400).json({ error: "content is required" });
      return;
    }
    const message = await svc.editMessage(req.params.msgId!, content);
    if (!message) {
      res.status(404).json({ error: "Message not found" });
      return;
    }
    res.json(message);
  });

  // DELETE /api/conversations/:id/messages/after/:msgId -- Truncate messages after a given message
  router.delete("/conversations/:id/messages/after/:msgId", async (req, res) => {
    assertBoard(req);
    await svc.truncateMessagesAfter(req.params.id!, req.params.msgId!);
    res.status(204).end();
  });

  // POST /api/conversations/:id/handoff -- Brainstormer handoff to PM: inserts handoff + task_created messages and creates an issue
  router.post("/conversations/:id/handoff", async (req, res) => {
    assertBoard(req);
    const data = handoffSchema.parse(req.body);

    // Resolve companyId from conversation
    const conversation = await svc.getConversation(req.params.id!);
    const companyId = conversation.companyId;

    // 1. Insert handoff system message
    const handoffMsg = await svc.addSystemMessage(req.params.id!, {
      content: `Brainstormer \u2192 PM: spec handed off`,
      messageType: "handoff",
    });

    // 2. Create issue from spec
    const specDescription = [
      `**What:** ${data.spec.what}`,
      `**Why:** ${data.spec.why}`,
      data.spec.constraints ? `**Constraints:** ${data.spec.constraints}` : "",
      data.spec.success ? `**Success:** ${data.spec.success}` : "",
    ].filter(Boolean).join("\n\n");

    const issue = await issueSvc.create(companyId, {
      title: data.spec.what.slice(0, 100),
      description: specDescription,
      status: "backlog",
      priority: "medium",
    });

    // 3. Insert task_created system message
    await svc.addSystemMessage(req.params.id!, {
      content: JSON.stringify({
        taskId: issue.identifier,
        taskTitle: issue.title,
        taskUrl: `/issues/${issue.id}`,
      }),
      messageType: "task_created",
    });

    res.json({ handoffMessageId: handoffMsg.id, issues: [issue] });
  });

  // POST /api/conversations/:id/status-update -- Agent completion notification in chat
  router.post("/conversations/:id/status-update", async (req, res) => {
    assertBoard(req);
    const { agentName, taskId, taskTitle, taskUrl } = req.body;
    if (!agentName || !taskId) {
      res.status(400).json({ error: "agentName and taskId are required" });
      return;
    }

    const message = await svc.addSystemMessage(req.params.id!, {
      content: JSON.stringify({ agentName, taskId, taskTitle, taskUrl }),
      messageType: "status_update",
    });

    res.status(201).json(message);
  });

  // GET /api/companies/:companyId/messages/search
  router.get("/companies/:companyId/messages/search", async (req, res) => {
    assertBoard(req);
    assertCompanyAccess(req, req.params.companyId!);
    let parsed: { q: string; limit?: number };
    try {
      parsed = searchMessagesSchema.parse({ q: req.query.q, limit: req.query.limit });
    } catch {
      res.status(400).json({ error: "Query must be at least 2 characters" });
      return;
    }
    const result = await svc.searchMessages(req.params.companyId!, parsed.q, { limit: parsed.limit });
    res.json(result);
  });

  // POST /api/conversations/:id/bookmarks -- Toggle bookmark for a message
  router.post("/conversations/:id/bookmarks", async (req, res) => {
    assertBoard(req);
    const messageId = z.string().uuid().safeParse(req.body.messageId);
    if (!messageId.success) {
      res.status(400).json({ error: "messageId must be a valid UUID" });
      return;
    }
    const conversationId = req.params.id!;
    const conversation = await svc.getConversation(conversationId);
    const result = await svc.toggleBookmark(conversation.companyId, messageId.data, conversationId);
    res.json(result);
  });

  // GET /api/companies/:companyId/bookmarks
  router.get("/companies/:companyId/bookmarks", async (req, res) => {
    assertBoard(req);
    assertCompanyAccess(req, req.params.companyId!);
    const conversationId = req.query.conversationId as string | undefined;
    const result = await svc.getBookmarks(req.params.companyId!, { conversationId });
    res.json(result);
  });

  // POST /api/conversations/:id/branch -- Branch conversation from a message
  router.post("/conversations/:id/branch", async (req, res) => {
    assertBoard(req);
    const parsed = branchConversationSchema.parse(req.body);
    const conv = await svc.getConversation(req.params.id!);
    const newConversation = await svc.branchConversation(req.params.id!, parsed.branchFromMessageId, conv.companyId);
    res.status(201).json(newConversation);
  });

  // GET /api/conversations/:id/branches -- List child conversations
  router.get("/conversations/:id/branches", async (req, res) => {
    assertBoard(req);
    const branches = await svc.listBranches(req.params.id!);
    res.json({ items: branches });
  });

  // GET /api/conversations/:id/export -- Export conversation as Markdown or JSON file
  router.get("/conversations/:id/export", async (req, res) => {
    assertBoard(req);
    const format = req.query.format === "json" ? "json" : "markdown";
    const { content, filename } = await svc.exportConversation(req.params.id!, format);
    const mime = format === "json" ? "application/json" : "text/markdown";
    res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
    res.setHeader("Content-Type", mime);
    res.send(content);
  });

  return router;
}
