/**
 * memory-api.ts — REST router for user memory entries
 *
 * Mounted at /api/local/memory
 *
 * Routes:
 *   GET  /entries          — list parsed entries + MEMORY.md index
 *   PUT  /entries/:name    — upsert entry (frontmatter validation enforced)
 *   DELETE /entries/:name  — remove entry + update index
 *
 * Auth: all routes require an authenticated user (req.user).
 * Owner: each operation scopes to req.user.id — no cross-user access.
 */

import { Router, type Request, type Response } from 'express';
import { join } from 'path';
import { logger } from '../logger.js';
import {
  isValidMemoryName,
  MEMORY_TYPES,
  type MemoryType,
  listMemoryEntries,
  readMemoryIndexFromDir,
  upsertMemoryEntry,
  removeMemoryEntry,
} from '../user-folder/memory.js';
import { userMemoryDir } from '../user-folder/paths.js';
import { withUserLock } from '../engine/reflection/user-lock.js';
import { loadConfig } from '../config.js';

// ── Types ──────────────────────────────────────────────────────────────────────

interface AuthedUser { id: string; role: string; }

function getUser(req: Request): AuthedUser | null {
  return (req.user as AuthedUser | undefined) ?? null;
}

// ── Deps ───────────────────────────────────────────────────────────────────────

export interface MemoryApiDeps {
  /** Root data dir (same as userFolderRoot / dataDir in the rest of the app). */
  dataDir: string;
  /** When false (local-dev mode), inject a synthetic 'local' user if req.user absent. */
  authActive?: boolean;
}

// ── Factory ────────────────────────────────────────────────────────────────────

export function createMemoryApi(deps: MemoryApiDeps): Router {
  const { dataDir } = deps;
  const authActive = deps.authActive ?? true;

  const r = Router();

  // JSON body parser for this router
  r.use((_req, _res, next) => {
    // body-parser already applied globally only for certain routes; apply here too
    next();
  });

  // ── Auth gate ──────────────────────────────────────────────────────────────
  r.use((req: Request, res: Response, next) => {
    if (!authActive && !getUser(req)) {
      (req as any).user = { id: 'local', role: 'user' };
    }
    if (!getUser(req)) {
      res.status(401).json({ error: 'Unauthenticated' });
      return;
    }
    next();
  });

  // ── GET /entries ───────────────────────────────────────────────────────────
  r.get('/entries', (req: Request, res: Response) => {
    const u = getUser(req)!;
    const memDir = userMemoryDir(dataDir, u.id);

    try {
      const entries = listMemoryEntries(memDir);
      const index = readMemoryIndexFromDir(memDir);
      res.json({ entries, index });
    } catch (err) {
      logger.error(`[memory-api] GET /entries failed user=${u.id} err=${err}`);
      res.status(500).json({ error: 'Failed to list memory entries' });
    }
  });

  // ── PUT /entries/:name ─────────────────────────────────────────────────────
  // Validation thresholds (name pattern, four-value type, body byte cap) are
  // shared with the reflection applier's semantic validator at
  // `src/engine/reflection/semantic-validator.ts`. The rejection codes here
  // match the ReflectionRejectionCode union so the UI can render either source
  // consistently. If you add/rename a code, update both files together.
  r.put('/entries/:name', async (req: Request, res: Response) => {
    const u = getUser(req)!;
    const { name } = req.params;

    // Validate name (mirrors semantic-validator's rejected_bad_name path)
    if (!isValidMemoryName(name)) {
      res.status(400).json({ error: 'rejected_bad_name' });
      return;
    }

    const body = req.body as Record<string, unknown> | undefined;
    if (!body || typeof body !== 'object') {
      res.status(400).json({ error: 'rejected_bad_request' });
      return;
    }

    const { description, type, body: entryBody } = body as {
      description?: unknown;
      type?: unknown;
      body?: unknown;
    };

    // Validate description
    if (typeof description !== 'string' || description.trim() === '') {
      res.status(400).json({ error: 'rejected_bad_description' });
      return;
    }
    if (description.includes('\n') || description.includes('\r')) {
      res.status(400).json({ error: 'rejected_bad_description' });
      return;
    }

    // Validate type
    if (!MEMORY_TYPES.includes(type as MemoryType)) {
      res.status(400).json({ error: 'rejected_unknown_type' });
      return;
    }

    // Validate body
    if (typeof entryBody !== 'string') {
      res.status(400).json({ error: 'rejected_bad_body' });
      return;
    }

    // Body byte-length cap from config
    const cfg = loadConfig();
    const maxBodyBytes = cfg.reflection.maxEntryBodyBytes;
    if (Buffer.byteLength(entryBody, 'utf-8') > maxBodyBytes) {
      res.status(400).json({ error: 'rejected_body_too_large' });
      return;
    }

    try {
      const result = await withUserLock(dataDir, u.id, async () => {
        return upsertMemoryEntry(dataDir, u.id, {
          name,
          type: type as MemoryType,
          description: description.trim(),
          body: entryBody,
        });
      });
      logger.info(`[memory-api] PUT /entries/${name} user=${u.id} path=${result.path}`);
      res.json({ ok: true, name, path: result.path });
    } catch (err) {
      logger.error(`[memory-api] PUT /entries/${name} failed user=${u.id} err=${err}`);
      res.status(500).json({ error: 'Failed to upsert memory entry' });
    }
  });

  // ── DELETE /entries/:name ──────────────────────────────────────────────────
  r.delete('/entries/:name', async (req: Request, res: Response) => {
    const u = getUser(req)!;
    const { name } = req.params;

    // Validate name — do not leak existence for invalid names
    if (!isValidMemoryName(name)) {
      res.status(404).json({ error: 'not_found' });
      return;
    }

    try {
      const removed = await withUserLock(dataDir, u.id, async () => {
        return removeMemoryEntry(dataDir, u.id, name);
      });

      if (!removed) {
        res.status(404).json({ error: 'not_found' });
        return;
      }

      logger.info(`[memory-api] DELETE /entries/${name} user=${u.id}`);
      res.json({ ok: true, name });
    } catch (err) {
      logger.error(`[memory-api] DELETE /entries/${name} failed user=${u.id} err=${err}`);
      res.status(500).json({ error: 'Failed to delete memory entry' });
    }
  });

  return r;
}
