import type { RuntimeProcessTableRow, TmuxPaneRuntimeInfo } from '@features/tmux-installer/main';
import type {
  MemberSpawnStatusEntry,
  TeamAgentRuntimeBackendType,
  TeamAgentRuntimeDiagnosticSeverity,
  TeamAgentRuntimeLivenessKind,
  TeamAgentRuntimePidSource,
  TeamProviderId,
} from '@shared/types';

export interface ResolveTeamMemberRuntimeLivenessInput {
  teamName: string;
  memberName: string;
  agentId?: string;
  backendType?: TeamAgentRuntimeBackendType;
  providerId?: TeamProviderId;
  tmuxPaneId?: string;
  persistedRuntimePid?: number;
  persistedRuntimeSessionId?: string;
  trackedSpawnStatus?: MemberSpawnStatusEntry;
  runtimePid?: number;
  runtimeSessionId?: string;
  pane?: TmuxPaneRuntimeInfo;
  processRows: readonly RuntimeProcessTableRow[];
  processTableAvailable: boolean;
  nowIso: string;
}

export interface ResolvedTeamMemberRuntimeLiveness {
  alive: boolean;
  livenessKind: TeamAgentRuntimeLivenessKind;
  pidSource?: TeamAgentRuntimePidSource;
  pid?: number;
  metricsPid?: number;
  panePid?: number;
  paneCurrentCommand?: string;
  processCommand?: string;
  runtimeSessionId?: string;
  runtimeLastSeenAt?: string;
  runtimeDiagnostic: string;
  runtimeDiagnosticSeverity: TeamAgentRuntimeDiagnosticSeverity;
  diagnostics: string[];
}

