import os from 'os';
import path from 'path';

import type { AgentType } from './types.js';
import { getEnv } from './env.js';

export const ASSISTANT_NAME = getEnv('ASSISTANT_NAME') || 'Andy';
export const ASSISTANT_HAS_OWN_NUMBER =
  getEnv('ASSISTANT_HAS_OWN_NUMBER') === 'true';
const ASSISTANT_SLUG = ASSISTANT_NAME.trim().toLowerCase();
export const SERVICE_ID = getEnv('SERVICE_ID') || ASSISTANT_SLUG;
export const CLAUDE_SERVICE_ID = getEnv('CLAUDE_SERVICE_ID') || 'claude';
export const CODEX_MAIN_SERVICE_ID =
  getEnv('CODEX_MAIN_SERVICE_ID') || 'codex-main';
export const CODEX_REVIEW_SERVICE_ID =
  getEnv('CODEX_REVIEW_SERVICE_ID') || 'codex-review';

export function normalizeServiceId(serviceId: string): string {
  if (serviceId === 'codex') {
    return CODEX_MAIN_SERVICE_ID;
  }
  return serviceId;
}

export const SERVICE_SESSION_SCOPE = normalizeServiceId(SERVICE_ID);

export function isClaudeService(serviceId: string = SERVICE_ID): boolean {
  return normalizeServiceId(serviceId) === CLAUDE_SERVICE_ID;
}

export function isCodexMainService(serviceId: string = SERVICE_ID): boolean {
  return normalizeServiceId(serviceId) === CODEX_MAIN_SERVICE_ID;
}

export function isReviewService(serviceId: string = SERVICE_ID): boolean {
  return normalizeServiceId(serviceId) === CODEX_REVIEW_SERVICE_ID;
}
export const POLL_INTERVAL = 2000;
export const SCHEDULER_POLL_INTERVAL = 60000;

/** Minimum time (ms) a failover lease stays active before Claude can reclaim it. */
export const FAILOVER_MIN_DURATION_MS = 3 * 60 * 60 * 1000; // 3 hours

const PROJECT_ROOT = process.cwd();
const HOME_DIR = process.env.HOME || os.homedir();

export const SENDER_ALLOWLIST_PATH = path.join(
  HOME_DIR,
  '.config',
  'ejclaw',
  'sender-allowlist.json',
);
export const STORE_DIR = path.resolve(
  process.env.EJCLAW_STORE_DIR || path.join(PROJECT_ROOT, 'store'),
);
export const GROUPS_DIR = path.resolve(
  process.env.EJCLAW_GROUPS_DIR || path.join(PROJECT_ROOT, 'groups'),
);
export const DATA_DIR = path.resolve(
  process.env.EJCLAW_DATA_DIR || path.join(PROJECT_ROOT, 'data'),
);
// Shared cache directory (same across both services for dedup)
export const CACHE_DIR = path.join(PROJECT_ROOT, 'cache');

export const AGENT_TIMEOUT = parseInt(
  process.env.AGENT_TIMEOUT || '1800000',
  10,
);
export const AGENT_MAX_OUTPUT_SIZE = parseInt(
  process.env.AGENT_MAX_OUTPUT_SIZE || '10485760',
  10,
); // 10MB default
export const IPC_POLL_INTERVAL = 1000;
export const IDLE_TIMEOUT = parseInt(process.env.IDLE_TIMEOUT || '1800000', 10); // 30min default — how long to keep agent alive after last result
export const MAX_CONCURRENT_AGENTS = Math.max(
  1,
  parseInt(process.env.MAX_CONCURRENT_AGENTS || '5', 10) || 5,
);
export const RECOVERY_CONCURRENT_AGENTS = parseInt(
  getEnv('RECOVERY_CONCURRENT_AGENTS') || '3',
  10,
);

// ── Paired review ─────────────────────────────────────────────────

/** Owner agent type. Default: codex. Set OWNER_AGENT_TYPE=claude-code to use Claude as owner. */
const rawOwnerAgentType = getEnv('OWNER_AGENT_TYPE');
export const OWNER_AGENT_TYPE: AgentType =
  rawOwnerAgentType === 'codex' || rawOwnerAgentType === 'claude-code'
    ? rawOwnerAgentType
    : 'codex';

