import { WebPlugin } from '@capacitor/core';

// MCP SDK imports (ESM)
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';

import type {
  CorsBypassPlugin,
  HttpRequestOptions,
  HttpResponse,
  StreamRequestOptions,
  SSEOptions,
  SSEConnectionOptions,
  SSEConnection,
  WebSocketConnectionOptions,
  WebSocketConnection,
  MCPClientOptions,
  MCPClient,
  MCPResourceList,
  MCPResource,
  MCPToolList,
  MCPToolResult,
  MCPPromptList,
  MCPPrompt,
  MCPSamplingRequest,
  MCPSamplingResponse,
  Interceptor,
  InterceptorOptions,
  InterceptorHandle,
  GlobalProxyConfig,
  ProxyConfig,
  ProxyTestResult,
  ProxyStatus,
} from './definitions';
 

// Import modular managers
import { UtilsManager } from './web/utils';
import { HttpManager } from './web/http';
import { StreamManager } from './web/stream';
import { SSEManager } from './web/sse';
import { WebSocketManager } from './web/websocket';
import { InterceptorManager } from './web/interceptor';

export class CorsBypassWeb extends WebPlugin implements CorsBypassPlugin {
  private proxyServerUrl: string | null = null;
  private globalProxyConfig: GlobalProxyConfig | null = null;
  private proxyRequestCount: number = 0;
  private proxyLastSuccessTime: number | null = null;
  private proxyLastError: string | null = null;
  
  // Modular managers
  private utilsManager: UtilsManager;
  private httpManager: HttpManager;
  private streamManager: StreamManager;
  private sseManager: SSEManager;
  private wsManager: WebSocketManager;
  private interceptorManager: InterceptorManager;
  
  // MCP specific
  private mcpClients = new Map<string, any>();
  private mcpTransports = new Map<string, any>();
  private connectionCounter = 0;

