import { randomUUID } from "node:crypto";
import fs from "node:fs";
import {
  abortEmbeddedPiRun,
  isEmbeddedPiRunActive,
  resolveEmbeddedSessionLane,
  waitForEmbeddedPiRunEnd,
} from "../agents/pi-embedded.js";
import { loadConfig } from "../config/config.js";
import {
  resolveMainSessionKeyFromConfig,
  type SessionEntry,
  updateSessionStore,
} from "../config/sessions.js";
import { clearCommandLane } from "../process/command-queue.js";
import {
  ErrorCodes,
  formatValidationErrors,
  type SessionsCompactParams,
  type SessionsDeleteParams,
  type SessionsListParams,
  type SessionsPatchParams,
  type SessionsResetParams,
  type SessionsResolveParams,
  validateSessionsCompactParams,
  validateSessionsDeleteParams,
  validateSessionsListParams,
  validateSessionsPatchParams,
  validateSessionsResetParams,
  validateSessionsResolveParams,
} from "./protocol/index.js";
import type { BridgeMethodHandler } from "./server-bridge-types.js";
import {
  archiveFileOnDisk,
  listSessionsFromStore,
  loadCombinedSessionStoreForGateway,
  loadSessionEntry,
  resolveGatewaySessionStoreTarget,
  resolveSessionTranscriptCandidates,
  type SessionsPatchResult,
} from "./session-utils.js";
import { applySessionsPatchToStore } from "./sessions-patch.js";
import { resolveSessionKeyFromResolveParams } from "./sessions-resolve.js";