/** Reviewer agent type. Default: claude-code. Set REVIEWER_AGENT_TYPE=codex to use Codex as reviewer. */
const rawReviewerAgentType = getEnv('REVIEWER_AGENT_TYPE');
export const REVIEWER_AGENT_TYPE: AgentType =
  rawReviewerAgentType === 'codex' || rawReviewerAgentType === 'claude-code'
    ? rawReviewerAgentType
    : 'claude-code';

/** Service ID for the reviewer based on agent type. */
export const REVIEWER_SERVICE_ID_FOR_TYPE =
  REVIEWER_AGENT_TYPE === 'claude-code'
    ? CLAUDE_SERVICE_ID
    : CODEX_REVIEW_SERVICE_ID;

/** Arbiter agent type. Disabled by default. Set ARBITER_AGENT_TYPE=codex or claude-code to enable. */
const rawArbiterAgentType = getEnv('ARBITER_AGENT_TYPE');
export const ARBITER_AGENT_TYPE: AgentType | undefined =
  rawArbiterAgentType === 'codex' || rawArbiterAgentType === 'claude-code'
    ? rawArbiterAgentType
    : undefined;

/** Service ID for the arbiter. Re-uses codex-review bot by default when arbiter is enabled. */
export const ARBITER_SERVICE_ID = ARBITER_AGENT_TYPE
  ? getEnv('ARBITER_SERVICE_ID') || CODEX_REVIEW_SERVICE_ID
  : null;

/** Language for agent responses. When set, a language instruction is appended to all paired room prompts. */
export const AGENT_LANGUAGE = getEnv('AGENT_LANGUAGE') || '';

/** Number of consecutive owner↔reviewer round trips before arbiter is auto-requested. */
export const ARBITER_DEADLOCK_THRESHOLD = parseInt(
  getEnv('ARBITER_DEADLOCK_THRESHOLD') || '3',
  10,
);

export function isArbiterEnabled(): boolean {
  return ARBITER_AGENT_TYPE !== undefined;
}

// ── Per-role model configuration ─────────────────────────────────

export interface RoleModelConfig {
  /** Model name override (e.g. 'claude-opus-4-6', 'gpt-5.4'). */
  model?: string;
  /** Effort level override. */
  effort?: string;
  /** Whether to fall back to codex when primary provider fails. Default: true. */
  fallbackEnabled: boolean;
}

function buildRoleModelConfig(envPrefix: string): RoleModelConfig {
  return {
    model: getEnv(`${envPrefix}_MODEL`) || undefined,
    effort: getEnv(`${envPrefix}_EFFORT`) || undefined,
    fallbackEnabled: getEnv(`${envPrefix}_FALLBACK_ENABLED`) !== 'false',
  };
}

export const OWNER_MODEL_CONFIG = buildRoleModelConfig('OWNER');
export const REVIEWER_MODEL_CONFIG = buildRoleModelConfig('REVIEWER');
export const ARBITER_MODEL_CONFIG = buildRoleModelConfig('ARBITER');

export function getRoleModelConfig(
  role: 'owner' | 'reviewer' | 'arbiter',
): RoleModelConfig {
  switch (role) {
    case 'owner':
      return OWNER_MODEL_CONFIG;
    case 'reviewer':
      return REVIEWER_MODEL_CONFIG;
    case 'arbiter':
      return ARBITER_MODEL_CONFIG;
  }
}

// ── Mixture of Agents (MoA) ──────────────────────────────────────

import type { MoaConfig, MoaModelConfig } from './moa.js';

/**
 * Parse MOA reference models from env.
 * Format: MOA_REF_MODELS=kimi,glm (comma-separated names)
 * Each model: MOA_{NAME}_MODEL, MOA_{NAME}_BASE_URL, MOA_{NAME}_API_KEY
 */
