import path from 'node:path';

import type { Id } from '@spoon/backend/convex/_generated/dataModel.js';

import type { AgentRuntimeName } from './agent-runtime';

export type Claim = {
  job: {
    _id: Id<'agentJobs'>;
    prompt: string;
    runtime?: 'codex' | 'opencode' | 'claude';
    jobType?: 'user_change' | 'maintenance_review' | 'conflict_resolution';
    envFilePath?: string;
    materializeEnvFile?: boolean;
    baseBranch: string;
    workBranch: string;
    forkOwner: string;
    forkRepo: string;
    upstreamOwner: string;
    upstreamRepo: string;
  };
  spoon: { name: string };
  openai: {
    apiKey?: string;
    model: string;
    reasoningEffort: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
  };
  aiProviderProfile?: {
    id: string;
    name: string;
    provider:
      | 'openai'
      | 'anthropic'
      | 'google'
      | 'openrouter'
      | 'requesty'
      | 'litellm'
      | 'cloudflare_ai_gateway'
      | 'custom_openai_compatible'
      | 'opencode_openai_login';
    authType: 'api_key' | 'opencode_auth_json' | 'anthropic_oauth_json' | 'none';
    secret?: string;
    baseUrl?: string;
    model: string;
    reasoningEffort: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
  };
  github: { installationId?: string };
  agentSettings?: {
    installCommand?: string;
    checkCommand?: string;
    testCommand?: string;
    autoDetectCommands?: boolean;
  } | null;
  secrets: { name: string; value: string }[];
};

// The subset of the worker's active workspace that agent adapters read.
export type AdapterWorkspace = {
  claim: Claim;
  workdir: string;
  homeDir: string;
  username: string;
  containerHome: string;
  containerRepo: string;
  repoDir: string;
  boxName: string;
  redact: (value: string) => string;
  runtime: AgentRuntimeName;
  codexSessionId?: string;
  opencodeSessionId?: string;
  claudeSessionId?: string;
  turnMarker?: string;
};

export const isCodexLoginProfile = (claim: Claim) =>
  claim.aiProviderProfile?.provider === 'opencode_openai_login' ||
  claim.aiProviderProfile?.authType === 'opencode_auth_json';

export const collectJsonStringValues = (value?: string): string[] => {
  if (!value) return [];
  try {
    const parsed = JSON.parse(value) as unknown;
    const values: string[] = [];
    const visit = (item: unknown) => {
      if (typeof item === 'string') {
        if (item.length >= 12) values.push(item);
        return;
      }
      if (Array.isArray(item)) {
        item.forEach(visit);
        return;
      }
      if (item && typeof item === 'object') {
        Object.values(item).forEach(visit);
      }
    };
    visit(parsed);
    return values;
  } catch {
    return [];
  }
};

export const providerEnvironment = (
  claim: Claim,
  workspaceRoot?: string,
): Record<string, string> => {
  if (isCodexLoginProfile(claim)) {
    if (!workspaceRoot) {
      throw new Error('Codex auth profiles require a prepared workspace.');
    }
    return {
      CODEX_HOME: path.join(workspaceRoot, '.codex'),
      HOME: workspaceRoot,
      XDG_DATA_HOME: path.join(workspaceRoot, '.local', 'share'),
      XDG_CONFIG_HOME: path.join(workspaceRoot, '.config'),
    };
  }
  const profile = claim.aiProviderProfile;
  const secret = profile?.secret ?? claim.openai.apiKey;
  if (!secret) {
    throw new Error('No AI provider credential is configured for this job.');
  }
  const baseUrl: Record<string, string> = profile?.baseUrl
    ? { OPENAI_BASE_URL: profile.baseUrl }
    : {};
  if (!profile || profile.provider === 'openai') {
    return { OPENAI_API_KEY: secret, ...baseUrl };
  }
  if (profile.provider === 'anthropic') return { ANTHROPIC_API_KEY: secret };
  if (profile.provider === 'google') return { GOOGLE_API_KEY: secret };
  if (profile.provider === 'openrouter') {
    return { OPENROUTER_API_KEY: secret, ...baseUrl };
  }
  if (profile.provider === 'requesty') {
    return { REQUESTY_API_KEY: secret, ...baseUrl };
  }
  if (profile.provider === 'cloudflare_ai_gateway') {
    return { CLOUDFLARE_API_KEY: secret, ...baseUrl };
  }
  if (
    profile.provider === 'litellm' ||
    profile.provider === 'custom_openai_compatible'
  ) {
    return { OPENAI_API_KEY: secret, ...baseUrl };
  }
  throw new Error('Unsupported AI provider profile.');
};

export const opencodeModel = (claim: Claim) => {
  const profile = claim.aiProviderProfile;
  const model = profile?.model ?? claim.openai.model;
  if (model.includes('/')) return model;
  if (!profile) return `openai/${model}`;
  if (
    profile.provider === 'custom_openai_compatible' ||
    profile.provider === 'cloudflare_ai_gateway'
  ) {
    return model;
  }
  if (profile.provider === 'opencode_openai_login') return `openai/${model}`;
  return `${profile.provider}/${model}`;
};

export const codexModel = (claim: Claim) => {
  const model = claim.aiProviderProfile?.model ?? claim.openai.model;
  return model.includes('/') ? (model.split('/').at(-1) ?? model) : model;
};

export const codexModelArgs = (claim: Claim) =>
  isCodexLoginProfile(claim) ? [] : ['--model', codexModel(claim)];

// Claude Code OAuth profiles store their credentials as a JSON blob written to
// `~/.claude/.credentials.json`; API-key profiles authenticate via
// ANTHROPIC_API_KEY. Keyed on the Anthropic OAuth snapshot type.
export const isClaudeOAuthProfile = (claim: Claim) =>
  claim.aiProviderProfile?.authType === 'anthropic_oauth_json';

// The `claude --model` flag expects a bare model id (e.g. `claude-sonnet-4`),
// so strip any `provider/` prefix like codexModel does.
export const claudeModel = (claim: Claim) => {
  const model = claim.aiProviderProfile?.model ?? claim.openai.model;
  return model.includes('/') ? (model.split('/').at(-1) ?? model) : model;
};

// Environment for a `claude -p` turn. OAuth-json profiles authenticate from the
// on-disk credentials file (only HOME is needed); API-key profiles export
// ANTHROPIC_API_KEY.
export const claudeEnv = (
  claim: Claim,
  containerHome: string,
): Record<string, string> => {
  if (isClaudeOAuthProfile(claim)) return { HOME: containerHome };
  const secret = claim.aiProviderProfile?.secret ?? claim.openai.apiKey;
  if (!secret) {
    throw new Error('No Anthropic API key is configured for this Claude job.');
  }
  return { ANTHROPIC_API_KEY: secret, HOME: containerHome };
};
