import type { AuthenticatedRequest } from "../../../types/index.js";
import express, { type Request, type Response } from "express";
import { databaseLogger } from "../../utils/logger.js";
import { AuthManager } from "../../utils/auth-manager.js";
import { sessionManager } from "../../hosts/terminal/session-manager.js";
import {
  getCurrentSettingValue,
  createCurrentOpenTabRepository,
  createCurrentSessionShareRepository,
} from "../repositories/factory.js";

const router = express.Router();
const authManager = AuthManager.getInstance();
const authenticateJWT = authManager.createAuthMiddleware();

/**
 * @openapi
 * /open-tabs:
 *   get:
 *     summary: Get all open tabs for the current user
 *     tags:
 *       - Open Tabs
 *     responses:
 *       200:
 *         description: List of open tabs ordered by tab_order.
 */
const DEFAULT_TAB_TTL_MINUTES = 30;

function getTabTtlMs(): number {
  try {
    const value = getCurrentSettingValue("terminal_session_timeout_minutes");
    if (value) {
      const minutes = parseInt(value, 10);
      if (!isNaN(minutes) && minutes > 0) return minutes * 60_000;
    }
  } catch {
    // DB not available, use default
  }
  return DEFAULT_TAB_TTL_MINUTES * 60_000;
}

// Legacy tab types that were renamed. Normalize on read so previously saved
// tabs still restore to the correct (renamed) tab type.
const LEGACY_TAB_TYPE_MAP: Record<string, string> = {
  stats: "host-metrics",
};

function normalizeTabType(tabType: string): string {
  return LEGACY_TAB_TYPE_MAP[tabType] ?? tabType;
}

router.get("/", authenticateJWT, async (req: Request, res: Response) => {
  const userId = (req as AuthenticatedRequest).userId;
  try {
    const cutoff = new Date(Date.now() - getTabTtlMs()).toISOString();
    const tabs = await createCurrentOpenTabRepository().listRecentForUser(
      userId,
      cutoff,
    );
    return res.json(
      tabs.map((tab) => ({ ...tab, tabType: normalizeTabType(tab.tabType) })),
    );
  } catch (e) {
    databaseLogger.error("Failed to get open tabs", e, {
      operation: "get_open_tabs",
      userId,
    });
    return res.status(500).json({ error: "Failed to get open tabs" });
  }
});

/**
 * @openapi
 * /open-tabs:
 *   post:
 *     summary: Upsert a single open tab for the current user
 *     tags:
 *       - Open Tabs
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required: [id, tabType, label, tabOrder]
 *             properties:
 *               id:
 *                 type: string
 *               tabType:
 *                 type: string
 *               hostId:
 *                 type: integer
 *                 nullable: true
 *               label:
 *                 type: string
 *               tabOrder:
 *                 type: integer
 *               backendSessionId:
 *                 type: string
 *                 nullable: true
 *     responses:
 *       200:
 *         description: Tab upserted successfully.
 */
router.post("/", authenticateJWT, async (req: Request, res: Response) => {
  const userId = (req as AuthenticatedRequest).userId;
  const { id, tabType, hostId, label, tabOrder, backendSessionId } =
    req.body as {
      id: string;
      tabType: string;
      hostId?: number | null;
      label: string;
      tabOrder: number;
      backendSessionId?: string | null;
    };

  if (!id || !tabType || !label) {
    return res
      .status(400)
      .json({ error: "id, tabType, and label are required" });
  }

  try {
    await createCurrentOpenTabRepository().upsertForUser(userId, {
      id,
      tabType,
      hostId,
      label,
      tabOrder,
      backendSessionId,
    });
    return res.json({ success: true });
  } catch (e) {
    databaseLogger.error("Failed to upsert open tab", e, {
      operation: "upsert_open_tab",
      userId,
      id,
    });
    return res.status(500).json({ error: "Failed to upsert open tab" });
  }
});

/**
 * @openapi
 * /open-tabs:
 *   put:
 *     summary: Bulk replace all open tabs for the current user
 *     tags:
 *       - Open Tabs
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               tabs:
 *                 type: array
 *     responses:
 *       200:
 *         description: Tabs updated successfully.
 */
router.put("/", authenticateJWT, async (req: Request, res: Response) => {
  const userId = (req as AuthenticatedRequest).userId;
  const { tabs } = req.body as {
    tabs: Array<{
      id: string;
      tabType: string;
      hostId?: number | null;
      label: string;
      tabOrder: number;
      backendSessionId?: string | null;
    }>;
  };

  if (!Array.isArray(tabs)) {
    return res.status(400).json({ error: "tabs must be an array" });
  }

  try {
    await createCurrentOpenTabRepository().replaceForUser(userId, tabs);
    return res.json({ success: true });
  } catch (e) {
    databaseLogger.error("Failed to sync open tabs", e, {
      operation: "sync_open_tabs",
      userId,
    });
    return res.status(500).json({ error: "Failed to sync open tabs" });
  }
});

