/**
 * Page PDF Tool - Generate PDF from current page
 */

import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { MCPServer } from '../mcp-server';
import { MCPToolDefinition, MCPResult, ToolHandler } from '../types/mcp';
import { TOOL_ANNOTATIONS } from '../types/tool-annotations';
import { getSessionManager } from '../session-manager';

const definition: MCPToolDefinition = {
  name: 'page_pdf',
  description: 'Generate PDF from page. Saves to path or returns base64.',
  inputSchema: {
    type: 'object',
    properties: {
      tabId: {
        type: 'string',
        description: 'Tab ID to generate PDF from',
      },
      path: {
        type: 'string',
        description: 'Save path. Omit for base64',
      },
      format: {
        type: 'string',
        enum: ['A4', 'Letter', 'Legal', 'Tabloid', 'A3', 'A5'],
        description: 'Paper format. Default: A4',
      },
      landscape: {
        type: 'boolean',
        description: 'Landscape mode. Default: false',
      },
      printBackground: {
        type: 'boolean',
        description: 'Print backgrounds. Default: true',
      },
      scale: {
        type: 'number',
        description: 'Render scale (0.1-2.0). Default: 1',
      },
      marginTop: {
        type: 'string',
        description: 'Top margin, e.g. "1cm"',
      },
      marginRight: {
        type: 'string',
        description: 'Right margin, e.g. "1cm"',
      },
      marginBottom: {
        type: 'string',
        description: 'Bottom margin, e.g. "1cm"',
      },
      marginLeft: {
        type: 'string',
        description: 'Left margin, e.g. "1cm"',
      },
      pageRanges: {
        type: 'string',
        description: 'Page ranges, e.g. "1-5, 8, 11-13"',
      },
      displayHeaderFooter: {
        type: 'boolean',
        description: 'Show header/footer. Default: false',
      },
      headerTemplate: {
        type: 'string',
        description: 'Header HTML template (needs displayHeaderFooter)',
      },
      footerTemplate: {
        type: 'string',
        description: 'Footer HTML template (needs displayHeaderFooter)',
      },
    },
    required: ['tabId'],
  },
  annotations: TOOL_ANNOTATIONS.page_pdf,
};

