import type { CapturedLog } from "./schemas";

export const PROVIDER_TEST_SESSION_ID = "provider-test";

export type SessionClientInfo = {
  port: number | null;
  pid: number | null;
  cwd: string | null;
  projectFolder: string | null;
};

export type SessionSource = "explicit" | "client-process" | "client-connection" | "provider-test";

export type SessionIdentity = {
  id: string | null;
  source: SessionSource | null;
};

export type SessionRuntimeMode = "in-process" | "worker-thread" | "child-process";

function getRuntimeMode(): SessionRuntimeMode {
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "in-process";
  const mode = process.env["FINALIZER_RUNTIME"];
  if (mode === "worker") return "worker-thread";
  if (mode === "inline") return "in-process";
  // Default: per-session child process
  return "child-process";
}

export type SessionSnapshot = {
  id: string;
  source: SessionSource;
  runtimeMode: SessionRuntimeMode;
  createdAt: string;
  updatedAt: string;
  requestCount: number;
  activeRequests: number;
  completedRequests: number;
  failedRequests: number;
  queuedTasks: number;
  runningTasks: number;
  lastTaskError: string | null;
  lastLogId: number | null;
  lastModel: string | null;
  clientPid: number | null;
  clientProjectFolder: string | null;
};

type SessionRecord = SessionSnapshot;

const sessions = new Map<string, SessionRecord>();
const knownLogSessions = new Map<number, string>();
const completedLogIds = new Set<number>();
const failedLogIds = new Set<number>();

function normalizeExplicitSessionId(sessionId: string | null | undefined): string | null {
  if (sessionId === null || sessionId === undefined) return null;
  const trimmed = sessionId.trim();
  return trimmed.length > 0 ? trimmed : null;
}

function buildClientSessionId(clientInfo: SessionClientInfo): SessionIdentity {
  const projectFolder = normalizeExplicitSessionId(clientInfo.projectFolder);

  if (clientInfo.pid !== null) {
    return {
      id:
        projectFolder !== null
          ? `process:${clientInfo.pid}:${projectFolder}`
          : `process:${clientInfo.pid}`,
      source: "client-process",
    };
  }

  if (clientInfo.port !== null) {
    return {
      id: `connection:${clientInfo.port}`,
      source: "client-connection",
    };
  }

  return { id: null, source: null };
}

export function resolveSessionIdentity(input: {
  explicitSessionId: string | null;
  clientInfo?: SessionClientInfo;
  isTest?: boolean;
}): SessionIdentity {
  if (input.isTest === true) {
    return { id: PROVIDER_TEST_SESSION_ID, source: "provider-test" };
  }

  const explicit = normalizeExplicitSessionId(input.explicitSessionId);
  if (explicit !== null) {
    return { id: explicit, source: "explicit" };
  }

  if (input.clientInfo !== undefined) {
    return buildClientSessionId(input.clientInfo);
  }

  return { id: null, source: null };
}

export function getLogSessionIdentity(log: CapturedLog): SessionIdentity {
  return resolveSessionIdentity({
    explicitSessionId: log.sessionId,
    clientInfo: {
      port: log.clientPort ?? null,
      pid: log.clientPid ?? null,
      cwd: log.clientCwd ?? null,
      projectFolder: log.clientProjectFolder ?? null,
    },
    isTest: log.isTest,
  });
}

export function getLogSessionId(log: CapturedLog): string | null {
  return getLogSessionIdentity(log).id;
}

function getTimestamp(log: CapturedLog): string {
  return log.timestamp.length > 0 ? log.timestamp : new Date().toISOString();
}

function createSessionRecord(id: string, source: SessionSource, log: CapturedLog): SessionRecord {
  const timestamp = getTimestamp(log);
  return {
    id,
    source,
    runtimeMode: getRuntimeMode(),
    createdAt: timestamp,
    updatedAt: timestamp,
    requestCount: 0,
    activeRequests: 0,
    completedRequests: 0,
    failedRequests: 0,
    queuedTasks: 0,
    runningTasks: 0,
    lastTaskError: null,
    lastLogId: null,
    lastModel: null,
    clientPid: log.clientPid ?? null,
    clientProjectFolder: log.clientProjectFolder ?? null,
  };
}

