import { type Router } from "express";
import { z } from "zod";

import { getAgentAuthContext } from "../auth/agentAuth.js";
import { logger } from "../logger.js";
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
import { type SessionMetadataStore } from "../sessions/metadataStore.js";
import { type SessionUiStateStore } from "../sessions/uiStateStore.js";
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
import {
  type ActiveRun,
  toPermissionStatus,
  updateLastAssistantPermission,
  updateLastAssistantQuestion,
} from "./chatUiState.js";

const permissionReplyPayloadSchema = z.object({
  session_id: z.string().max(128),
  reply: z.enum(["once", "always", "reject"]),
  message: z.string().max(1000).optional(),
});

const questionReplyPayloadSchema = z.object({
  session_id: z.string().max(128),
  answers: z.array(z.array(z.string().max(2000))).default([]),
});

const questionRejectPayloadSchema = z.object({
  session_id: z.string().max(128),
});

type RegisterInteractionRoutesOptions = {
  activeRuns: Map<string, ActiveRun>;
  runtime: OpencodeRuntimeAdapter;
  sessionMetadataStore: SessionMetadataStore;
  sessionUiStateStore: SessionUiStateStore;
};

const toSessionUiStateContext = (sessionId: string) => ({
  sessionId,
});

export const registerChatInteractionRoutes = (
  chatRouter: Router,
  {
    activeRuns,
    runtime,
    sessionMetadataStore,
    sessionUiStateStore,
  }: RegisterInteractionRoutesOptions,
) => {
  chatRouter.post("/permission/:request_id/reply", async (req, res) => {
    const requestId = req.params.request_id?.trim();
    const parsed = permissionReplyPayloadSchema.safeParse(req.body);
    if (!requestId) {
      res.status(400).json({ message: "request_id is required" });
      return;
    }
    if (!parsed.success) {
      res.status(400).json({
        message: "invalid request payload",
        detail: parsed.error.flatten(),
      });
      return;
    }

    try {
      const authContext = getAgentAuthContext(req);
      const projectId = authContext.projectId;
      const userId = authContext.userId;
      const actorKey = toActorKey(userId);
      const projectKey = toProjectKey(projectId);
      const sessionRecord = await sessionMetadataStore.get(
        { actorKey, projectId, projectKey, userId },
        parsed.data.session_id,
      );
      if (!sessionRecord) {
        res.status(404).json({ message: "session not found" });
        return;
      }

      const run = activeRuns.get(sessionRecord.sessionId);
      if (!run || run.status !== "running") {
        res.status(409).json({ message: "session is not waiting for permissions" });
        return;
      }

      const pendingPermission = run.pendingPermissions.get(requestId);
      if (!pendingPermission) {
        res.status(404).json({ message: "permission request not found" });
        return;
      }
      const persistPermissionState = async () => {
        const currentState = await sessionUiStateStore.read(
          toSessionUiStateContext(sessionRecord.sessionId),
        );
        await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
          sessionId: sessionRecord.sessionId,
          isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
          messages: run.messages,
        });
      };

      try {
        await runtime.replyPermission({
          requestId,
          sessionId: sessionRecord.sessionId,
          reply: parsed.data.reply,
          message: parsed.data.message,
        });
      } catch (error) {
        run.messages = updateLastAssistantPermission(
          run.messages,
          requestId,
          (permission) => ({
            ...permission,
            status: "error",
            error:
              error instanceof Error
                ? error.message
                : "failed to reply permission",
          }),
        );
        await persistPermissionState().catch((persistError) => {
          logger.warn(
            { err: persistError, sessionId: sessionRecord.sessionId },
            "failed to persist permission error state",
          );
        });
        res.status(502).json({
          message: "permission reply failed",
          detail: error instanceof Error ? error.message : String(error),
        });
        return;
      }

      run.pendingPermissions.delete(requestId);
      const status = toPermissionStatus(parsed.data.reply);
      run.messages = updateLastAssistantPermission(
        run.messages,
        requestId,
        (permission) => ({
          ...permission,
          status,
          repliedAt: Date.now(),
        }),
      );
      await persistPermissionState().catch((persistError) => {
        logger.warn(
          { err: persistError, sessionId: sessionRecord.sessionId },
          "failed to persist permission reply state",
        );
      });
      for (const subscriber of run.subscribers) {
        subscriber.write("permission_response", {
          session_id: sessionRecord.sessionId,
          request_id: requestId,
          reply: parsed.data.reply,
        });
      }

      res.status(202).json({
        session_id: sessionRecord.sessionId,
        request_id: requestId,
        reply: parsed.data.reply,
      });
    } catch (error) {
      const detail = error instanceof Error ? error.message : String(error);
      logger.error({ err: error }, "permission reply route failed");
      res.status(500).json({
        message: "permission reply route failed",
        detail,
      });
    }
  });

  chatRouter.post("/question/:request_id/reply", async (req, res) => {
    const requestId = req.params.request_id?.trim();
    const parsed = questionReplyPayloadSchema.safeParse(req.body);
    if (!requestId) {
      res.status(400).json({ message: "request_id is required" });
      return;
    }
    if (!parsed.success) {
      res.status(400).json({
        message: "invalid request payload",
        detail: parsed.error.flatten(),
      });
      return;
    }

    try {
      const authContext = getAgentAuthContext(req);
      const projectId = authContext.projectId;
      const userId = authContext.userId;
      const actorKey = toActorKey(userId);
      const projectKey = toProjectKey(projectId);
      const sessionRecord = await sessionMetadataStore.get(
        { actorKey, projectId, projectKey, userId },
        parsed.data.session_id,
      );
      if (!sessionRecord) {
        res.status(404).json({ message: "session not found" });
        return;
      }

      const run = activeRuns.get(sessionRecord.sessionId);
      if (!run) {
        res.status(409).json({ message: "session is not waiting for questions" });
        return;
      }

      const pendingQuestion = run.pendingQuestions.get(requestId);
      if (!pendingQuestion) {
        res.status(404).json({ message: "question request not found" });
        return;
      }
      const persistQuestionState = async () => {
        const currentState = await sessionUiStateStore.read(
          toSessionUiStateContext(sessionRecord.sessionId),
        );
        await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
          sessionId: sessionRecord.sessionId,
          isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
          messages: run.messages,
        });
      };

      try {
        await runtime.replyQuestion({
          requestId,
          sessionId: sessionRecord.sessionId,
          answers: parsed.data.answers,
        });
      } catch (error) {
        run.messages = updateLastAssistantQuestion(
          run.messages,
          requestId,
          (question) => ({
            ...question,
            status: "error",
            error:
              error instanceof Error
                ? error.message
                : "failed to reply question",
          }),
        );
        await persistQuestionState().catch((persistError) => {
          logger.warn(
            { err: persistError, sessionId: sessionRecord.sessionId },
            "failed to persist question error state",
          );
        });
        res.status(502).json({
          message: "question reply failed",
          detail: error instanceof Error ? error.message : String(error),
        });
        return;
      }

      run.pendingQuestions.delete(requestId);
      run.messages = updateLastAssistantQuestion(
        run.messages,
        requestId,
        (question) => ({
          ...question,
          status: "answered",
          answers: parsed.data.answers,
          repliedAt: Date.now(),
          error: undefined,
        }),
      );
      await persistQuestionState().catch((persistError) => {
        logger.warn(
          { err: persistError, sessionId: sessionRecord.sessionId },
          "failed to persist question reply state",
        );
      });
      for (const subscriber of run.subscribers) {
        subscriber.write("question_response", {
          session_id: pendingQuestion.session_id,
          request_id: requestId,
          answers: parsed.data.answers,
        });
      }
      if (
        run.status !== "running" &&
        run.pendingPermissions.size === 0 &&
        run.pendingQuestions.size === 0
      ) {
        activeRuns.delete(sessionRecord.sessionId);
      }

      res.status(202).json({
        session_id: pendingQuestion.session_id,
        request_id: requestId,
        answers: parsed.data.answers,
      });
    } catch (error) {
      const detail = error instanceof Error ? error.message : String(error);
      logger.error({ err: error }, "question reply route failed");
      res.status(500).json({
        message: "question reply route failed",
        detail,
      });
    }
  });

  chatRouter.post("/question/:request_id/reject", async (req, res) => {
    const requestId = req.params.request_id?.trim();
    const parsed = questionRejectPayloadSchema.safeParse(req.body);
    if (!requestId) {
      res.status(400).json({ message: "request_id is required" });
      return;
    }
    if (!parsed.success) {
      res.status(400).json({
        message: "invalid request payload",
        detail: parsed.error.flatten(),
      });
      return;
    }

    try {
      const authContext = getAgentAuthContext(req);
      const projectId = authContext.projectId;
      const userId = authContext.userId;
      const actorKey = toActorKey(userId);
      const projectKey = toProjectKey(projectId);
      const sessionRecord = await sessionMetadataStore.get(
        { actorKey, projectId, projectKey, userId },
        parsed.data.session_id,
      );
      if (!sessionRecord) {
        res.status(404).json({ message: "session not found" });
        return;
      }

      const run = activeRuns.get(sessionRecord.sessionId);
      if (!run) {
        res.status(409).json({ message: "session is not waiting for questions" });
        return;
      }

      const pendingQuestion = run.pendingQuestions.get(requestId);
      if (!pendingQuestion) {
        res.status(404).json({ message: "question request not found" });
        return;
      }
      const persistQuestionState = async () => {
        const currentState = await sessionUiStateStore.read(
          toSessionUiStateContext(sessionRecord.sessionId),
        );
        await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
          sessionId: sessionRecord.sessionId,
          isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
          messages: run.messages,
        });
      };

      try {
        await runtime.rejectQuestion({
          requestId,
          sessionId: sessionRecord.sessionId,
        });
      } catch (error) {
        run.messages = updateLastAssistantQuestion(
          run.messages,
          requestId,
          (question) => ({
            ...question,
            status: "error",
            error:
              error instanceof Error
                ? error.message
                : "failed to reject question",
          }),
        );
        await persistQuestionState().catch((persistError) => {
          logger.warn(
            { err: persistError, sessionId: sessionRecord.sessionId },
            "failed to persist question error state",
          );
        });
        res.status(502).json({
          message: "question reject failed",
          detail: error instanceof Error ? error.message : String(error),
        });
        return;
      }

      run.pendingQuestions.delete(requestId);
      run.messages = updateLastAssistantQuestion(
        run.messages,
        requestId,
        (question) => ({
          ...question,
          status: "rejected",
          repliedAt: Date.now(),
          error: undefined,
        }),
      );
      await persistQuestionState().catch((persistError) => {
        logger.warn(
          { err: persistError, sessionId: sessionRecord.sessionId },
          "failed to persist question reject state",
        );
      });
      for (const subscriber of run.subscribers) {
        subscriber.write("question_response", {
          session_id: pendingQuestion.session_id,
          request_id: requestId,
          rejected: true,
        });
      }
      if (
        run.status !== "running" &&
        run.pendingPermissions.size === 0 &&
        run.pendingQuestions.size === 0
      ) {
        activeRuns.delete(sessionRecord.sessionId);
      }

      res.status(202).json({
        session_id: pendingQuestion.session_id,
        request_id: requestId,
        rejected: true,
      });
    } catch (error) {
      const detail = error instanceof Error ? error.message : String(error);
      logger.error({ err: error }, "question reject route failed");
      res.status(500).json({
        message: "question reject route failed",
        detail,
      });
    }
  });
};
