/**
 * Synor Provider
 *
 * Custom JSON-RPC provider for Synor blockchain.
 */

import { HardhatRuntimeEnvironment } from "hardhat/types";
import { TransactionRequest, TransactionResponse, TransactionReceipt, Log } from "./type-extensions";

/**
 * Synor-specific JSON-RPC provider
 */
export class SynorProvider {
  private hre: HardhatRuntimeEnvironment;
  private rpcUrl: string;

  constructor(hre: HardhatRuntimeEnvironment) {
    this.hre = hre;
    this.rpcUrl = this.getNetworkUrl();
  }

  /**
   * Gets the RPC URL for the current network
   */
  private getNetworkUrl(): string {
    const network = this.hre.network.config;
    if ("url" in network) {
      return network.url;
    }
    return "http://localhost:8545";
  }

  /**
   * Makes an RPC call
   */
  async rpc<T>(method: string, params: any[] = []): Promise<T> {
    const response = await fetch(this.rpcUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: Date.now(),
        method,
        params,
      }),
    });

    const data = await response.json();

    if (data.error) {
      throw new Error(`RPC Error: ${data.error.message}`);
    }

    return data.result;
  }

  /**
   * Gets account balance
   */
  async getBalance(address: string, blockTag: string = "latest"): Promise<bigint> {
    const result = await this.rpc<string>("eth_getBalance", [address, blockTag]);
    return BigInt(result);
  }

  /**
   * Gets current block number
   */
  async getBlockNumber(): Promise<number> {
    const result = await this.rpc<string>("eth_blockNumber");
    return parseInt(result, 16);
  }

  /**
   * Gets a block by number or hash
   */
  async getBlock(blockHashOrNumber: string | number, includeTransactions: boolean = false): Promise<any> {
    const method = typeof blockHashOrNumber === "number"
      ? "eth_getBlockByNumber"
      : "eth_getBlockByHash";

    const param = typeof blockHashOrNumber === "number"
      ? `0x${blockHashOrNumber.toString(16)}`
      : blockHashOrNumber;

    return this.rpc(method, [param, includeTransactions]);
  }

  /**
   * Gets transaction by hash
   */
  async getTransaction(hash: string): Promise<TransactionResponse | null> {
    const tx = await this.rpc<any>("eth_getTransactionByHash", [hash]);

    if (!tx) return null;

    return {
      hash: tx.hash,
      from: tx.from,
      to: tx.to,
      value: BigInt(tx.value || "0"),
      gasLimit: BigInt(tx.gas || "0"),
      gasPrice: BigInt(tx.gasPrice || "0"),
      nonce: parseInt(tx.nonce, 16),
      data: tx.input || "0x",
      blockNumber: tx.blockNumber ? parseInt(tx.blockNumber, 16) : undefined,
      blockHash: tx.blockHash,
      timestamp: undefined, // Would need to fetch block for timestamp
      confirmations: 0, // Would need current block to calculate
      wait: async (confirmations = 1) => this.waitForTransaction(hash, confirmations),
    };
  }

  /**
   * Gets transaction receipt
   */
  async getTransactionReceipt(hash: string): Promise<TransactionReceipt | null> {
    const receipt = await this.rpc<any>("eth_getTransactionReceipt", [hash]);

    if (!receipt) return null;

    return {
      hash: receipt.transactionHash,
      blockNumber: parseInt(receipt.blockNumber, 16),
      blockHash: receipt.blockHash,
      transactionIndex: parseInt(receipt.transactionIndex, 16),
      from: receipt.from,
      to: receipt.to,
      contractAddress: receipt.contractAddress,
      gasUsed: BigInt(receipt.gasUsed),
      status: parseInt(receipt.status, 16),
      logs: receipt.logs.map((log: any) => this.parseLog(log)),
      events: [], // Would need ABI to parse events
    };
  }

  /**
   * Waits for transaction confirmation
   */
  async waitForTransaction(hash: string, confirmations: number = 1): Promise<TransactionReceipt> {
    const timeout = this.hre.config.synor.timeout;
    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      const receipt = await this.getTransactionReceipt(hash);

      if (receipt) {
        const currentBlock = await this.getBlockNumber();
        const txConfirmations = currentBlock - receipt.blockNumber + 1;

        if (txConfirmations >= confirmations) {
          return receipt;
        }
      }

      // Wait before next check
      await new Promise(resolve => setTimeout(resolve, 1000));
    }

    throw new Error(`Transaction ${hash} not confirmed within ${timeout}ms`);
  }

  /**
   * Sends a transaction
   */
  async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
    const txData: any = {
      from: tx.from,
      to: tx.to,
      value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : "0x0",
      data: tx.data || "0x",
    };

    if (tx.gasLimit) {
      txData.gas = `0x${tx.gasLimit.toString(16)}`;
    }

    if (tx.gasPrice) {
      txData.gasPrice = `0x${tx.gasPrice.toString(16)}`;
    }

    if (tx.nonce !== undefined) {
      txData.nonce = `0x${tx.nonce.toString(16)}`;
    }

    const hash = await this.rpc<string>("eth_sendTransaction", [txData]);

    return {
      hash,
      from: tx.from || "",
      to: tx.to,
      value: BigInt(tx.value || 0),
      gasLimit: BigInt(tx.gasLimit || 0),
      gasPrice: tx.gasPrice || BigInt(0),
      nonce: tx.nonce || 0,
      data: tx.data || "0x",
      confirmations: 0,
      wait: async (confirmations = 1) => this.waitForTransaction(hash, confirmations),
    };
  }

  /**
   * Sends a raw transaction
   */
  async sendRawTransaction(signedTx: string): Promise<string> {
    return this.rpc<string>("eth_sendRawTransaction", [signedTx]);
  }

  /**
   * Calls a contract (read-only)
   */
  async call(tx: TransactionRequest, blockTag: string = "latest"): Promise<string> {
    const txData = {
      from: tx.from,
      to: tx.to,
      data: tx.data,
      value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : undefined,
    };

    return this.rpc<string>("eth_call", [txData, blockTag]);
  }

  /**
   * Estimates gas for a transaction
   */
  async estimateGas(tx: TransactionRequest): Promise<bigint> {
    const txData = {
      from: tx.from,
      to: tx.to,
      data: tx.data,
      value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : undefined,
    };

    const result = await this.rpc<string>("eth_estimateGas", [txData]);
    return BigInt(result);
  }

  /**
   * Gets current gas price
   */
  async getGasPrice(): Promise<bigint> {
    const result = await this.rpc<string>("eth_gasPrice");
    return BigInt(result);
  }

  /**
   * Gets account nonce
   */
  async getTransactionCount(address: string, blockTag: string = "pending"): Promise<number> {
    const result = await this.rpc<string>("eth_getTransactionCount", [address, blockTag]);
    return parseInt(result, 16);
  }

  /**
   * Gets contract code
   */
  async getCode(address: string, blockTag: string = "latest"): Promise<string> {
    return this.rpc<string>("eth_getCode", [address, blockTag]);
  }

  /**
   * Gets storage at position
   */
  async getStorageAt(address: string, position: string, blockTag: string = "latest"): Promise<string> {
    return this.rpc<string>("eth_getStorageAt", [address, position, blockTag]);
  }

  /**
   * Gets logs matching filter
   */
  async getLogs(filter: {
    fromBlock?: string | number;
    toBlock?: string | number;
    address?: string | string[];
    topics?: (string | string[] | null)[];
  }): Promise<Log[]> {
    const filterData: any = {};

    if (filter.fromBlock !== undefined) {
      filterData.fromBlock = typeof filter.fromBlock === "number"
        ? `0x${filter.fromBlock.toString(16)}`
        : filter.fromBlock;
    }

    if (filter.toBlock !== undefined) {
      filterData.toBlock = typeof filter.toBlock === "number"
        ? `0x${filter.toBlock.toString(16)}`
        : filter.toBlock;
    }

    if (filter.address) {
      filterData.address = filter.address;
    }

    if (filter.topics) {
      filterData.topics = filter.topics;
    }

    const logs = await this.rpc<any[]>("eth_getLogs", [filterData]);
    return logs.map(log => this.parseLog(log));
  }

  /**
   * Parses a raw log
   */
  private parseLog(log: any): Log {
    return {
      blockNumber: parseInt(log.blockNumber, 16),
      blockHash: log.blockHash,
      transactionIndex: parseInt(log.transactionIndex, 16),
      transactionHash: log.transactionHash,
      logIndex: parseInt(log.logIndex, 16),
      address: log.address,
      topics: log.topics,
      data: log.data,
    };
  }

  // Synor-specific methods

  /**
   * Gets quantum signature status
   */
  async getQuantumStatus(): Promise<{ enabled: boolean; algorithm: string }> {
    return this.rpc("synor_getQuantumStatus");
  }

  /**
   * Gets shard information
   */
  async getShardInfo(): Promise<{ shardId: number; totalShards: number }> {
    return this.rpc("synor_getShardInfo");
  }

  /**
   * Gets DAG block information
   */
  async getDagBlock(hash: string): Promise<any> {
    return this.rpc("synor_getDagBlock", [hash]);
  }

  /**
   * Gets pending cross-shard messages
   */
  async getPendingCrossShardMessages(address: string): Promise<any[]> {
    return this.rpc("synor_getPendingCrossShardMessages", [address]);
  }
}
