import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { google } from 'googleapis';
import { getAuthClient, getSheetsClient } from '../../../clients.js';

export function register(server: FastMCP) {
  server.addTool({
    name: 'listSheetsComments',
    description:
      'Lists all comments in a Google Spreadsheet. Filter by sheet name, specific row(s), specific cell, or a cell range. Returns comment IDs needed for getSheetsComment, replyToSheetsComment, resolveSheetsComment, or deleteSheetsComment.',
    parameters: z.strictObject({
      spreadsheetId: z
        .string()
        .describe(
          'The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'
        ),
      sheetName: z
        .string()
        .optional()
        .describe('Optional: filter comments to only this sheet/tab name.'),
      row: z
        .number()
        .int()
        .min(1)
        .optional()
        .describe(
          'Optional: filter to comments on this specific row number (1-based, e.g., 5 for row 5).'
        ),
      rows: z
        .array(z.number().int().min(1))
        .optional()
        .describe(
          'Optional: filter to comments on any of these row numbers (1-based, e.g., [3, 5, 12]). Ignored if "row" is set.'
        ),
      cell: z
        .string()
        .optional()
        .describe(
          'Optional: filter to comments on this specific cell in A1 notation (e.g., "B3"). Overrides row/rows filters.'
        ),
      range: z
        .string()
        .optional()
        .describe(
          'Optional: filter to comments within this A1 range (e.g., "A1:D10", "B:B" for entire column B). Overrides row/rows/cell filters.'
        ),
      includeResolved: z
        .boolean()
        .optional()
        .default(false)
        .describe('If true, include resolved comments. Defaults to false.'),
    }),
    execute: async (args, { log }) => {
      log.info(`Listing comments for spreadsheet ${args.spreadsheetId}`);

      try {
        const authClient = await getAuthClient();
        const drive = google.drive({ version: 'v3', auth: authClient });

        const comments: any[] = [];
        let pageToken: string | undefined;

        do {
          const response = await drive.comments.list({
            fileId: args.spreadsheetId,
            fields:
              'comments(id,content,anchor,quotedFileContent,author,createdTime,modifiedTime,resolved,replies(id,content,author,createdTime)),nextPageToken',
            pageSize: 100,
            ...(pageToken ? { pageToken } : {}),
          });

          comments.push(...(response.data.comments || []));
          pageToken = response.data.nextPageToken || undefined;
        } while (pageToken);

        let sheetGidMap: Record<string, string> = {};
        const needSheetMeta = args.sheetName || args.row || args.rows || args.cell || args.range;

        if (needSheetMeta) {
          const sheetsClient = await getSheetsClient();
          const meta = await sheetsClient.spreadsheets.get({
            spreadsheetId: args.spreadsheetId,
            fields: 'sheets.properties',
          });
          for (const sheet of meta.data.sheets || []) {
            const props = sheet.properties;
            if (props?.sheetId !== undefined && props?.title) {
              sheetGidMap[String(props.sheetId)] = props.title;
            }
          }
        }

        const rangeFilter = args.range ? parseA1Range(args.range) : null;
        const cellFilter = args.cell ? a1ToRowCol(args.cell) : null;
        const rowSet = args.row
          ? new Set([args.row - 1])
          : args.rows
            ? new Set(args.rows.map((r) => r - 1))
            : null;

        const result = comments
          .filter((c) => {
            if (!args.includeResolved && c.resolved) return false;
            const cellInfo = c.anchor ? parseSheetsAnchor(c.anchor) : null;

            if (args.sheetName && cellInfo) {
              const resolvedName = sheetGidMap[String(cellInfo.sheetId)];
              if (resolvedName && resolvedName !== args.sheetName) return false;
            }

            if (!cellInfo) return !rangeFilter && !cellFilter && !rowSet;

            if (rangeFilter) {
              return (
                cellInfo.row >= rangeFilter.startRow &&
                cellInfo.row <= rangeFilter.endRow &&
                cellInfo.col >= rangeFilter.startCol &&
                cellInfo.col <= rangeFilter.endCol
              );
            }
            if (cellFilter) {
              return cellInfo.row === cellFilter.row && cellInfo.col === cellFilter.col;
            }
            if (rowSet) {
              return rowSet.has(cellInfo.row);
            }
            return true;
          })
          .map((comment: any) => {
            const cellInfo = comment.anchor ? parseSheetsAnchor(comment.anchor) : null;
            let cellRef: string | null = null;
            let sheetTitle: string | null = null;

            if (cellInfo) {
              cellRef = rowColToA1(cellInfo.row, cellInfo.col);
              if (Object.keys(sheetGidMap).length > 0) {
                sheetTitle = sheetGidMap[String(cellInfo.sheetId)] || null;
              }
            }

            return {
              id: comment.id,
              author: comment.author?.displayName || null,
              content: comment.content,
              cell: cellRef,
              sheetName: sheetTitle,
              quotedText: comment.quotedFileContent?.value || null,
              resolved: comment.resolved || false,
              createdTime: comment.createdTime,
              modifiedTime: comment.modifiedTime,
              replyCount: comment.replies?.length || 0,
            };
          });

        return JSON.stringify({ comments: result, total: result.length }, null, 2);
      } catch (error: any) {
        log.error(`Error listing sheets comments: ${error.message || error}`);
        throw new UserError(
          `Failed to list spreadsheet comments: ${error.message || 'Unknown error'}`
        );
      }
    },
  });
}

