import { google } from 'googleapis';
import type { GmailConfig } from '../../config/schema.js';
import { createGoogleOAuth2Client } from '../../google/oauth.js';
import type { Tool, ToolResult } from '../types.js';
import { sanitizeHtml } from '../../utils/html.js';

function createOAuth2Client(config: NonNullable<GmailConfig>) {
  return createGoogleOAuth2Client({
    service: 'gmail',
    credentialsFile: config.credentials_file,
    tokenFile: config.token_file,
  });
}

interface EmailSummary {
  id: string;
  from: string;
  subject: string;
  date: string;
  snippet: string;
}

interface GmailFilterCreateArgs {
  from?: string;
  to?: string;
  subject?: string;
  query?: string;
  negatedQuery?: string;
  hasAttachment?: boolean;
  excludeChats?: boolean;
  size?: number;
  sizeComparison?: 'larger' | 'smaller';
  addLabelIds?: string[];
  removeLabelIds?: string[];
  forward?: string;
}

/** Fetch metadata for a single message. */
async function fetchMessageDetails(
  gmail: ReturnType<typeof google.gmail>,
  messageId: string,
): Promise<EmailSummary | null> {
  try {
    const msg = await gmail.users.messages.get({
      userId: 'me',
      id: messageId,
      format: 'metadata',
      metadataHeaders: ['From', 'Subject', 'Date'],
    });

    const headers = msg.data.payload?.headers ?? [];
    const getHeader = (name: string): string =>
      headers.find(h => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';

    return {
      id: messageId,
      from: getHeader('From'),
      subject: getHeader('Subject'),
      date: getHeader('Date'),
      snippet: sanitizeHtml(msg.data.snippet ?? ''),
    };
  } catch {
    return null;
  }
}

/** Decode a base64url-encoded string. */
function decodeBase64Url(data: string): string {
  return Buffer.from(data.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf-8');
}

/** Extract plain text body from a message payload, walking MIME parts recursively. */
function extractTextBody(payload: {
  mimeType?: string | null;
  body?: { data?: string | null } | null;
  parts?: Array<{
    mimeType?: string | null;
    body?: { data?: string | null } | null;
    parts?: unknown[];
  }> | null;
}): string {
  // Direct body (simple messages)
  if (payload.mimeType === 'text/plain' && payload.body?.data) {
    return decodeBase64Url(payload.body.data);
  }

  // Walk parts for multipart messages — prefer text/plain over text/html
  if (payload.parts) {
    let htmlFallback = '';
    for (const part of payload.parts) {
      if (part.mimeType === 'text/plain' && part.body?.data) {
        return decodeBase64Url(part.body.data);
      }
      if (part.mimeType === 'text/html' && part.body?.data) {
        htmlFallback = decodeBase64Url(part.body.data);
      }
      // Recurse into nested multipart
      if (part.parts) {
        const nested = extractTextBody(part as typeof payload);
        if (nested) {return nested;}
      }
    }
    if (htmlFallback) {
      // Convert HTML to clean plain text
      return sanitizeHtml(htmlFallback);
    }
  }

  return '';
}

/** Format a list of email summaries for tool output. */
function formatEmails(emails: EmailSummary[]): string {
  if (emails.length === 0) {
    return 'No messages found.';
  }

  return emails
    .map(e => `[${e.id}] ${e.date}\n  From: ${e.from}\n  Subject: ${e.subject}\n  ${e.snippet}`)
    .join('\n\n');
}

function formatCreatedFilter(args: GmailFilterCreateArgs, filterId: string): string {
  const criteriaParts: string[] = [];
  if (args.from) {criteriaParts.push(`from=${args.from}`);}
  if (args.to) {criteriaParts.push(`to=${args.to}`);}
  if (args.subject) {criteriaParts.push(`subject=${args.subject}`);}
  if (args.query) {criteriaParts.push(`query=${args.query}`);}
  if (args.negatedQuery) {criteriaParts.push(`negatedQuery=${args.negatedQuery}`);}
  if (typeof args.hasAttachment === 'boolean') {criteriaParts.push(`hasAttachment=${args.hasAttachment}`);}
  if (typeof args.excludeChats === 'boolean') {criteriaParts.push(`excludeChats=${args.excludeChats}`);}
  if (typeof args.size === 'number') {
    criteriaParts.push(`size=${args.size}`);
  }
  if (args.sizeComparison) {
    criteriaParts.push(`sizeComparison=${args.sizeComparison}`);
  }

  const actionParts: string[] = [];
  if (args.addLabelIds && args.addLabelIds.length > 0) {
    actionParts.push(`addLabelIds=[${args.addLabelIds.join(', ')}]`);
  }
  if (args.removeLabelIds && args.removeLabelIds.length > 0) {
    actionParts.push(`removeLabelIds=[${args.removeLabelIds.join(', ')}]`);
  }
  if (args.forward) {actionParts.push(`forward=${args.forward}`);}

  const output = [
    `Gmail filter created: ${filterId}`,
    `Criteria: ${criteriaParts.join(', ')}`,
    `Action: ${actionParts.join(', ')}`,
  ];

  return output.join('\n');
}

function normalizeStringArray(values: unknown): string[] {
  if (!Array.isArray(values)) {
    return [];
  }

  return values
    .filter((v): v is string => typeof v === 'string')
    .map(v => v.trim())
    .filter(Boolean);
}

function parseErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }
  return String(error);
}