  constructor() {
    super();
    
    // Initialize managers
    this.utilsManager = new UtilsManager();
    this.httpManager = new HttpManager(this.proxyServerUrl);
    this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this));
    this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this));
    this.wsManager = new WebSocketManager(this.notifyListeners.bind(this));
    this.interceptorManager = new InterceptorManager();
    
    // Try to detect if a proxy server is available
    this.detectProxyServer();
  }

  private async detectProxyServer() {
    const possibleUrls = [
      'http://localhost:3002',
      'http://127.0.0.1:3002',
      'http://localhost:3001',
      'http://127.0.0.1:3001',
      'http://localhost:8080',
      'http://127.0.0.1:8080'
    ];

    for (const url of possibleUrls) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 1000);

        const response = await fetch(`${url}/health`, {
          method: 'GET',
          signal: controller.signal
        });

        clearTimeout(timeoutId);

        if (response.ok) {
          this.proxyServerUrl = url;
          this.httpManager.setProxyServer(url);
          this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this));
          this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this));
          console.log(`🔧 CORS Proxy server detected at: ${url}`);
          break;
        }
      } catch (error) {
        // Ignore errors, continue checking
      }
    }

    if (!this.proxyServerUrl) {
      console.warn('⚠️ No CORS proxy server detected. Some requests may fail due to CORS.');
      console.log('💡 To enable full functionality, run: node web-proxy-server.js');
    }
  }

  /**
   * Set custom proxy server URL
   */
  setProxyServer(url: string) {
    this.proxyServerUrl = url;
    this.httpManager.setProxyServer(url);
    this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this));
    this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this));
    console.log(`🔧 Proxy server set to: ${url}`);
  }

  async request(options: HttpRequestOptions): Promise<HttpResponse> {
    const interceptors = this.interceptorManager.getInterceptorsInternal();
    return this.httpManager.request(options, interceptors);
  }

  async get(options: HttpRequestOptions): Promise<HttpResponse> {
    const interceptors = this.interceptorManager.getInterceptorsInternal();
    return this.httpManager.get(options, interceptors);
  }

  async post(options: HttpRequestOptions): Promise<HttpResponse> {
    const interceptors = this.interceptorManager.getInterceptorsInternal();
    return this.httpManager.post(options, interceptors);
  }

  async put(options: HttpRequestOptions): Promise<HttpResponse> {
    const interceptors = this.interceptorManager.getInterceptorsInternal();
    return this.httpManager.put(options, interceptors);
  }

  async patch(options: HttpRequestOptions): Promise<HttpResponse> {
    const interceptors = this.interceptorManager.getInterceptorsInternal();
    return this.httpManager.patch(options, interceptors);
  }

  async delete(options: HttpRequestOptions): Promise<HttpResponse> {
    const interceptors = this.interceptorManager.getInterceptorsInternal();
    return this.httpManager.delete(options, interceptors);
  }

  /**
   * Streaming HTTP request - supports AI model streaming output
   */
  async streamRequest(options: StreamRequestOptions): Promise<{ streamId: string }> {
    return this.streamManager.streamRequest(options);
  }

  /**
   * Cancel streaming request
   */
  async cancelStream(options: { streamId: string }): Promise<void> {
    return this.streamManager.cancelStream(options);
  }

  async startSSE(options: SSEOptions): Promise<{ connectionId: string }> {
    return this.sseManager.startSSE(options);
  }

  async stopSSE(options: { connectionId: string }): Promise<void> {
    return this.sseManager.stopSSE(options);
  }

  async createSSEConnection(options: SSEConnectionOptions): Promise<SSEConnection> {
    return this.sseManager.createSSEConnection(options);
  }

  async closeSSEConnection(options: { connectionId: string }): Promise<void> {
    return this.sseManager.closeSSEConnection(options);
  }

  async createWebSocketConnection(options: WebSocketConnectionOptions): Promise<WebSocketConnection> {
    return this.wsManager.createWebSocketConnection(options);
  }

  async closeWebSocketConnection(options: { connectionId: string }): Promise<void> {
    return this.wsManager.closeWebSocketConnection(options);
  }

  async sendWebSocketMessage(options: { connectionId: string; message: string }): Promise<void> {
    return this.wsManager.sendWebSocketMessage(options);
  }

  // ===== MCP Protocol Methods =====

  async createMCPClient(options: MCPClientOptions): Promise<MCPClient> {
    const connectionId = `mcp_${++this.connectionCounter}`;

    try {
      // Determine transport type and URL
      const transport = options.transport || 'streamablehttp';
      
      // Get URL (support both new and legacy config)
      let url = options.url;
      if (!url && options.sseUrl) {
        // Backward compatibility: use sseUrl if url is not provided
        url = options.sseUrl;
      }
      
      if (!url) {
        throw new Error('URL is required for MCP client (provide either "url" or "sseUrl")');
      }

      // Create transport layer
      let mcpTransport: any;

      if (transport === 'streamablehttp') {
        // Use new StreamableHTTP transport (recommended)
        throw new Error('StreamableHTTP transport should use mcpClientManager. Use @capacitor/cors-bypass-enhanced web managers directly.');
      } else if (transport === 'sse' || options.sseUrl) {
        // Legacy SSE transport
        if (this.proxyServerUrl && this.utilsManager.isCrossOrigin(url)) {
          // Use proxy server
          const proxyUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`;
          mcpTransport = new SSEClientTransport(new URL(proxyUrl));
        } else {
          // Direct connection
          mcpTransport = new SSEClientTransport(new URL(url));
        }
      } else {
        throw new Error(`Unsupported transport type: ${transport}`);
      }

      // Create MCP client
      const client = new Client(
        {
          name: options.clientInfo.name,
          version: options.clientInfo.version,
        },
        {
          capabilities: {
            roots: options.capabilities?.roots ? { listChanged: true } : undefined,
            sampling: options.capabilities?.sampling ? {} : undefined,
          }
        }
      );

      // Connect to server
      await client.connect(mcpTransport);

      // Store client and transport
      this.mcpClients.set(connectionId, client);
      this.mcpTransports.set(connectionId, mcpTransport);

      console.log(`✅ MCP client connected: ${connectionId}`);

      return {
        connectionId,
        status: 'connected',
        serverCapabilities: client.getServerCapabilities(),
        protocolVersion: '2025-03-26'
      };
    } catch (error) {
      console.error(`❌ MCP client connection failed:`, error);
      throw new Error(`Failed to create MCP client: ${error}`);
    }
  }

  async listMCPResources(options: { connectionId: string; cursor?: string }): Promise<MCPResourceList> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.listResources(options.cursor ? { cursor: options.cursor } : {});
      return {
        resources: result.resources || [],
        nextCursor: result.nextCursor
      };
    } catch (error) {
      throw new Error(`Failed to list MCP resources: ${error}`);
    }
  }

  async readMCPResource(options: { connectionId: string; uri: string }): Promise<MCPResource> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.readResource({ uri: options.uri });
      return {
        uri: options.uri,
        mimeType: result.contents?.[0]?.mimeType || 'text/plain',
        text: (result.contents?.[0] as any)?.text || '',
        blob: (result.contents?.[0] as any)?.data
      };
    } catch (error) {
      throw new Error(`Failed to read MCP resource: ${error}`);
    }
  }

  async listMCPTools(options: { connectionId: string; cursor?: string }): Promise<MCPToolList> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.listTools(options.cursor ? { cursor: options.cursor } : {});
      return {
        tools: result.tools || [],
        nextCursor: result.nextCursor
      };
    } catch (error) {
      throw new Error(`Failed to list MCP tools: ${error}`);
    }
  }

  async callMCPTool(options: { connectionId: string; name: string; arguments?: any }): Promise<MCPToolResult> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.callTool({
        name: options.name,
        arguments: options.arguments || {}
      });

      return {
        content: (result.content as any) || [],
        isError: (result as any).isError || false
      };
    } catch (error) {
      throw new Error(`Failed to call MCP tool: ${error}`);
    }
  }

  async listMCPPrompts(options: { connectionId: string; cursor?: string }): Promise<MCPPromptList> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.listPrompts(options.cursor ? { cursor: options.cursor } : {});
      return {
        prompts: result.prompts || [],
        nextCursor: result.nextCursor
      };
    } catch (error) {
      throw new Error(`Failed to list MCP prompts: ${error}`);
    }
  }

  async getMCPPrompt(options: { connectionId: string; name: string; arguments?: any }): Promise<MCPPrompt> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.getPrompt({
        name: options.name,
        arguments: options.arguments || {}
      });

      return {
        description: result.description,
        messages: result.messages || []
      };
    } catch (error) {
      throw new Error(`Failed to get MCP prompt: ${error}`);
    }
  }

  async sendMCPSampling(options: { connectionId: string; request: MCPSamplingRequest }): Promise<MCPSamplingResponse> {
    const client = this.mcpClients.get(options.connectionId);
    if (!client) {
      throw new Error('MCP client not found');
    }

    try {
      const result = await client.request({
        method: options.request.method,
        params: options.request.params
      });

      return result;
    } catch (error) {
      throw new Error(`Failed to send MCP sampling request: ${error}`);
    }
  }

  // ==================== Interceptor Management ====================

  async addInterceptor(interceptor: Interceptor, options?: InterceptorOptions): Promise<InterceptorHandle> {
    return this.interceptorManager.addInterceptor(interceptor, options);
  }

  async removeInterceptor(handle: InterceptorHandle | string): Promise<void> {
    return this.interceptorManager.removeInterceptor(handle);
  }

  async removeAllInterceptors(): Promise<void> {
    return this.interceptorManager.removeAllInterceptors();
  }

  async getInterceptors(): Promise<InterceptorHandle[]> {
    return this.interceptorManager.getInterceptors();
  }

  // ==================== Proxy Management ====================

  /**
   * Set global proxy configuration
   * Note: On Web platform, proxy is handled through the CORS proxy server
   * The proxy config is stored and can be passed to the server for server-side proxying
   */
  async setGlobalProxy(config: GlobalProxyConfig): Promise<void> {
    this.globalProxyConfig = config;
    
    // If using a proxy server, we can configure it to use the specified proxy
    if (this.proxyServerUrl && config.enabled) {
      console.log(`🔧 [Web] Global proxy configured: ${config.type || 'http'}://${config.host}:${config.port}`);
      console.log('💡 Note: Web platform proxying requires server-side support.');
    }
  }

  /**
   * Get current global proxy configuration
   */
  async getGlobalProxy(): Promise<GlobalProxyConfig | null> {
    return this.globalProxyConfig;
  }

  /**
   * Clear global proxy configuration
   */
  async clearGlobalProxy(): Promise<void> {
    this.globalProxyConfig = null;
    this.proxyLastError = null;
    console.log('🔧 [Web] Global proxy configuration cleared');
  }

  /**
   * Test proxy connection
   * On Web platform, this tests connectivity through the CORS proxy server
   */
  async testProxy(config: ProxyConfig, testUrl?: string): Promise<ProxyTestResult> {
    const startTime = Date.now();
    const url = testUrl || 'https://www.google.com';
    
    if (!config.enabled || !config.host) {
      return {
        success: false,
        error: 'Proxy configuration is invalid or disabled',
        responseTime: 0
      };
    }

    try {
      // On Web, we can only test through our proxy server
      if (this.proxyServerUrl) {
        const response = await fetch(`${this.proxyServerUrl}/proxy`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            url,
            method: 'HEAD',
            proxy: {
              enabled: true,
              type: config.type || 'http',
              host: config.host,
              port: config.port,
              username: config.username,
              password: config.password
            }
          })
        });

        const responseTime = Date.now() - startTime;
        this.proxyRequestCount++;

        if (response.ok) {
          this.proxyLastSuccessTime = Date.now();
          this.proxyLastError = null;
          return {
            success: true,
            responseTime,
            statusCode: response.status
          };
        } else {
          const error = `HTTP ${response.status}`;
          this.proxyLastError = error;
          return {
            success: false,
            responseTime,
            statusCode: response.status,
            error
          };
        }
      } else {
        // No proxy server available, test direct connection
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 10000);

        try {
          const response = await fetch(url, {
            method: 'HEAD',
            mode: 'no-cors',
            signal: controller.signal
          });

          clearTimeout(timeoutId);
          const responseTime = Date.now() - startTime;

          return {
            success: true,
            responseTime,
            statusCode: response.status || 0
          };
        } catch (fetchError: any) {
          clearTimeout(timeoutId);
          return {
            success: false,
            responseTime: Date.now() - startTime,
            error: fetchError.message || 'Connection failed'
          };
        }
      }
    } catch (error: any) {
      this.proxyLastError = error.message;
      return {
        success: false,
        responseTime: Date.now() - startTime,
        error: error.message || 'Proxy test failed'
      };
    }
  }

  /**
   * Get current proxy status
   */
  async getProxyStatus(): Promise<ProxyStatus> {
    return {
      active: this.globalProxyConfig?.enabled ?? false,
      config: this.globalProxyConfig ?? undefined,
      requestCount: this.proxyRequestCount,
      lastError: this.proxyLastError ?? undefined,
      lastSuccessTime: this.proxyLastSuccessTime ?? undefined
    };
  }
}
