'use strict';

let ctmSessionContext = null;
let ctmSessionContextLoadError = null;

function loadCtmSessionContext() {
  if (ctmSessionContext || ctmSessionContextLoadError) return ctmSessionContext;
  try {
    ctmSessionContext = require('../../wall-e/memory/ctm-session-context');
  } catch (err) {
    ctmSessionContextLoadError = err;
  }
  return ctmSessionContext;
}

function handleCtmSessionContextApi(req, res, url, { db, dbModule, runHeavy } = {}) {
  if (!url.pathname.startsWith('/api/ctm/session-memory')) return false;

  const context = loadCtmSessionContext();
  if (!context) {
    return respondJson(res, 503, {
      ok: false,
      source: 'ctm-api',
      reason: 'ctm_session_context_unavailable',
      error: ctmSessionContextLoadError?.message || 'CTM session context module unavailable',
    });
  }

  // The search / context / context-pack reads are the heavy ones (FTS + LIKE scans over
  // 220k+ message rows and a tens-of-MB conversation-blob column, plus full JSON.parse of
  // matched conversations) — a single search was measured blocking the main loop 7.74s.
  // When the server wires an off-thread runner, run them on the read-pool worker and reply
  // when it resolves; `args` is the plain (clone-safe) input the worker re-runs with its own
  // read-only connection, and `computeSync` is the main-thread fallback used when no runner
  // is wired (tests / direct callers) or the pool is unavailable.
  const serveHeavy = (op, args, computeSync) => {
    if (typeof runHeavy !== 'function') {
      return respondJson(res, 200, asApiResult(computeSync()));
    }
    Promise.resolve()
      .then(() => runHeavy(op, args, computeSync))
      .then((result) => respondJson(res, 200, asApiResult(result)))
      .catch((err) => respondJson(res, 500, {
        ok: false,
        source: 'ctm-api',
        authority: 'ctm',
        reason: 'ctm_session_context_failed',
        error: err.message,
      }));
    return true;
  };

  try {
    if (url.pathname === '/api/ctm/session-memory/health' && req.method === 'GET') {
      return respondJson(res, 200, asApiResult(context.getCtmDbHealth({ db })));
    }

    if (url.pathname === '/api/ctm/session-memory/search' && req.method === 'GET') {
      const args = {
        query: url.searchParams.get('q') || url.searchParams.get('query') || '',
        limit: url.searchParams.get('limit') || undefined,
      };
      return serveHeavy('ctmSessionMemorySearch', args,
        () => context.searchCtmSessions({ ...args, db }));
    }

    if (url.pathname === '/api/ctm/session-memory/context' && req.method === 'GET') {
      const args = {
        session_id: url.searchParams.get('session_id') || url.searchParams.get('id') || '',
        session_ids: parseListParams(url.searchParams, 'session_id', 'session_ids', 'ids'),
        limit: url.searchParams.get('limit') || undefined,
        cursor: url.searchParams.get('cursor') || undefined,
        include_raw: parseBool(url.searchParams.get('include_raw')),
        dedupe: !parseBool(url.searchParams.get('no_dedupe')),
        format: url.searchParams.get('format') || 'messages',
      };
      return serveHeavy('ctmSessionMemoryContext', args,
        () => context.getCtmSessionContext({ ...args, db }));
    }

    if (
      (url.pathname === '/api/ctm/session-memory/message-index/backfill'
        || url.pathname === '/api/ctm/session-memory/rebuild-message-index')
      && req.method === 'POST'
    ) {
      readJsonBody(req, 32 * 1024)
        .then((body) => handleMessageIndexBackfill({ body, res, context, db, dbModule })
          .catch((err) => respondJson(res, 500, {
            ok: false,
            source: 'ctm-api',
            authority: 'ctm',
            reason: 'ctm_message_index_backfill_failed',
            error: err.message,
          })))
        .catch((err) => respondJson(res, 400, {
          ok: false,
          source: 'ctm-api',
          reason: 'invalid_json',
          error: err.message,
        }));
      return true;
    }

    if (url.pathname === '/api/ctm/session-memory/context-pack' && req.method === 'POST') {
      readJsonBody(req, 128 * 1024)
        .then((body) => {
          const args = {
            task: body.task || '',
            query: body.query || '',
            session_ids: body.session_ids || body.ids,
            limit: body.limit,
            token_budget: body.token_budget,
            include_raw: Boolean(body.include_raw),
            mode: body.mode || 'auto',
          };
          serveHeavy('ctmSessionMemoryContextPack', args,
            () => context.buildContextPack({ ...args, db }));
        })
        .catch((err) => respondJson(res, 400, {
          ok: false,
          source: 'ctm-api',
          reason: 'invalid_json',
          error: err.message,
        }));
      return true;
    }

    return respondJson(res, 404, {
      ok: false,
      source: 'ctm-api',
      reason: 'not_found',
    });
  } catch (err) {
    return respondJson(res, 500, {
      ok: false,
      source: 'ctm-api',
      reason: 'ctm_session_context_failed',
      error: err.message,
    });
  }
}