/**
 * Creates Gmail query tools bound to the given GmailConfig.
 * Tools create their own OAuth2 client, independent of GmailWatcher.
 */
export function createGmailTools(config: NonNullable<GmailConfig>): Tool[] {
  const gmailList: Tool = {
    name: 'gmail.list',
    description:
      'List recent emails from Gmail. Returns id, from, subject, date, and snippet for each message.',
    requiredSecretScopes: ['gmail'],
    inputSchema: {
      type: 'object',
      properties: {
        maxResults: {
          type: 'number',
          description: 'Maximum number of emails to return (default: 10)',
        },
        label: {
          type: 'string',
          description: 'Gmail label to filter by (default: INBOX)',
        },
      },
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { maxResults?: number; label?: string };
      const maxResults = args.maxResults ?? 10;
      const label = args.label ?? 'INBOX';

      try {
        const auth = createOAuth2Client(config);
        const gmail = google.gmail({ version: 'v1', auth });

        const listResponse = await gmail.users.messages.list({
          userId: 'me',
          labelIds: [label],
          maxResults,
        });

        const messages = listResponse.data.messages ?? [];
        const emails: EmailSummary[] = [];

        for (const msg of messages) {
          if (!msg.id) {continue;}
          const details = await fetchMessageDetails(gmail, msg.id);
          if (details) {emails.push(details);}
        }

        return {
          success: true,
          output: formatEmails(emails),
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  const gmailSearch: Tool = {
    name: 'gmail.search',
    description:
      'Search Gmail using Gmail query syntax (e.g. "from:user@example.com", "is:unread", "subject:hello"). Returns id, from, subject, date, and snippet for each match.',
    requiredSecretScopes: ['gmail'],
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Gmail search query (same syntax as Gmail search bar)',
        },
        maxResults: {
          type: 'number',
          description: 'Maximum number of results to return (default: 10)',
        },
      },
      required: ['query'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { query: string; maxResults?: number };
      const maxResults = args.maxResults ?? 10;

      try {
        const auth = createOAuth2Client(config);
        const gmail = google.gmail({ version: 'v1', auth });

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

        const messages = listResponse.data.messages ?? [];
        const emails: EmailSummary[] = [];

        for (const msg of messages) {
          if (!msg.id) {continue;}
          const details = await fetchMessageDetails(gmail, msg.id);
          if (details) {emails.push(details);}
        }

        return {
          success: true,
          output: formatEmails(emails),
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  const gmailRead: Tool = {
    name: 'gmail.read',
    description:
      'Read the full content of a Gmail message by ID. Returns headers (from, to, subject, date) and the full text body. Use gmail.list or gmail.search first to get message IDs.',
    requiredSecretScopes: ['gmail'],
    inputSchema: {
      type: 'object',
      properties: {
        id: {
          type: 'string',
          description: 'The Gmail message ID (from gmail.list or gmail.search results)',
        },
      },
      required: ['id'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { id: string };

      try {
        const auth = createOAuth2Client(config);
        const gmail = google.gmail({ version: 'v1', auth });

        const msg = await gmail.users.messages.get({
          userId: 'me',
          id: args.id,
          format: 'full',
        });

        const headers = msg.data.payload?.headers ?? [];
        const getHeader = (name: string): string =>
          headers.find(h => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';

        const body = msg.data.payload ? extractTextBody(msg.data.payload) : '';

        const parts = [
          `From: ${getHeader('From')}`,
          `To: ${getHeader('To')}`,
          `Subject: ${getHeader('Subject')}`,
          `Date: ${getHeader('Date')}`,
          '',
          body || '(no text content)',
        ];

        return {
          success: true,
          output: parts.join('\n'),
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  const gmailFilterCreate: Tool = {
    name: 'gmail.filter.create',
    description:
      'Create a Gmail filter rule using Gmail API criteria and actions (e.g. archive, apply labels, forward). Requires Gmail auth with filter-management scope.',
    requiredSecretScopes: ['gmail'],
    inputSchema: {
      type: 'object',
      properties: {
        from: {
          type: 'string',
          description: 'Filter by sender email/address text',
        },
        to: {
          type: 'string',
          description: 'Filter by recipient email/address text',
        },
        subject: {
          type: 'string',
          description: 'Filter by subject text',
        },
        query: {
          type: 'string',
          description: 'Gmail search query (supports operators like is:unread, has:attachment)',
        },
        negatedQuery: {
          type: 'string',
          description: 'Gmail search query that must NOT match',
        },
        hasAttachment: {
          type: 'boolean',
          description: 'Match messages that have attachments',
        },
        excludeChats: {
          type: 'boolean',
          description: 'Exclude Google Chat messages',
        },
        size: {
          type: 'number',
          description: 'Message size threshold in bytes (requires sizeComparison)',
        },
        sizeComparison: {
          type: 'string',
          enum: ['larger', 'smaller'],
          description: 'How to compare size threshold',
        },
        addLabelIds: {
          type: 'array',
          description: 'Label IDs to add when matched (system labels like INBOX/IMPORTANT also valid)',
          items: { type: 'string' },
        },
        removeLabelIds: {
          type: 'array',
          description: 'Label IDs to remove when matched (e.g. INBOX to archive)',
          items: { type: 'string' },
        },
        forward: {
          type: 'string',
          description: 'Forward matched mail to this pre-verified forwarding address',
        },
      },
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as GmailFilterCreateArgs;

      const hasCriteria = Boolean(
        args.from?.trim()
        || args.to?.trim()
        || args.subject?.trim()
        || args.query?.trim()
        || args.negatedQuery?.trim()
        || typeof args.hasAttachment === 'boolean'
        || typeof args.excludeChats === 'boolean'
        || typeof args.size === 'number'
        || args.sizeComparison,
      );
      if (!hasCriteria) {
        return {
          success: false,
          output: '',
          error: 'At least one filter criterion is required (e.g. from, subject, query, hasAttachment).',
        };
      }

      if (typeof args.size === 'number' && !args.sizeComparison) {
        return {
          success: false,
          output: '',
          error: 'sizeComparison is required when size is provided ("larger" or "smaller").',
        };
      }

      if (args.sizeComparison && typeof args.size !== 'number') {
        return {
          success: false,
          output: '',
          error: 'size is required when sizeComparison is provided.',
        };
      }

      const addLabelIds = normalizeStringArray(args.addLabelIds);
      const removeLabelIds = normalizeStringArray(args.removeLabelIds);
      const forward = args.forward?.trim();

      const hasAction = addLabelIds.length > 0 || removeLabelIds.length > 0 || Boolean(forward);
      if (!hasAction) {
        return {
          success: false,
          output: '',
          error: 'At least one filter action is required (addLabelIds, removeLabelIds, or forward).',
        };
      }

      try {
        const auth = createOAuth2Client(config);
        const gmail = google.gmail({ version: 'v1', auth });

        const createResponse = await gmail.users.settings.filters.create({
          userId: 'me',
          requestBody: {
            criteria: {
              from: args.from?.trim(),
              to: args.to?.trim(),
              subject: args.subject?.trim(),
              query: args.query?.trim(),
              negatedQuery: args.negatedQuery?.trim(),
              hasAttachment: args.hasAttachment,
              excludeChats: args.excludeChats,
              size: args.size,
              sizeComparison: args.sizeComparison,
            },
            action: {
              addLabelIds: addLabelIds.length > 0 ? addLabelIds : undefined,
              removeLabelIds: removeLabelIds.length > 0 ? removeLabelIds : undefined,
              forward,
            },
          },
        });

        const filterId = createResponse.data.id ?? 'unknown-id';
        return {
          success: true,
          output: formatCreatedFilter(
            {
              ...args,
              addLabelIds,
              removeLabelIds,
              forward,
            },
            filterId,
          ),
        };
      } catch (error) {
        const message = parseErrorMessage(error);
        const needsScopeHint = /insufficient.*scope|insufficientPermissions|Request had insufficient authentication scopes/i.test(message);
        return {
          success: false,
          output: '',
          error: needsScopeHint
            ? `${message}. Re-run "flynn gmail-auth" to grant Gmail filter-management permissions.`
            : message,
        };
      }
    },
  };

  return [gmailList, gmailSearch, gmailRead, gmailFilterCreate];
}
