import {
  ARBITER_SERVICE_ID,
  CLAUDE_SERVICE_ID,
  CODEX_MAIN_SERVICE_ID,
  CODEX_REVIEW_SERVICE_ID,
  OWNER_AGENT_TYPE,
  REVIEWER_SERVICE_ID_FOR_TYPE,
  SERVICE_ID,
  isArbiterEnabled,
  normalizeServiceId,
} from './config.js';
import {
  clearChannelOwnerLease,
  getAllChannelOwnerLeases,
  getEffectiveRuntimeRoomMode,
  getRegisteredAgentTypesForJid,
  getStoredRoomSettings,
  setChannelOwnerLease,
  type ChannelOwnerLeaseRow,
} from './db.js';
import { logger } from './logger.js';
import type { AgentType } from './types.js';

export interface EffectiveChannelLease {
  chat_jid: string;
  owner_service_id: string;
  reviewer_service_id: string | null;
  arbiter_service_id: string | null;
  activated_at: string | null;
  reason: string | null;
  explicit: boolean;
}

const LEASE_CACHE_REFRESH_MS = 2_000;

let lastLeaseRefreshAt = 0;
const leaseCache = new Map<string, ChannelOwnerLeaseRow>();

function normalizeLeaseRow(
  row: ChannelOwnerLeaseRow,
  explicit: boolean,
): EffectiveChannelLease {
  return {
    chat_jid: row.chat_jid,
    owner_service_id: normalizeServiceId(row.owner_service_id),
    reviewer_service_id: row.reviewer_service_id
      ? normalizeServiceId(row.reviewer_service_id)
      : null,
    arbiter_service_id: row.arbiter_service_id
      ? normalizeServiceId(row.arbiter_service_id)
      : isArbiterEnabled()
        ? ARBITER_SERVICE_ID
        : null,
    activated_at: row.activated_at,
    reason: row.reason,
    explicit,
  };
}

function getServiceIdForAgentType(agentType: AgentType): string {
  return agentType === 'codex' ? CODEX_MAIN_SERVICE_ID : CLAUDE_SERVICE_ID;
}

function inferFallbackOwnerAgentType(
  hasClaude: boolean,
  hasCodex: boolean,
): AgentType | undefined {
  if (hasClaude && hasCodex) return OWNER_AGENT_TYPE;
  if (hasCodex) return 'codex';
  if (hasClaude) return 'claude-code';
  return undefined;
}

function resolveDefaultOwnerAgentType(chatJid: string): AgentType | undefined {
  const storedOwnerAgentType = getStoredRoomSettings(chatJid)?.ownerAgentType;
  if (storedOwnerAgentType) {
    return storedOwnerAgentType;
  }

  const types = getRegisteredAgentTypesForJid(chatJid);
  const hasClaude = types.includes('claude-code');
  const hasCodex = types.includes('codex');

  return inferFallbackOwnerAgentType(hasClaude, hasCodex);
}

function getDefaultLease(chatJid: string): EffectiveChannelLease {
  const roomMode = getEffectiveRuntimeRoomMode(chatJid);
  const ownerAgentType = resolveDefaultOwnerAgentType(chatJid);
  const ownerServiceId = ownerAgentType
    ? getServiceIdForAgentType(ownerAgentType)
    : CLAUDE_SERVICE_ID;

  if (roomMode === 'tribunal') {
    return {
      chat_jid: chatJid,
      owner_service_id: ownerServiceId,
      reviewer_service_id: REVIEWER_SERVICE_ID_FOR_TYPE,
      arbiter_service_id: isArbiterEnabled() ? ARBITER_SERVICE_ID : null,
      activated_at: null,
      reason: null,
      explicit: false,
    };
  }

  return {
    chat_jid: chatJid,
    owner_service_id: ownerServiceId,
    reviewer_service_id: null,
    arbiter_service_id: null,
    activated_at: null,
    reason: null,
    explicit: false,
  };
}