const handler: ToolHandler = async (
  sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const tabId = args.tabId as string;
  const filePath = args.path as string | undefined;
  const format = (args.format as string | undefined) ?? 'A4';
  const landscape = (args.landscape as boolean | undefined) ?? false;
  const printBackground = (args.printBackground as boolean | undefined) ?? true;
  const scale = (args.scale as number | undefined) ?? 1;
  const marginTop = args.marginTop as string | undefined;
  const marginRight = args.marginRight as string | undefined;
  const marginBottom = args.marginBottom as string | undefined;
  const marginLeft = args.marginLeft as string | undefined;
  const pageRanges = args.pageRanges as string | undefined;
  const displayHeaderFooter = (args.displayHeaderFooter as boolean | undefined) ?? false;
  const headerTemplate = args.headerTemplate as string | undefined;
  const footerTemplate = args.footerTemplate as string | undefined;

  const sessionManager = getSessionManager();

  if (!tabId) {
    return {
      content: [{ type: 'text', text: 'Error: tabId is required' }],
      isError: true,
    };
  }

  // Validate scale
  if (scale < 0.1 || scale > 2.0) {
    return {
      content: [{ type: 'text', text: 'Error: scale must be between 0.1 and 2.0' }],
      isError: true,
    };
  }

  try {
    const page = await sessionManager.getPage(sessionId, tabId, undefined, 'page_pdf');
    if (!page) {
      return {
        content: [{ type: 'text', text: `Error: Tab ${tabId} not found` }],
        isError: true,
      };
    }

    // Build PDF options
    const pdfOptions: Parameters<typeof page.pdf>[0] = {
      format: format as 'A4' | 'Letter' | 'Legal' | 'Tabloid' | 'A3' | 'A5',
      landscape,
      printBackground,
      scale,
      displayHeaderFooter,
    };

    // Add margins if specified
    if (marginTop || marginRight || marginBottom || marginLeft) {
      pdfOptions.margin = {
        top: marginTop,
        right: marginRight,
        bottom: marginBottom,
        left: marginLeft,
      };
    }

    if (pageRanges) {
      pdfOptions.pageRanges = pageRanges;
    }

    if (displayHeaderFooter) {
      if (headerTemplate) pdfOptions.headerTemplate = headerTemplate;
      if (footerTemplate) pdfOptions.footerTemplate = footerTemplate;
    }

    // Generate PDF (with 60s timeout)
    let pdfTid: ReturnType<typeof setTimeout>;
    const pdfBuffer = await Promise.race([
      page.pdf(pdfOptions).finally(() => clearTimeout(pdfTid)),
      new Promise<never>((_, reject) => {
        pdfTid = setTimeout(() => reject(new Error('PDF generation timed out after 60000ms')), 60000);
      }),
    ]);

    if (filePath) {
      // Resolve path (support ~ for home directory)
      let resolvedPath = filePath;
      if (filePath.startsWith('~')) {
        resolvedPath = path.join(os.homedir(), filePath.slice(1));
      } else if (process.platform === 'win32' && filePath.startsWith('%USERPROFILE%')) {
        const rest = filePath.slice('%USERPROFILE%'.length).replace(/^[/\\]+/, '');
        resolvedPath = path.join(os.homedir(), rest);
      } else if (!path.isAbsolute(filePath)) {
        resolvedPath = path.resolve(filePath);
      }

      // Validate the output path — block writes to sensitive directories
      const normalizedPath = path.resolve(resolvedPath);
      const homeDir = os.homedir();
      const sensitiveRoots = [
        path.join(homeDir, '.ssh'),
        path.join(homeDir, '.gnupg'),
        path.join(homeDir, '.aws'),
      ];
      if (sensitiveRoots.some(root => normalizedPath.startsWith(root + path.sep) || normalizedPath === root)) {
        return {
          content: [{ type: 'text', text: `Error: Cannot write PDF to sensitive directory "${path.dirname(normalizedPath)}"` }],
          isError: true,
        };
      }

      // Ensure directory exists
      const dir = path.dirname(resolvedPath);
      await fs.mkdir(dir, { recursive: true });

      // Write file
      await fs.writeFile(resolvedPath, pdfBuffer);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              action: 'page_pdf',
              path: resolvedPath,
              size: pdfBuffer.length,
              sizeKB: Math.round(pdfBuffer.length / 1024),
              format,
              landscape,
              message: `PDF saved to ${resolvedPath} (${Math.round(pdfBuffer.length / 1024)} KB)`,
            }),
          },
        ],
      };
    } else {
      // Check size before returning base64 (5MB limit, matching page_screenshot)
      const fiveMB = 5 * 1024 * 1024;
      if (pdfBuffer.length > fiveMB) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: PDF is ${Math.round(pdfBuffer.length / 1024 / 1024 * 10) / 10}MB which exceeds the 5MB inline limit. Use the 'path' parameter to save to a file instead.`,
            },
          ],
          isError: true,
        };
      }

      const base64 = Buffer.from(pdfBuffer).toString('base64');

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              action: 'page_pdf',
              size: pdfBuffer.length,
              sizeKB: Math.round(pdfBuffer.length / 1024),
              format,
              landscape,
              message: `PDF generated (${Math.round(pdfBuffer.length / 1024)} KB). Use the base64 field to access the data.`,
              base64,
            }),
          },
        ],
      };
    }
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `PDF error: ${error instanceof Error ? error.message : String(error)}`,
        },
      ],
      isError: true,
    };
  }
};

export function registerPagePdfTool(server: MCPServer): void {
  server.registerTool('page_pdf', handler, definition);
}
