/**
 * RPC Handler - Handles JSON-RPC 2.0 requests and routes them appropriately
 */

import { BesuClient } from '../clients/besu-client';
import { TxInterceptor } from '../interceptors/tx-interceptor';
import { Web3SignerClient } from '../clients/web3signer-client';

export interface JsonRpcRequest {
  jsonrpc: string;
  method: string;
  params?: any[];
  id: string | number | null;
}

export interface JsonRpcResponse {
  jsonrpc: string;
  result?: any;
  error?: {
    code: number;
    message: string;
    data?: any;
  };
  id: string | number | null;
}

// Dangerous methods that should be denied by default
// These are admin/debug methods that expose sensitive information
const DENIED_METHODS = [
  'admin_',
  'debug_',
  'txpool_',
  'miner_',
];

// Private network consensus methods (CLIQUE, IBFT, QBFT)
// These are allowed to pass through - they are used for private network management
const PRIVATE_NETWORK_METHODS = [
  'clique_',
  'ibft_',
  'qbft_',
  'perm_',
];

// Methods that should be intercepted
const INTERCEPTED_METHODS = ['eth_sendTransaction'];

export class RpcHandler {
  private besuClient: BesuClient;
  private txInterceptor: TxInterceptor;
  private web3SignerClient?: Web3SignerClient;
  private chainId: number;
  private allowPrivateNetworkMethods: boolean;

  constructor(
    besuClient: BesuClient,
    txInterceptor: TxInterceptor,
    chainId: number,
    allowPrivateNetworkMethods: boolean = true,
    web3SignerClient?: Web3SignerClient
  ) {
    this.besuClient = besuClient;
    this.txInterceptor = txInterceptor;
    this.web3SignerClient = web3SignerClient;
    this.chainId = chainId;
    this.allowPrivateNetworkMethods = allowPrivateNetworkMethods;
  }

  private isMethodDenied(method: string): boolean {
    // Always deny admin/debug methods
    if (DENIED_METHODS.some(prefix => method.startsWith(prefix))) {
      return true;
    }

    // Conditionally deny private network methods based on configuration
    if (!this.allowPrivateNetworkMethods) {
      return PRIVATE_NETWORK_METHODS.some(prefix => method.startsWith(prefix));
    }

    return false;
  }

  private isPrivateNetworkMethod(method: string): boolean {
    return PRIVATE_NETWORK_METHODS.some(prefix => method.startsWith(prefix));
  }

  private isMethodIntercepted(method: string): boolean {
    return INTERCEPTED_METHODS.includes(method);
  }

  async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
    // Validate JSON-RPC version
    if (request.jsonrpc !== '2.0') {
      return {
        jsonrpc: '2.0',
        error: {
          code: -32600,
          message: 'Invalid Request',
          data: 'jsonrpc must be "2.0"',
        },
        id: request.id,
      };
    }

    // Validate method
    if (!request.method || typeof request.method !== 'string') {
      return {
        jsonrpc: '2.0',
        error: {
          code: -32600,
          message: 'Invalid Request',
          data: 'method is required and must be a string',
        },
        id: request.id,
      };
    }

    // Check if method is denied
    if (this.isMethodDenied(request.method)) {
      const isPrivateMethod = this.isPrivateNetworkMethod(request.method);
      const reason = isPrivateMethod
        ? 'Private network methods are disabled (set ALLOW_PRIVATE_NETWORK_METHODS=true)'
        : 'Method is denied for security reasons';
      
      return {
        jsonrpc: '2.0',
        error: {
          code: -32601,
          message: 'Method not found',
          data: `Method ${request.method} is not allowed: ${reason}`,
        },
        id: request.id,
      };
    }

    // Handle intercepted methods
    if (this.isMethodIntercepted(request.method)) {
      return this.handleInterceptedMethod(request);
    }

    // Pass through to Besu
    return this.handlePassThrough(request);
  }

  private async handleInterceptedMethod(request: JsonRpcRequest): Promise<JsonRpcResponse> {
    if (request.method === 'eth_sendTransaction') {
      if (!request.params || !Array.isArray(request.params) || request.params.length === 0) {
        return {
          jsonrpc: '2.0',
          error: {
            code: -32602,
            message: 'Invalid params',
            data: 'eth_sendTransaction requires a transaction object parameter',
          },
          id: request.id,
        };
      }

      const txObject = request.params[0];
      const fromAddress = txObject.from;

      // Smart interception: Check if address has key in Web3Signer
      // If no key, pass through to Besu (user wallet like MetaMask)
      // If key exists, intercept and sign via Web3Signer (service wallet)
      if (this.web3SignerClient && fromAddress) {
        try {
          const hasKey = await this.web3SignerClient.hasKey(fromAddress);
          
          if (!hasKey) {
            // User wallet (MetaMask, etc.) - no key in Web3Signer
            // Pass through to Besu - user will sign locally
            // Note: Besu doesn't support unsigned eth_sendTransaction,
            // but this allows the proper error to come from Besu
            console.log(`Address ${fromAddress} has no key in Web3Signer, passing through to Besu`);
            return this.handlePassThrough(request);
          }
          
          // Service wallet - has key in Web3Signer, intercept and sign
          console.log(`Address ${fromAddress} has key in Web3Signer, intercepting and signing`);
        } catch (error: any) {
          // If key check fails, log warning but still try to intercept
          // (fallback to original behavior)
          console.warn(`Failed to check if address has key: ${error.message}, attempting interception`);
        }
      } else if (!this.web3SignerClient) {
        // No Web3Signer client configured, pass through to Besu
        console.log('Web3Signer not configured, passing through to Besu');
        return this.handlePassThrough(request);
      }

      // Intercept and sign via Web3Signer (service wallet)
      try {
        const txHash = await this.txInterceptor.interceptAndSign(txObject);
        return {
          jsonrpc: '2.0',
          result: txHash,
          id: request.id,
        };
      } catch (error: any) {
        return {
          jsonrpc: '2.0',
          error: {
            code: -32000,
            message: 'Server error',
            data: error.message || 'Transaction processing failed',
          },
          id: request.id,
        };
      }
    }

    // Should not reach here
    return {
      jsonrpc: '2.0',
      error: {
        code: -32601,
        message: 'Method not found',
      },
      id: request.id,
    };
  }

  private async handlePassThrough(request: JsonRpcRequest): Promise<JsonRpcResponse> {
    try {
      const params = request.params || [];
      const result = await this.besuClient.callRpc(request.method, params);

      return {
        jsonrpc: '2.0',
        result,
        id: request.id,
      };
    } catch (error: any) {
      // Map common errors to JSON-RPC error codes
      let errorCode = -32000; // Server error
      let errorMessage = error.message || 'Internal error';

      if (error.message?.includes('Method not found')) {
        errorCode = -32601;
        errorMessage = 'Method not found';
      } else if (error.message?.includes('Invalid params')) {
        errorCode = -32602;
        errorMessage = 'Invalid params';
      }

      return {
        jsonrpc: '2.0',
        error: {
          code: errorCode,
          message: errorMessage,
          data: error.message,
        },
        id: request.id,
      };
    }
  }

  async handleBatch(requests: JsonRpcRequest[]): Promise<JsonRpcResponse[]> {
    const promises = requests.map(req => this.handleRequest(req));
    return Promise.all(promises);
  }
}