async function handleMessageIndexBackfill({ body, res, context, db, dbModule }) {
  const limit = clampInt(body.limit, 100, 1, 1000);
  const chunkSize = clampInt(body.chunk_size ?? body.chunkSize, 250, 25, 2000);
  const dryRun = parseBool(body.dry_run);
  if (dryRun) {
    return respondJson(res, 200, asApiResult(context.backfillCtmSessionMessages({
      db,
      limit,
      dry_run: true,
    })));
  }

  if (!dbModule || typeof dbModule.backfillSessionMessagesAsync !== 'function') {
    return respondJson(res, 503, {
      ok: false,
      source: 'ctm-api',
      authority: 'ctm',
      reason: 'ctm_message_index_writer_unavailable',
    });
  }

  const insertedMessages = await dbModule.backfillSessionMessagesAsync({ limit, chunkSize });
  return respondJson(res, 200, asApiResult({
    ok: true,
    source: 'ctm-db',
    dry_run: false,
    limit,
    chunk_size: chunkSize,
    inserted_messages: insertedMessages,
    table_counts: tableCounts(db, ['session_conversations', 'session_messages', 'session_messages_fts']),
  }));
}

function asApiResult(result) {
  return {
    ...(result || {}),
    source: 'ctm-api',
    backend_source: result?.source || 'ctm-db',
    authority: 'ctm',
  };
}

function parseListParams(searchParams, ...names) {
  const values = [];
  for (const name of names) {
    for (const value of searchParams.getAll(name)) {
      values.push(...String(value || '').split(','));
    }
  }
  return values.map((value) => value.trim()).filter(Boolean);
}

function parseBool(value) {
  return value === true || value === 1 || value === '1' || value === 'true' || value === 'yes';
}

function clampInt(value, fallback, min, max) {
  const n = Number(value);
  if (!Number.isFinite(n)) return fallback;
  return Math.min(Math.max(Math.trunc(n), min), max);
}

function tableCounts(db, names) {
  const counts = {};
  if (!db) return counts;
  for (const name of names) {
    try {
      const exists = db.prepare("SELECT 1 FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?").get(name);
      if (!exists) continue;
      counts[name] = db.prepare(`SELECT COUNT(*) AS count FROM ${name}`).get().count;
    } catch (err) {
      counts._errors = counts._errors || {};
      counts._errors[name] = err.message;
    }
  }
  return counts;
}

function readJsonBody(req, maxBytes) {
  return new Promise((resolve, reject) => {
    let body = '';
    req.on('data', (chunk) => {
      body += chunk;
      if (body.length > maxBytes) {
        reject(new Error('request_body_too_large'));
        req.destroy();
      }
    });
    req.on('end', () => {
      try {
        resolve(body ? JSON.parse(body) : {});
      } catch (err) {
        reject(err);
      }
    });
    req.on('error', reject);
  });
}

function respondJson(res, status, payload) {
  res.writeHead(status, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(payload));
  return true;
}

module.exports = {
  handleCtmSessionContextApi,
  loadCtmSessionContext,
};
