import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
import type { FastifyBaseLogger } from 'fastify';
import type { MCPClientConnector } from '../../harness/mcp-client.js';

/**
 * MCP Tool Wrapper
 *
 * Wraps remote MCP server tools as standard LangChain tools.
 * Provides dynamic tool creation based on MCP tool definitions.
 */

export interface MCPToolInfo {
  name: string;
  description?: string;
  inputSchema?: {
    type: string;
    properties?: Record<string, any>;
    required?: string[];
  };
}

/**
 * Create a LangChain tool from an MCP tool definition
 */
export function createMCPToolWrapper(
  toolInfo: MCPToolInfo,
  mcpClient: MCPClientConnector,
  logger: FastifyBaseLogger,
  onImage?: (image: { data: string; mimeType: string }) => void,
  onWorkspaceMutation?: (storeName: string, newState: unknown) => void
): DynamicStructuredTool {
  // Convert MCP input schema to Zod schema
  const zodSchema = mcpInputSchemaToZod(toolInfo.inputSchema);

  return new DynamicStructuredTool({
    name: toolInfo.name,
    description: toolInfo.description || `MCP tool: ${toolInfo.name}`,
    schema: zodSchema,
    func: async (input: Record<string, unknown>) => {
      try {
        const result = await mcpClient.callTool(toolInfo.name, input);

        logger.info({ tool: toolInfo.name }, 'MCP tool call completed');

        // Fire workspace mutation callback when WorkspacePatch or WorkspaceWrite succeeds.
        // The sandbox returns {"success": true, "data": <newState>} as a text content item.
        if (onWorkspaceMutation) {
          const content = (result as any)?.content;
          if (Array.isArray(content)) {
            for (const item of content) {
              if (item.type === 'text' && item.text) {
                try {
                  const parsed = JSON.parse(item.text);
                  // WorkspacePatch / WorkspaceWrite: {"success": true, "data": <state>}
                  if (
                    (toolInfo.name === 'WorkspacePatch' || toolInfo.name === 'WorkspaceWrite') &&
                    parsed?.success && parsed?.data !== undefined
                  ) {
                    const storeName = (input as any).store_name as string;
                    onWorkspaceMutation(storeName, parsed.data);
                  }
                  // PythonWrite / PythonEdit / PythonDelete / PythonRevert:
                  // {"_workspace_sync": {"store": <name>, "data": <state>}}
                  if (parsed?._workspace_sync?.store && parsed._workspace_sync.data !== undefined) {
                    const storeName = parsed._workspace_sync.store as string;
                    onWorkspaceMutation(storeName, parsed._workspace_sync.data);
                  }
                } catch { /* ignore parse errors */ }
              }
            }
          }
        }

        // Handle different MCP result formats
        if (typeof result === 'string') {
          return result;
        }

        // Handle structured MCP responses with content arrays
        if (result && typeof result === 'object') {
          // Extract text content from MCP response
          const textParts: string[] = [];

          // Check for content array (standard MCP format)
          if (Array.isArray((result as any).content)) {
            logger.debug({ tool: toolInfo.name, itemCount: (result as any).content.length }, 'Processing MCP content array');
            for (const item of (result as any).content) {
              if (item.type === 'text' && item.text) {
                textParts.push(item.text);
              } else if (item.type === 'image' && item.data && item.mimeType) {
                logger.info({ tool: toolInfo.name, mimeType: item.mimeType }, 'Capturing image from MCP response');
                onImage?.({ data: item.data, mimeType: item.mimeType });
              }
            }
            if (textParts.length > 0) {
              return textParts.join('\n\n');
            }
          }

          // Check for nested execution.content
          if ((result as any).execution && Array.isArray((result as any).execution.content)) {
            for (const item of (result as any).execution.content) {
              if (item.type === 'text' && item.text) {
                textParts.push(item.text);
              } else if (item.type === 'image' && item.data && item.mimeType) {
                onImage?.({ data: item.data, mimeType: item.mimeType });
              }
            }
            if (textParts.length > 0) {
              return textParts.join('\n\n');
            }
          }

          // Fallback: stringify the result
          return JSON.stringify(result, null, 2);
        }

        return String(result || '');
      } catch (error) {
        logger.error({ error, tool: toolInfo.name, input }, 'MCP tool call failed');
        return `Error calling MCP tool ${toolInfo.name}: ${error instanceof Error ? error.message : String(error)}`;
      }
    },
  });
}

/**
 * Convert MCP input schema to Zod schema
 */
function mcpInputSchemaToZod(inputSchema?: MCPToolInfo['inputSchema']): z.ZodObject<any> {
  if (!inputSchema || !inputSchema.properties) {
    // Generic schema that accepts any properties
    return z.object({}).passthrough();
  }

  const properties = inputSchema.properties;
  const required = inputSchema.required || [];

  const zodFields: Record<string, z.ZodTypeAny> = {};

  for (const [key, prop] of Object.entries(properties)) {
    let zodType: z.ZodTypeAny;

    // Map JSON Schema types to Zod types
    switch (prop.type) {
      case 'string':
        zodType = z.string().describe(prop.description || '');
        break;
      case 'number':
        zodType = z.number().describe(prop.description || '');
        break;
      case 'integer':
        zodType = z.number().int().describe(prop.description || '');
        break;
      case 'boolean':
        zodType = z.boolean().describe(prop.description || '');
        break;
      case 'array':
        // Handle array items
        if (prop.items) {
          const itemType = getZodTypeForProperty(prop.items);
          zodType = z.array(itemType).describe(prop.description || '');
        } else {
          zodType = z.array(z.any()).describe(prop.description || '');
        }
        break;
      case 'object':
        zodType = z.object({}).passthrough().describe(prop.description || '');
        break;
      default:
        zodType = z.any().describe(prop.description || '');
    }

    // Make optional if not required
    if (!required.includes(key)) {
      zodType = zodType.optional();
    }

    zodFields[key] = zodType;
  }

  return z.object(zodFields);
}

/**
 * Helper to get Zod type for a property definition
 */
function getZodTypeForProperty(prop: any): z.ZodTypeAny {
  switch (prop.type) {
    case 'string':
      return z.string();
    case 'number':
      return z.number();
    case 'integer':
      return z.number().int();
    case 'boolean':
      return z.boolean();
    case 'object':
      return z.object({}).passthrough();
    default:
      return z.any();
  }
}

/**
 * Create multiple MCP tool wrappers from tool list
 */
export function createMCPToolWrappers(
  toolInfos: MCPToolInfo[],
  mcpClient: MCPClientConnector,
  logger: FastifyBaseLogger,
  onImage?: (image: { data: string; mimeType: string }) => void,
  onWorkspaceMutation?: (storeName: string, newState: unknown) => void
): DynamicStructuredTool[] {
  return toolInfos.map(toolInfo =>
    createMCPToolWrapper(toolInfo, mcpClient, logger, onImage, onWorkspaceMutation)
  );
}