export function refreshChannelOwnerCache(force = false): void {
  const now = Date.now();
  if (!force && now - lastLeaseRefreshAt < LEASE_CACHE_REFRESH_MS) {
    return;
  }

  leaseCache.clear();
  for (const row of getAllChannelOwnerLeases()) {
    leaseCache.set(row.chat_jid, row);
  }
  lastLeaseRefreshAt = now;
}

export function getEffectiveChannelLease(
  chatJid: string,
): EffectiveChannelLease {
  // Global failover overrides all per-channel leases
  if (globalFailoverActive) {
    return {
      chat_jid: chatJid,
      owner_service_id: CODEX_REVIEW_SERVICE_ID,
      reviewer_service_id: CODEX_MAIN_SERVICE_ID,
      arbiter_service_id: null,
      activated_at: globalFailoverActivatedAt,
      reason: globalFailoverReason,
      explicit: true,
    };
  }
  refreshChannelOwnerCache();
  const row = leaseCache.get(chatJid);
  if (row) {
    return normalizeLeaseRow(row, true);
  }
  return getDefaultLease(chatJid);
}

export function isOwnerServiceForChat(
  chatJid: string,
  serviceId: string = SERVICE_ID,
): boolean {
  const lease = getEffectiveChannelLease(chatJid);
  return normalizeServiceId(serviceId) === lease.owner_service_id;
}

export function isReviewerServiceForChat(
  chatJid: string,
  serviceId: string = SERVICE_ID,
): boolean {
  const lease = getEffectiveChannelLease(chatJid);
  return (
    lease.reviewer_service_id !== null &&
    normalizeServiceId(serviceId) === lease.reviewer_service_id
  );
}

export function hasReviewerLease(chatJid: string): boolean {
  return getEffectiveChannelLease(chatJid).reviewer_service_id !== null;
}

export function isArbiterServiceForChat(
  chatJid: string,
  serviceId: string = SERVICE_ID,
): boolean {
  const lease = getEffectiveChannelLease(chatJid);
  return (
    lease.arbiter_service_id !== null &&
    normalizeServiceId(serviceId) === lease.arbiter_service_id
  );
}

export function shouldServiceProcessChat(
  _chatJid: string,
  _serviceId: string = SERVICE_ID,
): boolean {
  return true;
}

// ── Global failover ──────────────────────────────────────────────
// Claude API limits are account-level, so failover applies to all channels.

let globalFailoverActive = false;
let globalFailoverReason: string | null = null;
let globalFailoverActivatedAt: string | null = null;

export function activateCodexFailover(_chatJid: string, reason: string): void {
  globalFailoverActive = true;
  globalFailoverReason = reason;
  globalFailoverActivatedAt = new Date().toISOString();
  logger.warn(
    { reason, activatedAt: globalFailoverActivatedAt },
    'Global failover activated — all channels switching to codex',
  );
}

export function isGlobalFailoverActive(): boolean {
  return globalFailoverActive;
}

export function getGlobalFailoverInfo(): {
  active: boolean;
  reason: string | null;
  activatedAt: string | null;
} {
  return {
    active: globalFailoverActive,
    reason: globalFailoverReason,
    activatedAt: globalFailoverActivatedAt,
  };
}

export function clearGlobalFailover(): void {
  if (!globalFailoverActive) return;
  globalFailoverActive = false;
  globalFailoverReason = null;
  globalFailoverActivatedAt = null;
  logger.info('Global failover cleared — resuming normal routing');
}

export function restoreDefaultChannelLease(chatJid: string): void {
  clearChannelOwnerLease(chatJid);
  leaseCache.delete(chatJid);
  lastLeaseRefreshAt = Date.now();
}

export interface ActiveFailoverLease {
  chatJid: string;
  activatedAt: string | null;
}

export function getActiveCodexFailoverLeases(): ActiveFailoverLease[] {
  // Global failover: report as a single pseudo-lease
  if (globalFailoverActive) {
    return [{ chatJid: '*', activatedAt: globalFailoverActivatedAt }];
  }
  return [];
}

/** @deprecated Use getActiveCodexFailoverLeases() instead */
export function getActiveCodexFailoverChatJids(): string[] {
  return getActiveCodexFailoverLeases().map((l) => l.chatJid);
}