function parseMoaReferenceModels(): MoaModelConfig[] {
  const names = (getEnv('MOA_REF_MODELS') || '')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);

  return names
    .map((name) => {
      const prefix = `MOA_${name.toUpperCase()}`;
      const model = getEnv(`${prefix}_MODEL`) || '';
      const baseUrl = getEnv(`${prefix}_BASE_URL`) || '';
      const apiKey = getEnv(`${prefix}_API_KEY`) || '';
      if (!model || !baseUrl || !apiKey) return null;
      const rawFormat = getEnv(`${prefix}_API_FORMAT`) || '';
      const apiFormat: 'openai' | 'anthropic' =
        rawFormat === 'anthropic' ? 'anthropic' : 'openai';
      return { name, model, baseUrl, apiKey, apiFormat };
    })
    .filter((m): m is MoaModelConfig => m !== null);
}

export function getMoaConfig(): MoaConfig {
  const referenceModels = parseMoaReferenceModels();
  return {
    enabled: getEnv('MOA_ENABLED') === 'true' && referenceModels.length > 0,
    referenceModels,
  };
}

// Max owner↔reviewer round trips per task. 0 = unlimited.
const rawMaxRoundTrips = getEnv('PAIRED_MAX_ROUND_TRIPS') || '1000';
export const PAIRED_MAX_ROUND_TRIPS =
  rawMaxRoundTrips === '0' ? Infinity : parseInt(rawMaxRoundTrips, 10) || 1000;

// ── Container isolation ───────────────────────────────────────────
export const REVIEWER_CONTAINER_IMAGE =
  getEnv('REVIEWER_CONTAINER_IMAGE') || 'ejclaw-reviewer:latest';
export const CREDENTIAL_PROXY_PORT = parseInt(
  getEnv('CREDENTIAL_PROXY_PORT') || '3001',
  10,
);
export const RECOVERY_STAGGER_MS = parseInt(
  getEnv('RECOVERY_STAGGER_MS') || '2000',
  10,
);
export const RECOVERY_DURATION_MS = parseInt(
  getEnv('RECOVERY_DURATION_MS') || '60000',
  10,
);

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

export const TRIGGER_PATTERN = new RegExp(
  `^@${escapeRegex(ASSISTANT_NAME)}\\b`,
  'i',
);

// Status dashboard: Discord channel ID for live agent status updates
export const STATUS_CHANNEL_ID = process.env.STATUS_CHANNEL_ID || '';
export const STATUS_UPDATE_INTERVAL = 10000; // 10s
export const USAGE_UPDATE_INTERVAL = 300000; // 5 minutes
export const STATUS_SHOW_ROOMS =
  (getEnv('STATUS_SHOW_ROOMS') || 'true') !== 'false';
export const STATUS_SHOW_ROOM_DETAILS =
  (getEnv('STATUS_SHOW_ROOM_DETAILS') || 'true') !== 'false';
export const USAGE_DASHBOARD_ENABLED = getEnv('USAGE_DASHBOARD') === 'true';

// Timezone for scheduled tasks (cron expressions, etc.)
// Uses system timezone by default
export const TIMEZONE =
  process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone;

const rawSessionCommandAllowedSenders =
  getEnv('SESSION_COMMAND_ALLOWED_SENDERS') ||
  getEnv('SESSION_COMMAND_USER_IDS') ||
  '';

const SESSION_COMMAND_ALLOWED_SENDERS = new Set(
  rawSessionCommandAllowedSenders
    .split(',')
    .map((value) => value.trim())
    .filter(Boolean),
);

export function isSessionCommandSenderAllowed(sender: string): boolean {
  return SESSION_COMMAND_ALLOWED_SENDERS.has(sender);
}

// Delta handoff: cross-provider session continuity with probe + fallback
export const DELTA_HANDOFF_ENABLED = getEnv('DELTA_HANDOFF_ENABLED') === 'true';

// Comma-separated list of group folders for canary testing
const rawDeltaHandoffCanaryGroups = getEnv('DELTA_HANDOFF_CANARY_GROUPS') || '';
export const DELTA_HANDOFF_CANARY_GROUPS = new Set(
  rawDeltaHandoffCanaryGroups
    .split(',')
    .map((v) => v.trim())
    .filter(Boolean),
);

export function isDeltaHandoffEnabledForGroup(groupFolder: string): boolean {
  if (!DELTA_HANDOFF_ENABLED) return false;
  // If canary groups are specified, only enable for those groups
  if (DELTA_HANDOFF_CANARY_GROUPS.size > 0) {
    return DELTA_HANDOFF_CANARY_GROUPS.has(groupFolder);
  }
  return true;
}