export const handleSessionsBridgeMethods: BridgeMethodHandler = async (
  ctx,
  _nodeId,
  method,
  params,
) => {
  switch (method) {
    case "sessions.list": {
      if (!validateSessionsListParams(params)) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `invalid sessions.list params: ${formatValidationErrors(validateSessionsListParams.errors)}`,
          },
        };
      }
      const p = params as SessionsListParams;
      const cfg = loadConfig();
      const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
      const result = listSessionsFromStore({
        cfg,
        storePath,
        store,
        opts: p,
      });
      return { ok: true, payloadJSON: JSON.stringify(result) };
    }
    case "sessions.resolve": {
      if (!validateSessionsResolveParams(params)) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `invalid sessions.resolve params: ${formatValidationErrors(validateSessionsResolveParams.errors)}`,
          },
        };
      }

      const p = params as SessionsResolveParams;
      const cfg = loadConfig();
      const resolved = resolveSessionKeyFromResolveParams({ cfg, p });
      if (!resolved.ok) {
        return {
          ok: false,
          error: {
            code: resolved.error.code,
            message: resolved.error.message,
            details: resolved.error.details,
          },
        };
      }
      return {
        ok: true,
        payloadJSON: JSON.stringify({ ok: true, key: resolved.key }),
      };
    }
    case "sessions.patch": {
      if (!validateSessionsPatchParams(params)) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `invalid sessions.patch params: ${formatValidationErrors(validateSessionsPatchParams.errors)}`,
          },
        };
      }

      const p = params as SessionsPatchParams;
      const key = String(p.key ?? "").trim();
      if (!key) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: "key required",
          },
        };
      }

      const cfg = loadConfig();
      const target = resolveGatewaySessionStoreTarget({ cfg, key });
      const storePath = target.storePath;
      const applied = await updateSessionStore(storePath, async (store) => {
        const primaryKey = target.storeKeys[0] ?? key;
        const existingKey = target.storeKeys.find((candidate) => store[candidate]);
        if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
          store[primaryKey] = store[existingKey];
          delete store[existingKey];
        }
        return await applySessionsPatchToStore({
          cfg,
          store,
          storeKey: primaryKey,
          patch: p,
          loadGatewayModelCatalog: ctx.loadGatewayModelCatalog,
        });
      });
      if (!applied.ok) {
        return {
          ok: false,
          error: {
            code: applied.error.code,
            message: applied.error.message,
            details: applied.error.details,
          },
        };
      }
      const payload: SessionsPatchResult = {
        ok: true,
        path: storePath,
        key: target.canonicalKey,
        entry: applied.entry,
      };
      return { ok: true, payloadJSON: JSON.stringify(payload) };
    }
    case "sessions.reset": {
      if (!validateSessionsResetParams(params)) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `invalid sessions.reset params: ${formatValidationErrors(validateSessionsResetParams.errors)}`,
          },
        };
      }

      const p = params as SessionsResetParams;
      const key = String(p.key ?? "").trim();
      if (!key) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: "key required",
          },
        };
      }

      const cfg = loadConfig();
      const target = resolveGatewaySessionStoreTarget({ cfg, key });
      const storePath = target.storePath;
      const next = await updateSessionStore(storePath, (store) => {
        const primaryKey = target.storeKeys[0] ?? key;
        const existingKey = target.storeKeys.find((candidate) => store[candidate]);
        if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
          store[primaryKey] = store[existingKey];
          delete store[existingKey];
        }
        const entry = store[primaryKey];
        const now = Date.now();
        const nextEntry: SessionEntry = {
          sessionId: randomUUID(),
          updatedAt: now,
          systemSent: false,
          abortedLastRun: false,
          thinkingLevel: entry?.thinkingLevel,
          verboseLevel: entry?.verboseLevel,
          reasoningLevel: entry?.reasoningLevel,
          model: entry?.model,
          contextTokens: entry?.contextTokens,
          sendPolicy: entry?.sendPolicy,
          label: entry?.label,
          displayName: entry?.displayName,
          chatType: entry?.chatType,
          channel: entry?.channel,
          subject: entry?.subject,
          room: entry?.room,
          space: entry?.space,
          lastChannel: entry?.lastChannel,
          lastTo: entry?.lastTo,
          skillsSnapshot: entry?.skillsSnapshot,
        };
        store[primaryKey] = nextEntry;
        return nextEntry;
      });
      return {
        ok: true,
        payloadJSON: JSON.stringify({ ok: true, key, entry: next }),
      };
    }
    case "sessions.delete": {
      if (!validateSessionsDeleteParams(params)) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `invalid sessions.delete params: ${formatValidationErrors(validateSessionsDeleteParams.errors)}`,
          },
        };
      }

      const p = params as SessionsDeleteParams;
      const key = String(p.key ?? "").trim();
      if (!key) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: "key required",
          },
        };
      }

      const mainKey = resolveMainSessionKeyFromConfig();
      if (key === mainKey) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `Cannot delete the main session (${mainKey}).`,
          },
        };
      }

      const deleteTranscript = typeof p.deleteTranscript === "boolean" ? p.deleteTranscript : true;

      const cfg = loadConfig();
      const target = resolveGatewaySessionStoreTarget({ cfg, key });
      const storePath = target.storePath;
      const { entry } = loadSessionEntry(key);
      const sessionId = entry?.sessionId;
      clearCommandLane(resolveEmbeddedSessionLane(key));
      if (sessionId && isEmbeddedPiRunActive(sessionId)) {
        abortEmbeddedPiRun(sessionId);
        const ended = await waitForEmbeddedPiRunEnd(sessionId, 15_000);
        if (!ended) {
          return {
            ok: false,
            error: {
              code: ErrorCodes.UNAVAILABLE,
              message: `Session ${key} is still active; try again in a moment.`,
            },
          };
        }
      }
      const deletion = await updateSessionStore(storePath, (store) => {
        const primaryKey = target.storeKeys[0] ?? key;
        const existingKey = target.storeKeys.find((candidate) => store[candidate]);
        if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
          store[primaryKey] = store[existingKey];
          delete store[existingKey];
        }
        const entryToDelete = store[primaryKey];
        const existed = Boolean(entryToDelete);
        if (existed) delete store[primaryKey];
        return { existed, entry: entryToDelete };
      });
      const existed = deletion.existed;

      const archived: string[] = [];
      if (deleteTranscript && sessionId) {
        for (const candidate of resolveSessionTranscriptCandidates(
          sessionId,
          storePath,
          entry?.sessionFile,
        )) {
          if (!fs.existsSync(candidate)) continue;
          try {
            archived.push(archiveFileOnDisk(candidate, "deleted"));
          } catch {
            // Best-effort; deleting the store entry is the main operation.
          }
        }
      }

      return {
        ok: true,
        payloadJSON: JSON.stringify({
          ok: true,
          key,
          deleted: existed,
          archived,
        }),
      };
    }
    case "sessions.compact": {
      if (!validateSessionsCompactParams(params)) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: `invalid sessions.compact params: ${formatValidationErrors(validateSessionsCompactParams.errors)}`,
          },
        };
      }

      const p = params as SessionsCompactParams;
      const key = String(p.key ?? "").trim();
      if (!key) {
        return {
          ok: false,
          error: {
            code: ErrorCodes.INVALID_REQUEST,
            message: "key required",
          },
        };
      }

      const maxLines =
        typeof p.maxLines === "number" && Number.isFinite(p.maxLines)
          ? Math.max(1, Math.floor(p.maxLines))
          : 400;

      const cfg = loadConfig();
      const target = resolveGatewaySessionStoreTarget({ cfg, key });
      const storePath = target.storePath;
      // Resolve entry inside the lock, but compact outside to avoid holding it.
      const compactTarget = await updateSessionStore(storePath, (store) => {
        const primaryKey = target.storeKeys[0] ?? key;
        const existingKey = target.storeKeys.find((candidate) => store[candidate]);
        if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
          store[primaryKey] = store[existingKey];
          delete store[existingKey];
        }
        return { entry: store[primaryKey], primaryKey };
      });
      const entry = compactTarget.entry;
      const sessionId = entry?.sessionId;
      if (!sessionId) {
        return {
          ok: true,
          payloadJSON: JSON.stringify({
            ok: true,
            key,
            compacted: false,
            reason: "no sessionId",
          }),
        };
      }

      const filePath = resolveSessionTranscriptCandidates(
        sessionId,
        storePath,
        entry?.sessionFile,
      ).find((candidate) => fs.existsSync(candidate));
      if (!filePath) {
        return {
          ok: true,
          payloadJSON: JSON.stringify({
            ok: true,
            key,
            compacted: false,
            reason: "no transcript",
          }),
        };
      }

      const raw = fs.readFileSync(filePath, "utf-8");
      const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
      if (lines.length <= maxLines) {
        return {
          ok: true,
          payloadJSON: JSON.stringify({
            ok: true,
            key,
            compacted: false,
            kept: lines.length,
          }),
        };
      }

      const archived = archiveFileOnDisk(filePath, "bak");
      const keptLines = lines.slice(-maxLines);
      fs.writeFileSync(filePath, `${keptLines.join("\n")}\n`, "utf-8");

      // Token counts no longer match; clear so status + UI reflect reality after the next turn.
      await updateSessionStore(storePath, (store) => {
        const entryToUpdate = store[compactTarget.primaryKey];
        if (!entryToUpdate) return;
        delete entryToUpdate.inputTokens;
        delete entryToUpdate.outputTokens;
        delete entryToUpdate.totalTokens;
        entryToUpdate.updatedAt = Date.now();
      });

      return {
        ok: true,
        payloadJSON: JSON.stringify({
          ok: true,
          key,
          compacted: true,
          archived,
          kept: keptLines.length,
        }),
      };
    }
    default:
      return null;
  }
};
