import type { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { z } from "zod";
import type { CliLauncher } from "../cli-launcher.js";
import type { WsBridge } from "../ws-bridge.js";
import type { WorktreeTracker } from "../worktree-tracker.js";
import type { SessionStore } from "../session-store.js";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import { containerManager } from "../container-manager.js";
import { createSession, SessionCreationError } from "../session-creator.js";
import { parseBody } from "../validation.js";
import * as sessionNames from "../session-names.js";
import * as sessionLinearIssues from "../session-linear-issues.js";
import * as gitUtils from "../git-utils.js";
import { discoverClaudeSessions } from "../claude-session-discovery.js";
import { getClaudeSessionHistoryPage } from "../claude-session-history.js";
import { createLogger } from "../logger.js";
import { notFound, ErrorCode } from "../errors.js";

const log = createLogger("session-routes");

// ─── Zod Schemas ──────────────────────────────────────────────────

const sessionCreateSchema = z.object({
  cwd: z.string().optional(),
  backend: z.enum(["claude", "codex"]).optional().default("claude"),
  model: z.string().max(200).optional(),
  prompt: z.string().optional(),
  resumeSessionAt: z.string().max(500).optional(),
  forkSession: z.boolean().optional(),
  envSlug: z.string().max(200).optional(),
  env: z.record(z.string(), z.string()).optional(),
  container: z.boolean().optional(),
  branch: z.string().max(500).optional(),
  createBranch: z.boolean().optional(),
  useWorktree: z.boolean().optional(),
  permissionMode: z.string().max(100).optional(),
  codexInternetAccess: z.boolean().optional(),
  enableFileCheckpointing: z.boolean().optional(),
  maxTurns: z.number().int().min(1).max(10000).optional(),
  maxBudgetUsd: z.number().min(0.01).max(1000).optional(),
  systemPrompt: z.string().max(50000).optional(),
  appendSystemPrompt: z.string().max(50000).optional(),
  thinking: z.discriminatedUnion("type", [
    z.object({ type: z.literal("adaptive") }),
    z.object({ type: z.literal("enabled"), budgetTokens: z.number().int().min(1024).max(128000) }),
    z.object({ type: z.literal("disabled") }),
  ]).optional(),
  effort: z.enum(["low", "medium", "high", "max"]).optional(),
  disallowedTools: z.array(z.string().max(200)).max(50).optional(),
  additionalDirectories: z.array(z.string().max(1000)).max(20).optional(),
  betas: z.array(z.string().max(100)).max(10).optional(),
}).strip();

const sessionNameSchema = z.object({
  name: z.string().min(1, "name is required").max(200),
});

const sessionArchiveSchema = z.object({
  force: z.boolean().optional(),
}).optional().default({});

const batchCreateSchema = z.object({
  sessions: z.array(sessionCreateSchema).min(1).max(10),
});

// ─── Dependencies ─────────────────────────────────────────────────

export interface SessionRouteDeps {
  launcher: CliLauncher;
  wsBridge: WsBridge;
  sessionStore: SessionStore;
  worktreeTracker: WorktreeTracker;
  prPoller?: import("../pr-poller.js").PRPoller;
}

// ─── Worktree cleanup helper ──────────────────────────────────────

async function cleanupWorktree(
  worktreeTracker: WorktreeTracker,
  sessionId: string,
  force?: boolean,
): Promise<{ cleaned?: boolean; dirty?: boolean; path?: string } | undefined> {
  const mapping = worktreeTracker.getBySession(sessionId);
  if (!mapping) return undefined;

  // Check if other sessions still use this worktree
  if (worktreeTracker.isWorktreeInUse(mapping.worktreePath, sessionId)) {
    worktreeTracker.removeBySession(sessionId);
    return { cleaned: false, path: mapping.worktreePath };
  }

  // Auto-remove if clean, or force-remove if requested
  const dirty = await gitUtils.isWorktreeDirty(mapping.worktreePath);
  if (dirty && !force) {
    return { cleaned: false, dirty: true, path: mapping.worktreePath };
  }

  // Delete zencode-managed branch if it differs from the user-selected branch
  const branchToDelete =
    mapping.actualBranch && mapping.actualBranch !== mapping.branch
      ? mapping.actualBranch
      : undefined;
  const result = await gitUtils.removeWorktree(mapping.repoRoot, mapping.worktreePath, {
    force: dirty,
    branchToDelete,
  });
  if (result.removed) {
    worktreeTracker.removeBySession(sessionId);
  }
  return { cleaned: result.removed, path: mapping.worktreePath };
}

// ─── Routes ───────────────────────────────────────────────────────

export function registerSessionRoutes(api: Hono, deps: SessionRouteDeps): void {
  const { launcher, wsBridge, sessionStore, worktreeTracker, prPoller } = deps;
  const sessionDeps = { launcher, wsBridge, worktreeTracker };

  // ─── Create ─────────────────────────────────────────────────────

  api.post("/sessions/create", async (c) => {
    const parsed = await parseBody(c, sessionCreateSchema);
    if (!parsed.success) return parsed.response;
    try {
      const session = await createSession(parsed.data, sessionDeps);
      return c.json(session);
    } catch (e: unknown) {
      if (e instanceof SessionCreationError) {
        return c.json({ error: e.message }, e.statusCode as ContentfulStatusCode);
      }
      const msg = e instanceof Error ? e.message : String(e);
      log.error({ err: msg }, "Failed to create session");
      return c.json({ error: msg }, 500);
    }
  });

  // ─── SSE Session Creation (with progress streaming) ─────────────

  api.post("/sessions/create-stream", async (c) => {
    const parsed = await parseBody(c, sessionCreateSchema);
    if (!parsed.success) return parsed.response;
    const body = parsed.data;

    return streamSSE(c, async (stream) => {
      try {
        const session = await createSession(body, sessionDeps, (step, label, status, detail) =>
          stream.writeSSE({
            event: "progress",
            data: JSON.stringify({ step, label, status, detail }),
          }),
        );

        await stream.writeSSE({
          event: "done",
          data: JSON.stringify({
            sessionId: session.sessionId,
            state: session.state,
            cwd: session.cwd,
            backendType: session.backendType,
            resumeSessionAt: session.resumeSessionAt,
            forkSession: session.forkSession,
          }),
        });
      } catch (e: unknown) {
        if (e instanceof SessionCreationError) {
          await stream.writeSSE({
            event: "error",
            data: JSON.stringify({
              error: e.message,
              ...(e.step ? { step: e.step } : {}),
            }),
          });
          return;
        }
        const msg = e instanceof Error ? e.message : String(e);
        log.error({ err: msg }, "Failed to create session (stream)");
        await stream.writeSSE({
          event: "error",
          data: JSON.stringify({ error: msg }),
        });
      }
    });
  });

  // ─── Batch Session Creation ─────────────────────────────────────

  api.post("/sessions/batch-create", async (c) => {
    const parsed = await parseBody(c, batchCreateSchema);
    if (!parsed.success) return parsed.response;

    const results = await Promise.allSettled(
      parsed.data.sessions.map((sessionOpts) =>
        createSession(sessionOpts, sessionDeps),
      ),
    );

    const created: Array<{
      sessionId: string;
      state: string;
      cwd: string;
      backendType?: string;
    }> = [];
    const errors: Array<{ index: number; error: string }> = [];

    for (let i = 0; i < results.length; i++) {
      const result = results[i];
      if (result.status === "fulfilled") {
        created.push({
          sessionId: result.value.sessionId,
          state: result.value.state,
          cwd: result.value.cwd,
          backendType: result.value.backendType,
        });
      } else {
        const msg =
          result.reason instanceof SessionCreationError
            ? result.reason.message
            : result.reason instanceof Error
              ? result.reason.message
              : String(result.reason);
        errors.push({ index: i, error: msg });
      }
    }

    return c.json({
      created,
      errors,
      total: parsed.data.sessions.length,
    });
  });

  // ─── List / Get ─────────────────────────────────────────────────

  api.get("/sessions", (c) => {
    const sessions = launcher.listSessions();
    const names = sessionNames.getAllNames();
    const bridgeStates = wsBridge.getAllSessions();
    const bridgeMap = new Map(bridgeStates.map((s) => [s.session_id, s]));
    const enriched = sessions.map((s) => {
      const bridge = bridgeMap.get(s.sessionId);
      return {
        ...s,
        // Bridge state is the source of truth for runtime cwd updates
        // (notably containerized sessions mapped back to host paths).
        cwd: bridge?.cwd || s.cwd,
        name: names[s.sessionId] ?? s.name,
        gitBranch: bridge?.git_branch || "",
        gitAhead: bridge?.git_ahead || 0,
        gitBehind: bridge?.git_behind || 0,
        totalLinesAdded: bridge?.total_lines_added || 0,
        totalLinesRemoved: bridge?.total_lines_removed || 0,
      };
    });
    return c.json(enriched);
  });

  api.get("/sessions/:id", (c) => {
    const id = c.req.param("id");
    const session = launcher.getSession(id);
    if (!session) throw notFound("Session not found", ErrorCode.SESSION_NOT_FOUND);
    return c.json(session);
  });

  // ─── Claude session discovery ───────────────────────────────────

  api.get("/claude/sessions/discover", (c) => {
    const limitRaw = c.req.query("limit");
    const limit = limitRaw ? Number(limitRaw) : undefined;
    const sessions = discoverClaudeSessions({ limit });
    return c.json({ sessions });
  });

  api.get("/claude/sessions/:id/history", (c) => {
    const sessionId = c.req.param("id");
    const limitRaw = c.req.query("limit");
    const cursorRaw = c.req.query("cursor");
    const limit = limitRaw !== undefined ? Number(limitRaw) : undefined;
    const cursor = cursorRaw !== undefined ? Number(cursorRaw) : undefined;

    const page = getClaudeSessionHistoryPage({
      sessionId,
      limit,
      cursor,
    });
    if (!page) {
      return c.json({ error: "Claude session history not found" }, 404);
    }
    return c.json(page);
  });

  // ─── Rename ─────────────────────────────────────────────────────

  api.patch("/sessions/:id/name", async (c) => {
    const id = c.req.param("id");
    const parsed = await parseBody(c, sessionNameSchema);
    if (!parsed.success) return parsed.response;
    const { name } = parsed.data;
    const session = launcher.getSession(id);
    if (!session) throw notFound("Session not found", ErrorCode.SESSION_NOT_FOUND);
    sessionNames.setName(id, name.trim());
    return c.json({ ok: true, name: name.trim() });
  });

  // ─── Kill / Relaunch ───────────────────────────────────────────

  api.post("/sessions/:id/kill", async (c) => {
    const id = c.req.param("id");
    const killed = await launcher.kill(id);
    if (!killed)
      return c.json({ error: "Session not found or already exited" }, 404);

    // Clean up container if any
    await containerManager.removeContainer(id);

    return c.json({ ok: true });
  });

  api.post("/sessions/:id/relaunch", async (c) => {
    const id = c.req.param("id");
    const result = await launcher.relaunch(id);
    if (!result.ok) {
      const status = result.error?.includes("not found") || result.error?.includes("Session not found") ? 404 : 503;
      return c.json({ error: result.error || "Relaunch failed" }, status);
    }
    return c.json({ ok: true });
  });

  // ─── Delete ─────────────────────────────────────────────────────

  api.delete("/sessions/:id", async (c) => {
    const id = c.req.param("id");
    await launcher.kill(id);

    // Clean up container if any
    await containerManager.removeContainer(id);

    const worktreeResult = await cleanupWorktree(worktreeTracker, id, true);
    prPoller?.unwatch(id);
    sessionLinearIssues.removeLinearIssue(id);
    launcher.removeSession(id);
    wsBridge.closeSession(id);
    return c.json({ ok: true, worktree: worktreeResult });
  });

  // ─── Archive / Unarchive ────────────────────────────────────────

  api.post("/sessions/:id/archive", async (c) => {
    const id = c.req.param("id");
    const parsed = await parseBody(c, sessionArchiveSchema);
    if (!parsed.success) return parsed.response;
    const body = parsed.data;
    await launcher.kill(id);

    // Clean up container if any
    await containerManager.removeContainer(id);

    // Stop PR polling for this session
    prPoller?.unwatch(id);

    const worktreeResult = await cleanupWorktree(worktreeTracker, id, body.force);
    launcher.setArchived(id, true);
    await sessionStore.setArchived(id, true);
    return c.json({ ok: true, worktree: worktreeResult });
  });

  api.post("/sessions/:id/unarchive", async (c) => {
    const id = c.req.param("id");
    launcher.setArchived(id, false);
    await sessionStore.setArchived(id, false);
    return c.json({ ok: true });
  });
}
