'use strict';

const ctmSessionContext = require('./ctm-session-context');

const DEFAULT_TIMEOUT_MS = 2500;
const DEFAULT_COOLDOWN_MS = 15000;

let apiFailureUntil = 0;
let lastApiFailure = null;

function resolveApiBaseUrl(env = process.env) {
  const explicit = env.CTM_SESSION_MEMORY_API_BASE_URL || env.CTM_API_BASE_URL || env.CTM_BASE_URL;
  if (explicit) return explicit.replace(/\/+$/, '');
  if (env.WALLE_CTM_API === '0') return '';
  return `http://127.0.0.1:${env.CTM_PORT || env.DEV_CTM_PORT || '3456'}`;
}

function apiDisabled(env = process.env) {
  return env.WALLE_CTM_API_DISABLED === '1' || env.CTM_SESSION_MEMORY_API === '0';
}

function directFallbackAllowed(env = process.env, options = {}) {
  return options.allowDirectFallback === true || env.WALLE_CTM_DIRECT_READ_FALLBACK === '1';
}

async function getHealth(options = {}) {
  return apiFirst({
    path: '/api/ctm/session-memory/health',
    fallback: () => ctmSessionContext.getCtmDbHealth(directOptions(options)),
    options,
  });
}

async function searchSessions(args = {}, options = {}) {
  const params = new URLSearchParams();
  if (args.query !== undefined) params.set('q', String(args.query || ''));
  if (args.limit !== undefined) params.set('limit', String(args.limit));
  return apiFirst({
    path: `/api/ctm/session-memory/search?${params.toString()}`,
    fallback: () => ctmSessionContext.searchCtmSessions({ ...args, ...directOptions(options) }),
    options: { ...options, preferApi: args.prefer_api !== false && options.preferApi !== false },
  });
}

async function getSessionContext(args = {}, options = {}) {
  const params = new URLSearchParams();
  for (const id of normalizeIds(args.session_ids || args.ids)) params.append('session_id', id);
  if (args.session_id || args.source_id) params.append('session_id', args.session_id || args.source_id);
  for (const [key, value] of Object.entries({
    limit: args.limit,
    cursor: args.cursor,
    format: args.format,
  })) {
    if (value !== undefined && value !== null) params.set(key, String(value));
  }
  if (args.include_raw) params.set('include_raw', '1');
  if (args.dedupe === false) params.set('no_dedupe', '1');

  return apiFirst({
    path: `/api/ctm/session-memory/context?${params.toString()}`,
    fallback: () => ctmSessionContext.getCtmSessionContext({ ...args, ...directOptions(options) }),
    options: { ...options, preferApi: args.prefer_api !== false && options.preferApi !== false },
  });
}

async function getRemoteAccessStatus(args = {}, options = {}) {
  return apiFirst({
    path: '/api/setup/network',
    fallback: () => ({
      ok: false,
      source: 'ctm-api',
      authority: 'ctm',
      reason: 'ctm_api_required',
      remote_access: null,
    }),
    options: {
      ...options,
      preferApi: args.prefer_api !== false && options.preferApi !== false,
      timeoutMs: options.timeoutMs ?? 6000,
    },
  });
}

async function buildContextPack(args = {}, options = {}) {
  return apiFirst({
    path: '/api/ctm/session-memory/context-pack',
    method: 'POST',
    body: {
      task: args.task || '',
      query: args.query || '',
      session_ids: args.session_ids || args.ids,
      limit: args.limit,
      token_budget: args.token_budget,
      include_raw: Boolean(args.include_raw),
      mode: args.mode || 'auto',
    },
    fallback: () => ctmSessionContext.buildContextPack({ ...args, ...directOptions(options) }),
    options: { ...options, preferApi: args.prefer_api !== false && options.preferApi !== false },
  });
}