function upsertSession(
  log: CapturedLog,
  sourceOverride?: SessionSource | null,
): SessionRecord | null {
  const identity = getLogSessionIdentity(log);
  if (identity.id === null) return null;

  const source = sourceOverride ?? identity.source;
  if (source === null) return null;

  const existing = sessions.get(identity.id);
  const session = existing ?? createSessionRecord(identity.id, source, log);
  session.updatedAt = new Date().toISOString();
  session.lastLogId = log.id;
  session.lastModel = log.model ?? session.lastModel;
  session.clientPid = log.clientPid ?? session.clientPid;
  session.clientProjectFolder = log.clientProjectFolder ?? session.clientProjectFolder;

  sessions.set(identity.id, session);
  return session;
}

function rememberLogSession(log: CapturedLog): boolean {
  const sessionId = getLogSessionId(log);
  if (sessionId === null) return false;

  const knownSessionId = knownLogSessions.get(log.id);
  if (knownSessionId === sessionId) return false;

  knownLogSessions.set(log.id, sessionId);
  return true;
}

function isFailedLog(log: CapturedLog): boolean {
  const hasError = log.error !== null && log.error !== undefined && log.error.length > 0;
  const hasErrorStatus = log.responseStatus !== null && log.responseStatus >= 400;
  return hasError || hasErrorStatus;
}

export function markSessionStarted(log: CapturedLog, sourceOverride?: SessionSource | null): void {
  const session = upsertSession(log, sourceOverride);
  if (session === null) return;

  const isNewLog = rememberLogSession(log);
  if (isNewLog) {
    session.requestCount += 1;
  }
  session.activeRequests += 1;
}

export function markSessionFinished(log: CapturedLog, sourceOverride?: SessionSource | null): void {
  const session = upsertSession(log, sourceOverride);
  if (session === null) return;

  if (session.activeRequests > 0) {
    session.activeRequests -= 1;
  }

  if (completedLogIds.has(log.id) === false) {
    completedLogIds.add(log.id);
    session.completedRequests += 1;
  }

  if (isFailedLog(log) && failedLogIds.has(log.id) === false) {
    failedLogIds.add(log.id);
    session.failedRequests += 1;
  }
}

export function markSessionTaskQueued(sessionId: string | null): void {
  if (sessionId === null) return;
  const session = sessions.get(sessionId);
  if (session === undefined) return;
  session.queuedTasks += 1;
  session.updatedAt = new Date().toISOString();
}

export function markSessionTaskStarted(sessionId: string | null): void {
  if (sessionId === null) return;
  const session = sessions.get(sessionId);
  if (session === undefined) return;
  if (session.queuedTasks > 0) {
    session.queuedTasks -= 1;
  }
  session.runningTasks += 1;
  session.updatedAt = new Date().toISOString();
}

export function markSessionTaskFinished(sessionId: string | null, error: string | null): void {
  if (sessionId === null) return;
  const session = sessions.get(sessionId);
  if (session === undefined) return;
  if (session.runningTasks > 0) {
    session.runningTasks -= 1;
  }
  session.lastTaskError = error;
  session.updatedAt = new Date().toISOString();
}

export function observeSessionLog(log: CapturedLog, sourceOverride?: SessionSource | null): void {
  const session = upsertSession(log, sourceOverride);
  if (session === null) return;

  const isNewLog = rememberLogSession(log);
  if (isNewLog) {
    session.requestCount += 1;
  }

  if (log.responseStatus !== null || log.responseText !== null) {
    markSessionFinished(log, sourceOverride);
  }
}

export function clearSessionRegistry(): void {
  sessions.clear();
  knownLogSessions.clear();
  completedLogIds.clear();
  failedLogIds.clear();
}

export function rebuildSessionRegistry(logs: Iterable<CapturedLog>): void {
  clearSessionRegistry();
  for (const log of logs) {
    observeSessionLog(log);
  }
}

export function getSessionIds(): string[] {
  return [...sessions.values()].map((session) => session.id);
}

export function getSessionSnapshots(): SessionSnapshot[] {
  return [...sessions.values()].map((session) => ({ ...session }));
}
