import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { gmail_v1 } from 'googleapis';
import { getGmailClient } from '../../clients.js';
import { findHeaderValue, extractMessageBody, extractDomain } from './helpers.js';

// "reschedul" is intentionally truncated to catch both "reschedule" and "rescheduling".
const MEETING_KEYWORD_PATTERN =
  /\b(meeting|call|invite|invitation|calendar|schedule|reschedul|zoom|google meet|teams)\b/i;
const QUESTION_PATTERN = /\?/;
const ACTION_PATTERN =
  /\b(please|could you|can you|let me know|need|review|approve|sign|deadline|by (mon|tue|wed|thu|fri|sat|sun|today|tomorrow|next week|eod|cob))\b/i;

function extractTextBody(payload?: gmail_v1.Schema$MessagePart): string {
  const { text, html } = extractMessageBody(payload);
  if (text) return text;
  // No text/plain part — strip HTML tags as a best-effort fallback so the
  // heuristic regexes still have something to match against.
  return html.replace(/<style[\s\S]*?<\/style>|<script[\s\S]*?<\/script>|<[^>]+>/g, ' ');
}

function truncate(text: string, max: number): string {
  const collapsed = text.replace(/\s+/g, ' ').trim();
  if (collapsed.length <= max) return collapsed;
  return collapsed.slice(0, max) + '…';
}

export function register(server: FastMCP) {
  server.addTool({
    name: 'triageInbox',
    description:
      "Composite tool: fetches the user's most recent unread Gmail messages with full content and heuristic categorization in a single call. Returns headers, body excerpts, labels, plus per-message signals (newsletter, meeting reference, contains question, action requested) AND aggregate stats (total unread, top senders, breakdown by category). Designed for AI inbox triage workflows — use the returned data to decide which messages need a reply, can be archived, or warrant a draft response. Pairs naturally with createDraft, modifyMessageLabels, and trashMessage.",
    parameters: z.strictObject({
      maxResults: z
        .number()
        .int()
        .min(1)
        .max(50)
        .optional()
        .default(20)
        .describe('How many unread messages to triage in one pass (1-50). Defaults to 20.'),
      additionalQuery: z
        .string()
        .optional()
        .describe(
          'Optional Gmail query appended to "is:unread", e.g. "newer_than:2d" or "-from:notifications@".'
        ),
      bodyExcerptLength: z
        .number()
        .int()
        .min(0)
        .max(2000)
        .optional()
        .default(400)
        .describe('Max characters of body text to include per message (0 to skip bodies).'),
    }),
    execute: async (args, { log }) => {
      const gmail = await getGmailClient();
      const query = ['is:unread', args.additionalQuery].filter(Boolean).join(' ');
      log.info(`Triaging inbox (max=${args.maxResults}, q="${query}")`);

      try {
        const listResponse = await gmail.users.messages.list({
          userId: 'me',
          maxResults: args.maxResults,
          q: query,
        });

        const totalUnread = listResponse.data.resultSizeEstimate ?? 0;
        const messageRefs = listResponse.data.messages ?? [];

        if (messageRefs.length === 0) {
          return JSON.stringify(
            {
              summary: {
                totalUnread,
                fetched: 0,
                topSenders: [],
                newsletterCount: 0,
                meetingReferenceCount: 0,
                questionCount: 0,
                actionRequestedCount: 0,
              },
              messages: [],
            },
            null,
            2
          );
        }

        // allSettled so a single failed message fetch doesn't kill the whole
        // triage — partial results are still actionable for the agent.
        const settled = await Promise.allSettled(
          messageRefs.map((ref) =>
            gmail.users.messages.get({
              userId: 'me',
              id: ref.id!,
              format: 'full',
            })
          )
        );

        const failedFetches = settled.filter((r) => r.status === 'rejected').length;
        const detailed: gmail_v1.Schema$Message[] = [];
        for (const r of settled) {
          if (r.status === 'fulfilled') detailed.push(r.value.data);
        }

        const messages = detailed.map((msg) => {
          const headers = msg.payload?.headers;
          const from = findHeaderValue(headers, 'From');
          const subject = findHeaderValue(headers, 'Subject') ?? '(no subject)';
          // Standard newsletter headers per RFC 2369 (List-Unsubscribe) and RFC 2919 (List-Id).
          const hasUnsubscribe =
            findHeaderValue(headers, 'List-Unsubscribe') !== null ||
            findHeaderValue(headers, 'List-Id') !== null;
          const text = args.bodyExcerptLength > 0 ? extractTextBody(msg.payload) : '';
          const bodyExcerpt =
            args.bodyExcerptLength > 0 ? truncate(text, args.bodyExcerptLength) : '';
          const searchSurface = `${subject} ${text}`;

          return {
            id: msg.id,
            threadId: msg.threadId,
            from,
            domain: extractDomain(from),
            to: findHeaderValue(headers, 'To'),
            subject,
            date: findHeaderValue(headers, 'Date'),
            snippet: msg.snippet ?? '',
            bodyExcerpt,
            labels: msg.labelIds ?? [],
            isNewsletter: hasUnsubscribe,
            containsMeetingReference: MEETING_KEYWORD_PATTERN.test(searchSurface),
            containsQuestion: QUESTION_PATTERN.test(searchSurface),
            actionRequested: ACTION_PATTERN.test(searchSurface),
          };
        });

        const senderCounts = new Map<string, number>();
        for (const m of messages) {
          if (!m.from) continue;
          senderCounts.set(m.from, (senderCounts.get(m.from) ?? 0) + 1);
        }
        const topSenders = [...senderCounts.entries()]
          .sort((a, b) => b[1] - a[1])
          .slice(0, 5)
          .map(([from, count]) => ({ from, count }));

        const summary = {
          totalUnread,
          fetched: messages.length,
          failedFetches,
          topSenders,
          newsletterCount: messages.filter((m) => m.isNewsletter).length,
          meetingReferenceCount: messages.filter((m) => m.containsMeetingReference).length,
          questionCount: messages.filter((m) => m.containsQuestion).length,
          actionRequestedCount: messages.filter((m) => m.actionRequested).length,
        };

        return JSON.stringify({ summary, messages }, null, 2);
      } catch (error: any) {
        log.error(`Error triaging inbox: ${error.message || error}`);
        if (error.code === 401)
          throw new UserError(
            'Gmail authorization failed. Re-authorize to grant the gmail.modify scope.'
          );
        if (error.code === 403)
          throw new UserError('Permission denied. Confirm the gmail.modify scope was granted.');
        throw new UserError(`Failed to triage inbox: ${error.message || 'Unknown error'}`);
      }
    },
  });
}
