'use strict';

const fs = require('fs');
const path = require('path');
const {
  canResumeAgent,
  getResumeSpec: defaultGetResumeSpec,
  normalizeAgentType,
} = require('./agent-capabilities');
const { resolveClaudeResumeCwd } = require('./resume-preflight');
const { normalizeCwd, samePath } = require('./resume-cwd');

function normalizeArgs(args) {
  if (Array.isArray(args)) return args;
  if (typeof args === 'string') {
    try {
      const parsed = JSON.parse(args);
      return Array.isArray(parsed) ? parsed : [];
    } catch {
      return [];
    }
  }
  return [];
}

function rowProviderMatches(row, agentType) {
  const wanted = normalizeAgentType(agentType);
  if (!wanted) return true;
  const rowType = normalizeAgentType(row?.provider || '');
  return !rowType || rowType === wanted;
}

function rowHasTranscript(row, fsExists, fsStat) {
  if (!row) return false;
  if (Number(row.file_size || 0) > 0) return true;
  const jsonlPath = String(row.jsonl_path || '').trim();
  if (!jsonlPath) return false;
  try {
    if (!fsExists(jsonlPath)) return false;
    return fsStat(jsonlPath).size > 0;
  } catch {
    return false;
  }
}

function orderedAgentRows(db, ctmSessionId) {
  if (!db || !ctmSessionId) return [];
  return db.prepare(`
    SELECT *
    FROM agent_sessions
    WHERE ctm_session_id = ?
      AND agent_session_id IS NOT NULL
      AND agent_session_id != ''
    ORDER BY COALESCE(NULLIF(modified_at, ''), updated_at, created_at) DESC,
      updated_at DESC,
      created_at DESC
  `).all(ctmSessionId);
}

function providerResumeIdBelongsToOtherCtm(db, providerResumeId, ctmSessionId) {
  const resumeId = String(providerResumeId || '').trim();
  const ctmId = String(ctmSessionId || '').trim();
  if (!db || !resumeId || !ctmId) return false;
  try {
    const owner = db.prepare('SELECT ctm_session_id FROM agent_sessions WHERE agent_session_id = ?').get(resumeId);
    return !!(owner?.ctm_session_id && owner.ctm_session_id !== ctmId);
  } catch {
    return false;
  }
}

function providerResumeIdPointsToSiblingAgent(db, providerResumeId, ctmSessionId, agentSessionId) {
  const resumeId = String(providerResumeId || '').trim();
  const ctmId = String(ctmSessionId || '').trim();
  const agentId = String(agentSessionId || '').trim();
  if (!db || !resumeId || !ctmId || !agentId || resumeId === agentId) return false;
  try {
    const owner = db.prepare('SELECT ctm_session_id FROM agent_sessions WHERE agent_session_id = ?').get(resumeId);
    return owner?.ctm_session_id === ctmId;
  } catch {
    return false;
  }
}

function resumeSessionIdForRow(row, agentType, opts = {}) {
  const normalizedAgentType = normalizeAgentType(agentType);
  const agentSessionId = String(row?.agent_session_id || '').trim();
  if (normalizedAgentType === 'codex') {
    const providerResumeId = String(row?.provider_resume_id || '').trim();
    if (
      providerResumeId
      && providerResumeId !== agentSessionId
      && !providerResumeIdBelongsToOtherCtm(opts.db, providerResumeId, opts.ctmSessionId)
    ) {
      return providerResumeId;
    }
    return agentSessionId;
  }
  return agentSessionId;
}

function selectRestoreAgentSession({
  db,
  ctmSessionId,
  agentType,
  fsExists = fs.existsSync,
  fsStat = fs.statSync,
  isKnownCodexThreadId = () => true,
} = {}) {
  const normalizedAgentType = normalizeAgentType(agentType);
  const invalidAgentSessionIds = [];
  if (!ctmSessionId || !normalizedAgentType || !canResumeAgent(normalizedAgentType)) {
    return { row: null, invalidAgentSessionIds };
  }

  for (const row of orderedAgentRows(db, ctmSessionId)) {
    const agentSessionId = String(row.agent_session_id || '').trim();
    const resumeSessionId = resumeSessionIdForRow(row, normalizedAgentType, { db, ctmSessionId });
    if (!agentSessionId) continue;
    if (!rowProviderMatches(row, normalizedAgentType)) continue;
    if (
      normalizedAgentType === 'codex'
      && providerResumeIdPointsToSiblingAgent(db, row.provider_resume_id, ctmSessionId, agentSessionId)
    ) {
      continue;
    }
    if (normalizedAgentType === 'codex' && !isKnownCodexThreadId(resumeSessionId)) {
      invalidAgentSessionIds.push(agentSessionId);
      continue;
    }

    // Gemini resume is keyed by the provider session ID and CTM does not have
    // a durable JSONL transcript for it. Claude/Codex need an actual transcript
    // row to avoid resuming an empty stub created before the agent wrote state.
    if (normalizedAgentType !== 'gemini' && !rowHasTranscript(row, fsExists, fsStat)) {
      continue;
    }

    const jsonlPath = String(row.jsonl_path || '').trim();
    if (jsonlPath && !fsExists(jsonlPath)) continue;

    return { row, invalidAgentSessionIds };
  }

  return { row: null, invalidAgentSessionIds };
}