async function backfillMessageIndex(args = {}, options = {}) {
  const dryRun = Boolean(args.dry_run);
  return apiFirst({
    path: '/api/ctm/session-memory/message-index/backfill',
    method: 'POST',
    body: {
      limit: args.limit,
      chunk_size: args.chunk_size || args.chunkSize,
      dry_run: dryRun,
    },
    fallback: () => {
      if (dryRun) {
        return ctmSessionContext.backfillCtmSessionMessages({
          ...directOptions(options),
          limit: args.limit,
          dry_run: true,
        });
      }
      return {
        ok: false,
        source: 'ctm-api',
        authority: 'ctm',
        reason: 'ctm_api_required_for_writes',
        dry_run: false,
      };
    },
    options: { ...options, preferApi: args.prefer_api !== false && options.preferApi !== false },
  });
}

async function apiFirst({ path, method = 'GET', body, fallback, options = {} }) {
  const env = options.env || process.env;
  const preferApi = options.preferApi !== false;
  const fallbackResult = (apiTransport) => {
    if (directFallbackAllowed(env, options)) return annotateFallback(fallback(), apiTransport);
    return {
      ok: false,
      source: 'ctm-api',
      authority: 'ctm',
      reason: 'ctm_api_required',
      api_transport: apiTransport,
    };
  };

  if (!preferApi || apiDisabled(env)) {
    return fallbackResult({
      attempted: false,
      skipped: true,
      reason: apiDisabled(env) ? 'api_disabled' : 'api_not_preferred',
    });
  }

  const baseUrl = resolveApiBaseUrl(env);
  if (!baseUrl) {
    return fallbackResult({
      attempted: false,
      skipped: true,
      reason: 'api_base_url_missing',
    });
  }

  const now = Date.now();
  const cooldownMs = options.cooldownMs ?? DEFAULT_COOLDOWN_MS;
  if (cooldownMs > 0 && apiFailureUntil > now) {
    return fallbackResult({
      attempted: false,
      skipped: true,
      reason: 'api_cooldown',
      last_error: lastApiFailure,
    });
  }

  try {
    const result = await requestJson(`${baseUrl}${path}`, {
      method,
      body,
      timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
      token: env.CTM_TOKEN || env.WALLE_CTM_TOKEN || env.CTM_API_TOKEN,
    });
    apiFailureUntil = 0;
    lastApiFailure = null;
    return {
      ...result,
      api_transport: {
        attempted: true,
        ok: true,
        base_url: baseUrl,
      },
    };
  } catch (err) {
    lastApiFailure = err.message;
    apiFailureUntil = Date.now() + cooldownMs;
    return fallbackResult({
      attempted: true,
      ok: false,
      base_url: baseUrl,
      reason: 'api_request_failed',
      error: err.message,
    });
  }
}

async function requestJson(url, { method, body, timeoutMs, token }) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const headers = { Accept: 'application/json' };
    let payload;
    if (body !== undefined) {
      headers['Content-Type'] = 'application/json';
      payload = JSON.stringify(body);
    }
    if (token) headers.Authorization = `Bearer ${token}`;

    const response = await fetch(url, {
      method,
      headers,
      body: payload,
      signal: controller.signal,
    });
    const text = await response.text();
    const json = text ? JSON.parse(text) : {};
    if (!response.ok) {
      throw new Error(`ctm_api_http_${response.status}:${json?.reason || json?.error || response.statusText}`);
    }
    return json;
  } finally {
    clearTimeout(timer);
  }
}

function annotateFallback(result, apiTransport) {
  return {
    ...(result || {}),
    api_transport: apiTransport,
  };
}

function directOptions(options = {}) {
  const out = {};
  if (options.db) out.db = options.db;
  if (options.dbPath) out.dbPath = options.dbPath;
  if (options.env) out.env = options.env;
  return out;
}

function normalizeIds(value) {
  if (!value) return [];
  const raw = Array.isArray(value) ? value : String(value).split(',');
  return raw.map((item) => String(item || '').trim()).filter(Boolean);
}

function resetApiState() {
  apiFailureUntil = 0;
  lastApiFailure = null;
}

module.exports = {
  resolveApiBaseUrl,
  getHealth,
  searchSessions,
  getSessionContext,
  getRemoteAccessStatus,
  buildContextPack,
  backfillMessageIndex,
  resetApiState,
};