const SHELL_COMMAND_NAMES = new Set(['sh', 'bash', 'zsh', 'fish', 'dash', 'login', 'tmux']);
const SECRET_FLAG_PATTERN =
  /(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi;
const CLI_ARG_VALUES_CACHE_MAX_COMMANDS = 1_000;
const CLI_ARG_EQUALS_CACHE_MAX_KEYS_PER_COMMAND = 100;
const cliArgValuesCache = new Map<string, Map<string, string[]>>();
const cliArgEqualsCache = new Map<string, Map<string, boolean>>();

function basenameCommand(command: string | undefined): string {
  const firstToken = command?.trim().split(/\s+/, 1)[0] ?? '';
  const base = firstToken.split(/[\\/]/).pop() ?? firstToken;
  return base.replace(/^-/, '').toLowerCase();
}

export function isShellLikeCommand(command: string | undefined): boolean {
  return SHELL_COMMAND_NAMES.has(basenameCommand(command));
}

export function sanitizeProcessCommandForDiagnostics(
  command: string | undefined
): string | undefined {
  const trimmed = command?.trim();
  if (!trimmed) return undefined;
  return trimmed.replace(SECRET_FLAG_PATTERN, '$1[redacted]').slice(0, 500);
}

function escapeRegexLiteral(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function getCachedCliArgValues(command: string, argName: string): readonly string[] {
  if (!command.includes(argName)) {
    return [];
  }

  const cachedByArg = cliArgValuesCache.get(command);
  const cachedValues = cachedByArg?.get(argName);
  if (cachedValues) {
    if (cachedByArg) {
      cliArgValuesCache.delete(command);
      cliArgValuesCache.set(command, cachedByArg);
    }
    return cachedValues;
  }

  const escapedArg = escapeRegexLiteral(argName);
  const pattern = new RegExp(
    `(?:^|\\s)${escapedArg}(?:=|\\s+)("([^"]*)"|'([^']*)'|([^\\s]+))`,
    'g'
  );

  const values: string[] = [];
  for (const match of command.matchAll(pattern)) {
    const value = (match[2] ?? match[3] ?? match[4] ?? '').trim();
    if (value) values.push(value);
  }
  const nextByArg = cachedByArg ?? new Map<string, string[]>();
  nextByArg.set(argName, values);
  cliArgValuesCache.delete(command);
  cliArgValuesCache.set(command, nextByArg);
  while (cliArgValuesCache.size > CLI_ARG_VALUES_CACHE_MAX_COMMANDS) {
    const oldestKey = cliArgValuesCache.keys().next().value;
    if (oldestKey === undefined) break;
    cliArgValuesCache.delete(oldestKey);
  }
  return values;
}

function getCachedCliArgEquals(
  command: string,
  argName: string,
  normalizedExpected: string
): boolean | undefined {
  const cachedByKey = cliArgEqualsCache.get(command);
  if (!cachedByKey) {
    return undefined;
  }
  const cacheKey = `${argName}\0${normalizedExpected}`;
  const cached = cachedByKey.get(cacheKey);
  if (cached !== undefined) {
    cliArgEqualsCache.delete(command);
    cliArgEqualsCache.set(command, cachedByKey);
  }
  return cached;
}

function setCachedCliArgEquals(
  command: string,
  argName: string,
  normalizedExpected: string,
  value: boolean
): void {
  let cachedByKey = cliArgEqualsCache.get(command);
  if (!cachedByKey) {
    cachedByKey = new Map<string, boolean>();
  }
  const cacheKey = `${argName}\0${normalizedExpected}`;
  if (!cachedByKey.has(cacheKey) && cachedByKey.size >= CLI_ARG_EQUALS_CACHE_MAX_KEYS_PER_COMMAND) {
    const oldestKey = cachedByKey.keys().next().value;
    if (oldestKey !== undefined) {
      cachedByKey.delete(oldestKey);
    }
  }
  cachedByKey.set(cacheKey, value);
  cliArgEqualsCache.delete(command);
  cliArgEqualsCache.set(command, cachedByKey);
  while (cliArgEqualsCache.size > CLI_ARG_VALUES_CACHE_MAX_COMMANDS) {
    const oldestCommand = cliArgEqualsCache.keys().next().value;
    if (oldestCommand === undefined) break;
    cliArgEqualsCache.delete(oldestCommand);
  }
}

export function extractCliArgValues(command: string, argName: string): string[] {
  const values = getCachedCliArgValues(command, argName);
  return [...values];
}

export function commandArgEquals(
  command: string,
  argName: string,
  expected: string | undefined
): boolean {
  const normalizedExpected = expected?.trim();
  if (!normalizedExpected) return false;
  if (!command.includes(argName)) return false;
  if (!command.includes(normalizedExpected)) return false;
  const cached = getCachedCliArgEquals(command, argName, normalizedExpected);
  if (cached !== undefined) {
    return cached;
  }
  const value = getCachedCliArgValues(command, argName).some(
    (argValue) => argValue === normalizedExpected
  );
  setCachedCliArgEquals(command, argName, normalizedExpected, value);
  return value;
}

function collectDescendants(
  rows: readonly RuntimeProcessTableRow[],
  rootPid: number
): RuntimeProcessTableRow[] {
  const childrenByParent = new Map<number, RuntimeProcessTableRow[]>();
  for (const row of rows) {
    const current = childrenByParent.get(row.ppid) ?? [];
    current.push(row);
    childrenByParent.set(row.ppid, current);
  }

  const descendants: RuntimeProcessTableRow[] = [];
  const queue = [...(childrenByParent.get(rootPid) ?? [])];
  const seen = new Set<number>();
  while (queue.length > 0) {
    const row = queue.shift();
    if (!row || seen.has(row.pid)) continue;
    seen.add(row.pid);
    descendants.push(row);
    queue.push(...(childrenByParent.get(row.pid) ?? []));
  }
  return descendants;
}

function isVerifiedRuntimeProcess(params: {
  row: RuntimeProcessTableRow;
  teamName: string;
  agentId?: string;
}): boolean {
  return (
    commandArgEquals(params.row.command, '--team-name', params.teamName) &&
    commandArgEquals(params.row.command, '--agent-id', params.agentId)
  );
}

function findNewestVerifiedRuntimeProcess(params: {
  rows: readonly RuntimeProcessTableRow[];
  teamName: string;
  agentId?: string;
}): RuntimeProcessTableRow | undefined {
  const agentId = params.agentId?.trim();
  if (!agentId) {
    return undefined;
  }

  let newest: RuntimeProcessTableRow | undefined;
  for (const row of params.rows) {
    if (!isVerifiedRuntimeProcess({ row, teamName: params.teamName, agentId })) {
      continue;
    }
    if (!newest || row.pid > newest.pid) {
      newest = row;
    }
  }
  return newest;
}

function isOpenCodeRuntimeProcess(command: string | undefined): boolean {
  return (command ?? '').toLowerCase().includes('opencode');
}

function hasPersistedEvidence(input: ResolveTeamMemberRuntimeLivenessInput): boolean {
  return Boolean(
    input.agentId?.trim() ||
    input.tmuxPaneId?.trim() ||
    input.persistedRuntimePid ||
    input.runtimePid ||
    input.persistedRuntimeSessionId?.trim() ||
    input.runtimeSessionId?.trim() ||
    input.backendType
  );
}

function result(params: {
  alive: boolean;
  livenessKind: TeamAgentRuntimeLivenessKind;
  runtimeDiagnostic: string;
  runtimeDiagnosticSeverity?: TeamAgentRuntimeDiagnosticSeverity;
  diagnostics?: string[];
  pidSource?: TeamAgentRuntimePidSource;
  pid?: number;
  metricsPid?: number;
  panePid?: number;
  paneCurrentCommand?: string;
  processCommand?: string;
  runtimeSessionId?: string;
  runtimeLastSeenAt?: string;
}): ResolvedTeamMemberRuntimeLiveness {
  return {
    alive: params.alive,
    livenessKind: params.livenessKind,
    runtimeDiagnostic: params.runtimeDiagnostic,
    runtimeDiagnosticSeverity: params.runtimeDiagnosticSeverity ?? 'info',
    diagnostics: params.diagnostics ?? [params.runtimeDiagnostic],
    ...(params.pidSource ? { pidSource: params.pidSource } : {}),
    ...(typeof params.pid === 'number' && params.pid > 0 ? { pid: params.pid } : {}),
    ...(typeof params.metricsPid === 'number' && params.metricsPid > 0
      ? { metricsPid: params.metricsPid }
      : {}),
    ...(typeof params.panePid === 'number' && params.panePid > 0
      ? { panePid: params.panePid }
      : {}),
    ...(params.paneCurrentCommand ? { paneCurrentCommand: params.paneCurrentCommand } : {}),
    ...(params.processCommand ? { processCommand: params.processCommand } : {}),
    ...(params.runtimeSessionId ? { runtimeSessionId: params.runtimeSessionId } : {}),
    ...(params.runtimeLastSeenAt ? { runtimeLastSeenAt: params.runtimeLastSeenAt } : {}),
  };
}

export function resolveTeamMemberRuntimeLiveness(
  input: ResolveTeamMemberRuntimeLivenessInput
): ResolvedTeamMemberRuntimeLiveness {
  const tracked = input.trackedSpawnStatus;
  const runtimeSessionId = input.runtimeSessionId ?? input.persistedRuntimeSessionId;
  const hasConfirmedBootstrap =
    tracked?.bootstrapConfirmed === true || tracked?.launchState === 'confirmed_alive';
  const diagnostics: string[] = [];
  if (!input.processTableAvailable) {
    diagnostics.push('process table unavailable');
  }

  if (
    tracked?.launchState === 'runtime_pending_permission' ||
    (tracked?.pendingPermissionRequestIds?.length ?? 0) > 0
  ) {
    return result({
      alive: false,
      livenessKind: 'permission_blocked',
      runtimeSessionId,
      runtimeDiagnostic: 'waiting for permission approval',
      runtimeDiagnosticSeverity: 'warning',
      diagnostics: [...diagnostics, 'permission approval pending'],
    });
  }

  const verifiedProcess = findNewestVerifiedRuntimeProcess({
    rows: input.processRows,
    teamName: input.teamName,
    agentId: input.agentId,
  });
  if (verifiedProcess) {
    return result({
      alive: true,
      livenessKind: 'runtime_process',
      pidSource: 'agent_process_table',
      pid: verifiedProcess.pid,
      runtimeSessionId,
      processCommand: sanitizeProcessCommandForDiagnostics(verifiedProcess.command),
      runtimeDiagnostic: 'verified runtime process detected',
      diagnostics: [...diagnostics, 'matched process table by team-name and agent-id'],
    });
  }

  const runtimePid = input.runtimePid ?? input.persistedRuntimePid;
  const runtimePidRow =
    typeof runtimePid === 'number' && runtimePid > 0
      ? input.processRows.find((row) => row.pid === runtimePid)
      : undefined;
  if (runtimePidRow && input.providerId === 'opencode') {
    const processCommand = sanitizeProcessCommandForDiagnostics(runtimePidRow.command);
    if (isOpenCodeRuntimeProcess(runtimePidRow.command)) {
      if (hasConfirmedBootstrap) {
        return result({
          alive: true,
          livenessKind: 'runtime_process',
          pidSource: 'opencode_bridge',
          pid: runtimePidRow.pid,
          runtimeSessionId,
          processCommand,
          runtimeLastSeenAt: tracked?.lastHeartbeatAt ?? tracked?.updatedAt,
          runtimeDiagnostic: 'OpenCode runtime process detected after bootstrap confirmation',
          diagnostics: [
            ...diagnostics,
            'matched OpenCode runtime pid and process identity',
            'bootstrap confirmed',
          ],
        });
      }
      return result({
        alive: false,
        livenessKind: 'runtime_process_candidate',
        pidSource: 'opencode_bridge',
        pid: runtimePidRow.pid,
        runtimeSessionId,
        processCommand,
        runtimeDiagnostic:
          'OpenCode runtime process detected, but teammate bootstrap is not confirmed',
        runtimeDiagnosticSeverity: 'warning',
        diagnostics: [
          ...diagnostics,
          'matched OpenCode runtime pid and process identity',
          'waiting for teammate bootstrap confirmation',
        ],
      });
    }
    return result({
      alive: hasConfirmedBootstrap,
      livenessKind: hasConfirmedBootstrap ? 'confirmed_bootstrap' : 'runtime_process_candidate',
      pidSource: hasConfirmedBootstrap ? 'runtime_bootstrap' : 'opencode_bridge',
      pid: hasConfirmedBootstrap ? undefined : runtimePidRow.pid,
      runtimeSessionId,
      processCommand: hasConfirmedBootstrap ? undefined : processCommand,
      runtimeLastSeenAt: hasConfirmedBootstrap
        ? (tracked?.lastHeartbeatAt ?? tracked?.updatedAt)
        : undefined,
      runtimeDiagnostic: hasConfirmedBootstrap
        ? 'bootstrap confirmed; runtime pid currently points to a different process'
        : 'OpenCode runtime pid is alive, but process identity is unverified',
      runtimeDiagnosticSeverity: hasConfirmedBootstrap ? 'info' : 'warning',
      diagnostics: [
        ...diagnostics,
        hasConfirmedBootstrap
          ? 'bootstrap confirmed despite runtime pid identity mismatch'
          : 'matched OpenCode runtime pid without OpenCode process identity',
      ],
    });
  }

  if (hasConfirmedBootstrap) {
    return result({
      alive: true,
      livenessKind: 'confirmed_bootstrap',
      pidSource: 'runtime_bootstrap',
      runtimeSessionId,
      runtimeLastSeenAt: tracked.lastHeartbeatAt ?? tracked.updatedAt,
      runtimeDiagnostic: 'bootstrap confirmed',
      diagnostics: [...diagnostics, 'bootstrap confirmed'],
    });
  }

  const pane = input.pane;
  if (pane) {
    const descendants = collectDescendants(input.processRows, pane.panePid);
    const verifiedDescendant = findNewestVerifiedRuntimeProcess({
      rows: descendants,
      teamName: input.teamName,
      agentId: input.agentId,
    });
    if (verifiedDescendant) {
      return result({
        alive: true,
        livenessKind: 'runtime_process',
        pidSource: 'tmux_child',
        pid: verifiedDescendant.pid,
        panePid: pane.panePid,
        paneCurrentCommand: pane.currentCommand,
        runtimeSessionId,
        processCommand: sanitizeProcessCommandForDiagnostics(verifiedDescendant.command),
        runtimeDiagnostic: 'verified tmux runtime child detected',
        diagnostics: [...diagnostics, 'matched tmux descendant by team-name and agent-id'],
      });
    }

    const candidate = descendants.find((row) => !isShellLikeCommand(row.command));
    if (candidate) {
      return result({
        alive: false,
        livenessKind: 'runtime_process_candidate',
        pidSource: 'tmux_child',
        pid: candidate.pid,
        panePid: pane.panePid,
        paneCurrentCommand: pane.currentCommand,
        runtimeSessionId,
        processCommand: sanitizeProcessCommandForDiagnostics(candidate.command),
        runtimeDiagnostic: 'Runtime process candidate detected, but bootstrap is unconfirmed.',
        runtimeDiagnosticSeverity: 'warning',
        diagnostics: [...diagnostics, 'tmux descendant found without runtime identity match'],
      });
    }

    const shellOnly = isShellLikeCommand(pane.currentCommand);
    return result({
      alive: false,
      livenessKind: shellOnly ? 'shell_only' : 'runtime_process_candidate',
      pidSource: 'tmux_pane',
      pid: pane.panePid,
      panePid: pane.panePid,
      paneCurrentCommand: pane.currentCommand,
      runtimeSessionId,
      runtimeDiagnostic: shellOnly
        ? `tmux pane foreground command is ${pane.currentCommand ?? 'a shell'}`
        : 'tmux pane is alive, but runtime identity is not verified',
      runtimeDiagnosticSeverity: shellOnly ? 'warning' : 'info',
      diagnostics: [
        ...diagnostics,
        shellOnly
          ? `tmux pane is alive, but foreground command is ${pane.currentCommand ?? 'a shell'}`
          : 'tmux pane exists, but no verified runtime process was found',
      ],
    });
  }

  if (runtimePid && !runtimePidRow) {
    if (!input.processTableAvailable) {
      return result({
        alive: false,
        livenessKind: 'registered_only',
        pidSource: 'persisted_metadata',
        pid: runtimePid,
        runtimeSessionId,
        runtimeDiagnostic: 'runtime pid could not be verified because process table is unavailable',
        runtimeDiagnosticSeverity: 'warning',
        diagnostics: [...diagnostics, 'runtime pid could not be verified'],
      });
    }
    return result({
      alive: false,
      livenessKind: 'stale_metadata',
      pidSource: 'persisted_metadata',
      pid: runtimePid,
      runtimeSessionId,
      runtimeDiagnostic: 'persisted runtime pid is not alive',
      runtimeDiagnosticSeverity: 'warning',
      diagnostics: [...diagnostics, 'persisted runtime pid was not found in process table'],
    });
  }

  if (hasPersistedEvidence(input)) {
    return result({
      alive: false,
      livenessKind: 'registered_only',
      runtimeSessionId,
      runtimeDiagnostic: 'registered runtime metadata without live process',
      runtimeDiagnosticSeverity: 'warning',
      diagnostics: [...diagnostics, 'member has persisted runtime metadata only'],
    });
  }

  return result({
    alive: false,
    livenessKind: 'not_found',
    runtimeDiagnostic: 'runtime process not found',
    runtimeDiagnosticSeverity: 'warning',
    diagnostics: [...diagnostics, 'runtime process not found'],
  });
}

export function isStrongRuntimeEvidence(
  value: { livenessKind?: TeamAgentRuntimeLivenessKind } | undefined
): boolean {
  return value?.livenessKind === 'confirmed_bootstrap' || value?.livenessKind === 'runtime_process';
}