function parseSheetsAnchor(
  anchorStr: string
): { sheetId: number; row: number; col: number } | null {
  try {
    const anchor = JSON.parse(anchorStr);
    const actions = anchor.a;
    if (!actions || !Array.isArray(actions)) return null;
    for (const action of actions) {
      if (action.sht) {
        const sid = action.sht.sid;
        const rng = action.sht.rng;
        if (sid !== undefined && rng) {
          return { sheetId: sid, row: rng.r || 0, col: rng.c || 0 };
        }
      }
    }
    return null;
  } catch {
    return null;
  }
}

function rowColToA1(row: number, col: number): string {
  let colStr = '';
  let c = col;
  do {
    colStr = String.fromCharCode(65 + (c % 26)) + colStr;
    c = Math.floor(c / 26) - 1;
  } while (c >= 0);
  return `${colStr}${row + 1}`;
}

function a1ToRowCol(a1: string): { row: number; col: number } {
  const match = a1.match(/^([A-Za-z]+)(\d+)$/);
  if (!match) return { row: 0, col: 0 };
  const colStr = match[1].toUpperCase();
  let col = 0;
  for (let i = 0; i < colStr.length; i++) {
    col = col * 26 + (colStr.charCodeAt(i) - 64);
  }
  return { row: parseInt(match[2], 10) - 1, col: col - 1 };
}

function parseA1Range(range: string): {
  startRow: number;
  endRow: number;
  startCol: number;
  endCol: number;
} {
  const stripped = range.includes('!') ? range.split('!')[1] : range;
  const parts = stripped.split(':');

  const colOnly = /^[A-Za-z]+$/;
  const rowOnly = /^\d+$/;

  const parseCorner = (s: string): { row: number | null; col: number | null } => {
    if (colOnly.test(s)) {
      const upper = s.toUpperCase();
      let c = 0;
      for (let i = 0; i < upper.length; i++) c = c * 26 + (upper.charCodeAt(i) - 64);
      return { row: null, col: c - 1 };
    }
    if (rowOnly.test(s)) {
      return { row: parseInt(s, 10) - 1, col: null };
    }
    const rc = a1ToRowCol(s);
    return { row: rc.row, col: rc.col };
  };

  if (parts.length === 1) {
    const p = parseCorner(parts[0]);
    return {
      startRow: p.row ?? 0,
      endRow: p.row ?? 999999,
      startCol: p.col ?? 0,
      endCol: p.col ?? 999999,
    };
  }

  const start = parseCorner(parts[0]);
  const end = parseCorner(parts[1]);
  return {
    startRow: start.row ?? 0,
    endRow: end.row ?? 999999,
    startCol: start.col ?? 0,
    endCol: end.col ?? 999999,
  };
}
