import { Router, Request, Response, NextFunction, json as expressJson } from 'express';
import { NotesService } from '../notes/notes-service.js';

export interface NotesApiDeps {
  service: NotesService;
  /**
   * Whether the bridge wired the auth subsystem (Passport / OAuth /
   * sessions). When `false`, requests will not have a populated
   * `req.user` because the upstream `requireAuth` middleware was not
   * mounted; the route fills in a synthetic `local` user so the notes
   * UI works in no-auth single-user mode (mirrors user-folder-api and
   * dashboard-api). Defaults to `true` for backwards compatibility
   * with the previous (auth-only) call sites.
   */
  authActive?: boolean;
}

const NAME_RE = /^[a-zA-Z0-9._-]+$/;

export function createNotesApi(deps: NotesApiDeps): Router {
  const router = Router();
  const authActive = deps.authActive ?? true;
  router.use((req: Request, res: Response, next: NextFunction) => {
    if (!authActive && !(req as any).user) {
      // No-auth single-user deployment: synthesize a `local` user so
      // per-user storage (and the notes index) has a stable owner.
      // Real auth flows are unaffected because Passport populates
      // req.user before we get here.
      //
      // orgIds defaults to [] explicitly — NotesService reads
      // user.orgIds.length when computing org-scoped visibility, and
      // crashes on the undefined access otherwise.
      (req as any).user = { id: 'local', role: 'user', orgIds: [] };
    }
    if (!(req as any).user) {
      res.status(401).json({ error: 'unauthenticated' });
      return;
    }
    // `/api/notes` is NOT behind the `/api/local` requireAuth prefix, so it
    // must enforce account status itself. Otherwise a disabled/pending user
    // holding a still-valid session keeps notes access (parity with
    // requireAuth in auth.ts). The synthetic no-auth `local` user has no
    // status field and is unaffected.
    const status = (req as any).user.status;
    if (authActive && status !== undefined && status !== 'active') {
      res.status(403).json({ error: 'account is not active' });
      return;
    }
    next();
  });
  // Size-limited JSON parser replaces the hand-rolled unbounded reader below.
  router.use(expressJson({ limit: '64kb' }));

  router.get('/discover', (req, res) => {
    const user = (req as any).user;
    const rawOwnerId = req.query.owner_id ? String(req.query.owner_id) : undefined;
    // `me` is a UI-convenient alias for the caller's own id (matches /reindex).
    // Without this alias the FileTree under the Notes tab silently shows zero
    // entries when it queries `?owner_id=me`, because the DB stores rows under
    // the real user.id and never matches the literal string "me".
    const ownerId = rawOwnerId === 'me' ? user.id : rawOwnerId;
    const folder = req.query.folder ? String(req.query.folder) : undefined;
    const q = req.query.q ? String(req.query.q) : undefined;
    const limit = req.query.limit ? Math.max(1, Math.min(200, parseInt(String(req.query.limit), 10) || 50)) : 50;
    const offset = req.query.offset ? Math.max(0, parseInt(String(req.query.offset), 10) || 0) : 0;
    try {
      const rows = deps.service.discoverNotes({ user, ownerId, folder, q, limit, offset });
      res.json({ rows });
    } catch (err) {
      res.status(400).json({ error: (err as Error).message });
    }
  });

  router.get('/file', (req, res) => {
    const user = (req as any).user;
    const rawOwnerId = String(req.query.owner_id ?? '');
    const ownerId = rawOwnerId === 'me' ? user.id : rawOwnerId;
    const folder = String(req.query.folder ?? '');
    const fileName = String(req.query.file_name ?? '');
    if (!NAME_RE.test(folder) || !NAME_RE.test(fileName)) {
      res.status(400).json({ error: 'invalid folder or file_name' });
      return;
    }
    try {
      const out = deps.service.getCrossUserNote({ user, ownerId, folder, fileName });
      if (!out) {
        res.status(404).json({ error: 'not found or no permission' });
        return;
      }
      res.json({ fm: out.fm, body: out.body, content: out.content });
    } catch (err) {
      res.status(400).json({ error: (err as Error).message });
    }
  });

  router.get('/subscriptions', (req, res) => {
    const user = (req as any).user;
    const rows = deps.service.listSubscriptions(user.id);
    res.json({ rows });
  });

  router.put('/subscriptions', (req, res) => {
    const user = (req as any).user;
    const body = (req as any).body ?? {};
    const publisherUserId = String(body.publisher_user_id ?? '');
    const folder = String(body.folder ?? '');
    const mode = String(body.mode ?? '');
    const enabled = body.enabled === false ? 0 : 1;
    if (!publisherUserId || !NAME_RE.test(folder)) {
      res.status(400).json({ error: 'invalid publisher_user_id or folder' });
      return;
    }
    if (mode !== 'search' && mode !== 'inject') {
      res.status(400).json({ error: 'invalid mode (must be search or inject)' });
      return;
    }
    try {
      deps.service.upsertSubscription({
        consumerUser: user,
        publisherUserId, folder, mode, enabled,
      });
      res.json({ ok: true });
    } catch (err) {
      const msg = (err as Error).message;
      if (msg.includes('no visible notes')) {
        res.status(403).json({ error: msg });
      } else {
        res.status(400).json({ error: msg });
      }
    }
  });

  router.delete('/subscriptions', (req, res) => {
    const user = (req as any).user;
    const publisherUserId = String(req.query.publisher_user_id ?? '');
    const folder = String(req.query.folder ?? '');
    if (!publisherUserId || !NAME_RE.test(folder)) {
      res.status(400).json({ error: 'invalid publisher_user_id or folder' });
      return;
    }
    deps.service.deleteSubscription({ consumerUserId: user.id, publisherUserId, folder });
    res.json({ ok: true });
  });

  router.post('/reindex', (req, res) => {
    const user = (req as any).user;
    const ownerIdParam = req.query.owner_id ? String(req.query.owner_id) : 'me';
    const ownerId = ownerIdParam === 'me' ? user.id : ownerIdParam;
    if (ownerId !== user.id && user.role !== 'admin') {
      res.status(403).json({ error: 'forbidden: only owner or admin can reindex' });
      return;
    }
    try {
      const stats = deps.service.reindex(ownerId);
      res.json({ ok: true, ...stats });
    } catch (err) {
      res.status(500).json({ error: (err as Error).message });
    }
  });

  router.get('/inject-preview', (req, res) => {
    const user = (req as any).user;
    const preview = deps.service.injectPreview(user);
    res.json(preview);
  });

  return router;
}
