import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ethers } from 'ethers';
import { BlockchainConfig } from '../../../config/blockchain.config';

@Injectable()
export class Web3Service implements OnModuleInit {
  private readonly logger = new Logger(Web3Service.name);
  private provider: ethers.JsonRpcProvider;
  private wallet: ethers.Wallet;
  private isInitialized = false;

  constructor(private configService: ConfigService) {}

  async onModuleInit(): Promise<void> {
    try {
      const blockchainConfig = this.configService.get<BlockchainConfig>('blockchain');

      if (!blockchainConfig.rpcUrl) {
        this.logger.warn('Blockchain RPC URL not configured. Service will be in limited mode.');
        return;
      }

      this.provider = new ethers.JsonRpcProvider(blockchainConfig.rpcUrl);

      if (blockchainConfig.platformWallet?.privateKey) {
        this.wallet = new ethers.Wallet(blockchainConfig.platformWallet.privateKey, this.provider);
        this.logger.debug(`Wallet initialized: ${this.wallet.address}`);
      } else {
        this.logger.warn('Blockchain private key not configured. Transaction sending disabled.');
      }

      await this.isConnected();
      this.isInitialized = true;
      this.logger.log('Web3Service initialized successfully');
    } catch (error) {
      this.logger.error('Failed to initialize Web3Service', error);
      throw error;
    }
  }

  async getProvider(): Promise<ethers.JsonRpcProvider> {
    if (!this.provider) {
      throw new Error('Blockchain provider not initialized');
    }
    return this.provider;
  }

  async getWallet(): Promise<ethers.Wallet> {
    if (!this.wallet) {
      throw new Error('Blockchain wallet not initialized');
    }
    return this.wallet;
  }

  getContract<T extends ethers.BaseContract>(address: string, abi: ethers.InterfaceAbi): T {
    if (!this.wallet) {
      throw new Error('Wallet not initialized');
    }

    try {
      return new ethers.Contract(address, abi, this.wallet) as unknown as T;
    } catch (error) {
      this.logger.error(`Failed to create contract instance at ${address}`, error);
      throw error;
    }
  }

  async sendTransaction(tx: ethers.TransactionRequest): Promise<ethers.TransactionReceipt> {
    if (!this.wallet) {
      throw new Error('Wallet not initialized');
    }

    const maxRetries = 3;
    let lastError: Error;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        this.logger.debug(`Sending transaction, attempt ${attempt + 1}/${maxRetries}`);

        // Estimate gas if not provided
        if (!tx.gasLimit) {
          try {
            const gasEstimate = await this.provider.estimateGas(tx);
            tx.gasLimit = (gasEstimate * BigInt(120)) / BigInt(100); // Add 20% buffer
            this.logger.debug(`Estimated gas: ${gasEstimate}, with buffer: ${tx.gasLimit}`);
          } catch (error) {
            this.logger.warn('Gas estimation failed, using default', error);
            tx.gasLimit = BigInt(6000000);
          }
        }

        // Set gas price if not provided
        if (!tx.gasPrice) {
          try {
            const gasPrice = await this.provider.getFeeData();
            tx.gasPrice = gasPrice.gasPrice;
            this.logger.debug(`Set gas price: ${tx.gasPrice}`);
          } catch (error) {
            this.logger.warn('Failed to get gas price', error);
          }
        }

        const response = await this.wallet.sendTransaction(tx);
        this.logger.log(`Transaction sent: ${response.hash}`);

        const receipt = await response.wait(1);
        if (!receipt) {
          throw new Error('Transaction failed to confirm');
        }

        this.logger.log(`Transaction confirmed: ${receipt.hash}, Block: ${receipt.blockNumber}`);
        return receipt;
      } catch (error) {
        lastError = error as Error;
        this.logger.warn(`Transaction attempt ${attempt + 1} failed: ${lastError.message}`);

        // Check if it's a nonce issue
        if (lastError.message.includes('nonce') || lastError.message.includes('replacement')) {
          if (attempt < maxRetries - 1) {
            await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
            continue;
          }
        }

        // For other errors, don't retry
        if (!lastError.message.includes('nonce')) {
          throw lastError;
        }
      }
    }

    throw new Error(
      `Transaction failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`,
    );
  }

  async getBlockNumber(): Promise<number> {
    if (!this.provider) {
      throw new Error('Provider not initialized');
    }
    return this.provider.getBlockNumber();
  }

  async getTransactionReceipt(txHash: string): Promise<ethers.TransactionReceipt | null> {
    if (!this.provider) {
      throw new Error('Provider not initialized');
    }

    try {
      return await this.provider.getTransactionReceipt(txHash);
    } catch (error) {
      this.logger.warn(`Failed to get transaction receipt for ${txHash}`, error);
      return null;
    }
  }

  async isConnected(): Promise<boolean> {
    try {
      if (!this.provider) {
        return false;
      }

      const network = await this.provider.getNetwork();
      this.logger.debug(`Connected to network: ${network.name} (chainId: ${network.chainId})`);
      return true;
    } catch (error) {
      this.logger.error('Blockchain connection check failed', error);
      return false;
    }
  }

  getIsInitialized(): boolean {
    return this.isInitialized;
  }
}