/**
 * @openapi
 * /open-tabs/{id}:
 *   patch:
 *     summary: Update a single open tab
 *     tags:
 *       - Open Tabs
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: Tab updated successfully.
 *       404:
 *         description: Tab not found.
 */
router.patch("/:id", authenticateJWT, async (req: Request, res: Response) => {
  const userId = (req as AuthenticatedRequest).userId;
  const id = String(req.params.id);
  const updates = req.body as Partial<{
    hostId: number | null;
    label: string;
    tabOrder: number;
    backendSessionId: string | null;
  }>;

  try {
    const updated = await createCurrentOpenTabRepository().updateForUser(
      userId,
      id,
      updates,
    );

    if (!updated) {
      return res.status(404).json({ error: "Tab not found" });
    }
    return res.json({ success: true });
  } catch (e) {
    databaseLogger.error("Failed to update open tab", e, {
      operation: "update_open_tab",
      userId,
      id,
    });
    return res.status(500).json({ error: "Failed to update open tab" });
  }
});

/**
 * @openapi
 * /open-tabs/{id}:
 *   delete:
 *     summary: Delete a single open tab
 *     tags:
 *       - Open Tabs
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: Tab deleted successfully.
 */
router.delete("/:id", authenticateJWT, async (req: Request, res: Response) => {
  const userId = (req as AuthenticatedRequest).userId;
  const id = String(req.params.id);

  try {
    await createCurrentOpenTabRepository().deleteForUser(userId, id);
    return res.json({ success: true });
  } catch (e) {
    databaseLogger.error("Failed to delete open tab", e, {
      operation: "delete_open_tab",
      userId,
      id,
    });
    return res.status(500).json({ error: "Failed to delete open tab" });
  }
});

/**
 * @openapi
 * /open-tabs/active-sessions:
 *   get:
 *     summary: Get all active backend sessions for the current user
 *     description: >
 *       Returns live terminal sessions from the session manager, both sessions the
 *       caller owns and SSH sessions shared to the caller by another user (via
 *       an in-app session share). Used by the Active Connections panel and tab restore logic.
 *     tags:
 *       - Open Tabs
 *     responses:
 *       200:
 *         description: List of active sessions (own and shared-with-me).
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 type: object
 *                 properties:
 *                   sessionId:
 *                     type: string
 *                   hostId:
 *                     type: integer
 *                   hostName:
 *                     type: string
 *                   tabInstanceId:
 *                     type: string
 *                   isConnected:
 *                     type: boolean
 *                   createdAt:
 *                     type: number
 *                   isOwnSession:
 *                     type: boolean
 *                   sharedByUsername:
 *                     type: string
 *                     nullable: true
 *                   permissionLevel:
 *                     type: string
 *                     nullable: true
 *                   shareId:
 *                     type: string
 *                     nullable: true
 */
router.get(
  "/active-sessions",
  authenticateJWT,
  async (req: Request, res: Response) => {
    const userId = (req as AuthenticatedRequest).userId;
    try {
      const ownSessions = sessionManager.getUserSessions(userId);
      const result = ownSessions.map((s) => ({
        sessionId: s.id,
        hostId: s.hostId,
        hostName: s.hostName,
        tabInstanceId: s.attachedTabInstanceId ?? s.tabInstanceId ?? null,
        isConnected: s.isConnected,
        createdAt: s.createdAt,
        isOwnSession: true,
        sharedByUsername: null as string | null,
        permissionLevel: null as string | null,
        shareId: null as string | null,
      }));

      const sharedWithMe =
        await createCurrentSessionShareRepository().findSharesTargetingUser(
          userId,
        );
      for (const share of sharedWithMe) {
        if (share.protocol !== "ssh") continue;
        const sharedSession = sessionManager.getSession(share.sessionId);
        if (!sharedSession || !sharedSession.isConnected) continue;
        result.push({
          sessionId: sharedSession.id,
          hostId: sharedSession.hostId,
          hostName: sharedSession.hostName,
          tabInstanceId:
            sharedSession.attachedTabInstanceId ??
            sharedSession.tabInstanceId ??
            null,
          isConnected: sharedSession.isConnected,
          createdAt: sharedSession.createdAt,
          isOwnSession: false,
          sharedByUsername: share.ownerUsername,
          permissionLevel: share.permissionLevel,
          shareId: share.id,
        });
      }

      return res.json(result);
    } catch (e) {
      databaseLogger.error("Failed to get active sessions", e, {
        operation: "get_active_sessions",
        userId,
      });
      return res.status(500).json({ error: "Failed to get active sessions" });
    }
  },
);

export default router;
