import { Injectable, Logger, Inject } from '@nestjs/common';
import { Web3Service } from './web3.service';
import {
  TransactionResult,
  NetworkStatus,
  NodeHealth,
} from '../interfaces/network-status.interface';
import { TransactionStatus as BlockchainTransactionStatus } from '../../../common/enums';
import { BlockchainTransaction } from '../../../database/models/blockchain-transaction.model';

@Injectable()
export class BlockchainMonitorService {
  private readonly logger = new Logger(BlockchainMonitorService.name);
  private readonly CONFIRMATION_BLOCKS = 1;
  private readonly MAX_POLL_ATTEMPTS = 60;
  private readonly POLL_INTERVAL = 2000; // 2 seconds

  constructor(
    private web3Service: Web3Service,
    @Inject(BlockchainTransaction)
    private blockchainTxRepository: typeof BlockchainTransaction,
  ) {}

  async monitorTransaction(txHash: string): Promise<TransactionResult> {
    this.logger.debug(`Monitoring transaction: ${txHash}`);

    let attempts = 0;
    let lastError: Error | null = null;

    while (attempts < this.MAX_POLL_ATTEMPTS) {
      try {
        const receipt = await this.web3Service.getTransactionReceipt(txHash);

        if (!receipt) {
          attempts++;
          this.logger.debug(
            `Transaction ${txHash} not yet confirmed (attempt ${attempts}/${this.MAX_POLL_ATTEMPTS})`,
          );
          await this.sleep(this.POLL_INTERVAL);
          continue;
        }

        // Transaction confirmed
        const currentBlockNumber = await this.web3Service.getBlockNumber();
        const confirmations = currentBlockNumber - receipt.blockNumber;

        if (confirmations >= this.CONFIRMATION_BLOCKS) {
          const result: TransactionResult = {
            txHash: receipt.hash!,
            status: receipt.status === 1 ? 'confirmed' : 'failed',
            blockNumber: receipt.blockNumber,
            gasUsed: receipt.gasUsed,
            confirmationTime: (attempts + 1) * (this.POLL_INTERVAL / 1000),
          };

          if (receipt.status !== 1) {
            result.errorMessage = 'Transaction reverted';
          }

          this.logger.log(`Transaction confirmed: ${txHash}, status=${result.status}`);

          // Update database
          await this.updateTransactionStatus(
            txHash,
            result.status === 'confirmed'
              ? BlockchainTransactionStatus.CONFIRMED
              : BlockchainTransactionStatus.FAILED,
            receipt.blockNumber,
            receipt.gasUsed,
            result.errorMessage,
          );

          return result;
        }

        attempts++;
        await this.sleep(this.POLL_INTERVAL);
      } catch (error) {
        lastError = error as Error;
        this.logger.warn(`Error polling transaction ${txHash}: ${lastError.message}`);
        attempts++;
        await this.sleep(this.POLL_INTERVAL);
      }
    }

    // Transaction confirmation timeout
    const timeoutError = new Error(
      `Transaction ${txHash} did not confirm within ${(this.MAX_POLL_ATTEMPTS * this.POLL_INTERVAL) / 1000}s`,
    );
    this.logger.error(timeoutError.message);

    await this.updateTransactionStatus(
      txHash,
      BlockchainTransactionStatus.FAILED,
      null,
      null,
      timeoutError.message,
    );

    throw timeoutError;
  }

  async getNetworkStatus(): Promise<NetworkStatus> {
    try {
      const isConnected = await this.web3Service.isConnected();

      if (!isConnected) {
        return {
          isConnected: false,
          chainId: 0,
          networkName: '',
          currentBlockNumber: 0,
          gasPrice: '0',
          nodeHealth: [],
          lastUpdated: new Date(),
        };
      }

      const provider = await this.web3Service.getProvider();
      const network = await provider.getNetwork();
      const blockNumber = await this.web3Service.getBlockNumber();
      const feeData = await provider.getFeeData();

      const nodeHealth = await this.getNodeHealth();

      return {
        isConnected: true,
        chainId: Number(network.chainId),
        networkName: network.name || 'unknown',
        currentBlockNumber: blockNumber,
        gasPrice: feeData.gasPrice?.toString() || '0',
        nodeHealth,
        lastUpdated: new Date(),
      };
    } catch (error) {
      this.logger.error('Failed to get network status', error);
      throw error;
    }
  }

  async getNodeHealth(): Promise<NodeHealth[]> {
    try {
      const provider = await this.web3Service.getProvider();
      const startTime = Date.now();

      const blockNumber = await provider.getBlockNumber();
      const latency = Date.now() - startTime;

      return [
        {
          rpcUrl: (provider as any).connection?.url || 'unknown',
          isHealthy: true,
          latency,
          lastCheck: new Date(),
        },
      ];
    } catch (error) {
      this.logger.error('Failed to check node health', error);
      return [
        {
          rpcUrl: 'unknown',
          isHealthy: false,
          latency: 0,
          lastCheck: new Date(),
          errorMessage: (error as Error).message,
        },
      ];
    }
  }

  private async updateTransactionStatus(
    txHash: string,
    status: BlockchainTransactionStatus,
    blockNumber: number | null,
    gasUsed: bigint | null,
    errorMessage?: string,
  ): Promise<void> {
    try {
      await this.blockchainTxRepository
        .query()
        .where({ txHash })
        .patch({
          status,
          blockNumber: blockNumber?.toString(),
          gasUsed: gasUsed?.toString(),
          confirmedAt: status === BlockchainTransactionStatus.CONFIRMED ? new Date() : undefined,
          errorMessage: errorMessage || undefined,
        });
    } catch (error) {
      this.logger.error(`Failed to update transaction status in database: ${txHash}`, error);
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}
