import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import { config } from '../../lib/config.js';
import {
  type NoteDoc,
  notesCreateAgentAction,
  notesCreateDraft,
  notesGet,
  notesList,
  notesSearch,
} from '../../lib/notes-client.js';
import type { McpToolRequest } from '../tools/types.js';

const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7);

function requireProductScope(req: McpToolRequest): void {
  const requestProductId = req.jwtPayload?.productId;
  if (requestProductId && requestProductId !== 'bytelyst-notes') {
    throw new Error("Product scope mismatch: expected 'bytelyst-notes'");
  }
}

function requireUserId(req: McpToolRequest): string {
  requireProductScope(req);
  if (!req.jwtPayload?.sub) {
    throw new Error('Authentication required');
  }
  return req.jwtPayload.sub;
}

function mapNoteSummary(note: NoteDoc) {
  return {
    id: note.id,
    workspaceId: note.workspaceId,
    title: note.title,
    status: note.status,
    updatedAt: note.updatedAt,
    tags: note.tags,
  };
}

function mapNote(note: NoteDoc) {
  return {
    id: note.id,
    workspaceId: note.workspaceId,
    title: note.title,
    body: note.body,
    status: note.status,
    tags: note.tags,
    links: note.links,
    sourceType: note.sourceType,
    sourceUri: note.sourceUri,
    agentId: note.agentId,
    createdAt: note.createdAt,
    updatedAt: note.updatedAt,
  };
}

function getMatchFields(note: NoteDoc, query: string): Array<'title' | 'body' | 'tags'> {
  const lower = query.toLowerCase();
  const fields: Array<'title' | 'body' | 'tags'> = [];
  if (note.title.toLowerCase().includes(lower)) {
    fields.push('title');
  }
  if (note.body.toLowerCase().includes(lower)) {
    fields.push('body');
  }
  if (note.tags.some(tag => tag.toLowerCase().includes(lower))) {
    fields.push('tags');
  }
  return fields;
}

registerTool({
  name: 'notes.notes.list',
  description: 'List notes in a workspace with optional status and tag filters.',
  requiredRole: 'viewer',
  inputSchema: z.object({
    workspaceId: z.string().min(1).max(128),
    status: z.enum(['draft', 'active', 'archived']).optional(),
    tag: z.string().min(1).max(64).optional(),
    limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(50),
    offset: z.coerce.number().min(0).default(0),
  }),
  async execute(args, req) {
    requireUserId(req);
    const result = await notesList(args, { token: tokenOf(req), requestId: req.id });
    return {
      items: result.items.map(mapNoteSummary),
      total: result.total,
      limit: args.limit,
      offset: args.offset,
    };
  },
});

registerTool({
  name: 'notes.notes.get',
  description: 'Get a single note by note ID and workspace scope.',
  requiredRole: 'viewer',
  inputSchema: z.object({
    noteId: z.string().min(1).max(128),
    workspaceId: z.string().min(1).max(128),
  }),
  async execute(args, req) {
    requireUserId(req);
    const note = await notesGet(args.noteId, args.workspaceId, {
      token: tokenOf(req),
      requestId: req.id,
    });
    return mapNote(note);
  },
});

registerTool({
  name: 'notes.notes.search',
  description: 'Search notes in a workspace using lexical query plus optional filters.',
  requiredRole: 'viewer',
  inputSchema: z.object({
    workspaceId: z.string().min(1).max(128),
    query: z.string().min(1).max(200),
    status: z.enum(['draft', 'active', 'archived']).optional(),
    tag: z.string().min(1).max(64).optional(),
    limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(25),
    offset: z.coerce.number().min(0).default(0),
  }),
  async execute(args, req) {
    requireUserId(req);
    const result = await notesSearch(args, { token: tokenOf(req), requestId: req.id });
    return {
      query: args.query,
      items: result.items.map(note => ({
        ...mapNoteSummary(note),
        matchFields: getMatchFields(note, args.query),
      })),
      total: result.total,
      limit: args.limit,
      offset: args.offset,
    };
  },
});

registerTool({
  name: 'notes.notes.create_draft',
  description:
    'Create a note draft in a workspace. Supports dry-run, idempotency, and correlation metadata.',
  requiredRole: 'admin',
  inputSchema: z.object({
    workspaceId: z.string().min(1).max(128),
    title: z.string().min(1).max(500),
    body: z.string().min(1).max(50000),
    tags: z.array(z.string().min(1).max(64)).default([]),
    links: z.array(z.string().min(1).max(512)).default([]),
    sourceType: z.string().max(128).optional(),
    sourceUri: z.string().max(2000).optional(),
    agentId: z.string().max(128).optional(),
    dryRun: z.boolean().default(false),
    idempotencyKey: z.string().max(255).optional(),
    correlationId: z.string().max(255).optional(),
  }),
  async execute(args, req) {
    const userId = requireUserId(req);
    const noteId = randomUUID();
    const correlationId = args.correlationId ?? req.id;

    if (args.dryRun) {
      const now = new Date().toISOString();
      return {
        dryRun: true,
        state: 'proposed',
        note: mapNote({
          id: noteId,
          productId: 'bytelyst-notes',
          userId,
          workspaceId: args.workspaceId,
          title: args.title,
          body: args.body,
          status: 'draft',
          tags: args.tags,
          links: args.links,
          sourceType: args.sourceType,
          sourceUri: args.sourceUri,
          createdAt: now,
          updatedAt: now,
          createdBy: userId,
          updatedBy: userId,
          agentId: args.agentId,
        }),
        idempotencyKey: args.idempotencyKey,
        correlationId,
      };
    }

    const note = await notesCreateDraft(
      {
        id: noteId,
        workspaceId: args.workspaceId,
        title: args.title,
        body: args.body,
        tags: args.tags,
        links: args.links,
        sourceType: args.sourceType,
        sourceUri: args.sourceUri,
        agentId: args.agentId,
      },
      { token: tokenOf(req), requestId: req.id }
    );

    await notesCreateAgentAction(
      {
        id: randomUUID(),
        workspaceId: args.workspaceId,
        noteId: note.id,
        actorId: args.agentId ?? userId,
        actorType: args.agentId ? 'agent' : 'human',
        toolName: 'notes.notes.create_draft',
        actionType: 'create',
        state: 'proposed',
        reason: 'Created via shared MCP create_draft tool',
        afterSummary: `${note.title}\n\n${note.body}`.slice(0, 4000),
        idempotencyKey: args.idempotencyKey,
        correlationId,
        workflowId: req.id,
      },
      { token: tokenOf(req), requestId: req.id }
    );

    return {
      dryRun: false,
      state: 'draft',
      note: mapNote(note),
      idempotencyKey: args.idempotencyKey,
      correlationId,
    };
  },
});