function resolveRestoreArgsFromAgentSessions({
  db,
  task,
  agentType,
  fsExists = fs.existsSync,
  fsStat = fs.statSync,
  getResumeSpec = defaultGetResumeSpec,
  isKnownCodexThreadId = () => true,
} = {}) {
  const restoreArgs = normalizeArgs(task?.args);
  const normalizedAgentType = normalizeAgentType(agentType || task?.type || task?.cmd);
  const ctmSessionId = String(task?.ctm_session_id || task?.session_id || '').trim();
  const selected = selectRestoreAgentSession({
    db,
    ctmSessionId,
    agentType: normalizedAgentType,
    fsExists,
    fsStat,
    isKnownCodexThreadId,
  });

  if (!selected.row?.agent_session_id) {
    return {
      args: restoreArgs,
      source: 'startup_args',
      agentSessionId: '',
      row: null,
      invalidAgentSessionIds: selected.invalidAgentSessionIds,
    };
  }

  const resumeSessionId = resumeSessionIdForRow(selected.row, normalizedAgentType, { db, ctmSessionId });
  const resumeSpec = getResumeSpec(normalizedAgentType, resumeSessionId);
  return {
    args: resumeSpec ? resumeSpec.args.slice() : restoreArgs,
    source: 'agent_sessions',
    agentSessionId: selected.row.agent_session_id,
    resumeSessionId,
    row: selected.row,
    invalidAgentSessionIds: selected.invalidAgentSessionIds,
  };
}

function resolveRestoreCwdFromAgentSession({
  task,
  row,
  agentType,
  fsExists = fs.existsSync,
  fsStat = fs.statSync,
  homeDir,
  claudeConfigDir,
} = {}) {
  const normalizedAgentType = normalizeAgentType(agentType || task?.type || task?.cmd);
  const fallbackCwd = normalizeCwd(task?.cwd || '');
  if (normalizedAgentType !== 'claude' || !row) {
    return {
      cwd: fallbackCwd,
      changed: false,
      reason: '',
    };
  }

  const jsonlPath = String(row.jsonl_path || '').trim();
  const transcriptPath = jsonlPath || (
    row.agent_project_dir && row.agent_session_id
      ? path.join(row.agent_project_dir, `${row.agent_session_id}.jsonl`)
      : ''
  );
  if (!transcriptPath) {
    return { cwd: fallbackCwd, changed: false, reason: '' };
  }

  const result = resolveClaudeResumeCwd({
    cwd: fallbackCwd,
    projectPath: row.project_path || task?.project_path || '',
    agentRow: row,
    transcriptPath,
    fsExists,
    fsStat,
    homeDir,
    claudeConfigDir,
  });
  return {
    cwd: result.cwd || fallbackCwd,
    changed: !!(result.changed && result.cwd && !samePath(result.cwd, fallbackCwd)),
    reason: result.reason || '',
    blocked: !!result.blocked,
    transcriptProjectDir: result.transcriptProjectDir || '',
    cwdProjectDir: result.cwdProjectDir || '',
  };
}

function resolveCodexLinkRestoreArgs(codexLink, getResumeSpec = defaultGetResumeSpec) {
  const agentSessionId = String(codexLink?.agentSessionId || '').trim();
  if (!agentSessionId) return null;
  const resumeSessionId = String(codexLink?.providerResumeId || '').trim() || agentSessionId;
  const resumeSpec = getResumeSpec('codex', resumeSessionId);
  if (!resumeSpec) return null;
  return {
    args: resumeSpec.args.slice(),
    agentSessionId,
    resumeSessionId,
  };
}

module.exports = {
  orderedAgentRows,
  resolveCodexLinkRestoreArgs,
  resolveRestoreCwdFromAgentSession,
  resumeSessionIdForRow,
  selectRestoreAgentSession,
  resolveRestoreArgsFromAgentSessions,
};
