import { Web3Networks } from "./Web3Networks";



export class Web3Result {
  universalProvider: any;
  evmProvider: any;
  // chainType: Web3Networks;
  account: any;
  _signer: any;
  paymentProvider: any;

  public get signer(): any {
    if (this._signer == null) {
      if (this.evmProvider != null && this.evmProvider.isCon) {
        this._signer = this.evmProvider.getSigner();
      }
    }
    return this._signer;
  }
  public set signer(signer: any) {
    this._signer = signer;
  }

  public get success(): boolean {
    if (this.account == null || this.account == "") {
      return false;
    }
    if (this.evmProvider == null) {
      return false;
    }

    return true;
  }

  public static async formatEther(balance: any): Promise<number> {
    // @ts-ignore
    return Number(Number(window.ethers.utils.formatEther(balance)).toFixed(8));
  }

  public async getBalance(): Promise<number> {
    const wei = await this.evmProvider.getBalance(this.account);
    return await Web3Result.formatEther(wei);
  }
}


export enum ContractResult {
  WalletConnectError = "wallet connect error",
  SUCCESS = "all success",
  NOT_OWNER = "not owner",
  NOT_APPROVED = "not approved",
  GAS_ERROR = "gas error",
  EXECUTION_ERROR = "execution error",
}

export interface ContractError extends Error {
  code?: string;
  data?: any;
}

export class Contract {
  
  private static readonly ERROR_NOT_OWNER = "not owner";
  private static readonly ERROR_NOT_APPROVED = "not approved";
  private static readonly ERROR_GAS = "gas error";
  private static readonly ERROR_EXECUTION = "execution error";

  private readonly web3Result: Web3Result;
  private readonly abi: any;
  private readonly contractAddress: string;
  private contract: any;

  private constructor(
    web3Result: Web3Result,
    abi: any,
    contractAddress: string
  ) {
    this.web3Result = web3Result;
    this.abi = abi;
    this.contractAddress = contractAddress;
  }

  public static async build(
    web3Result: Web3Result,
    abi: any,
    contractAddress: string
  ): Promise<Contract> {
    const contract = new Contract(web3Result, abi, contractAddress);
    await contract.initialize();
    return contract;
  }

  private async initialize(): Promise<void> {
    console.log("Contract initializing", this.contractAddress);
    // @ts-ignore
    this.contract = await new window.ethers.Contract(
      this.contractAddress,
      this.abi,
      this.web3Result.signer
    );
  }

  public async getContractBalance(){
    const balance = await this.contract.balanceOf(this.web3Result.account);
    console.log("balance", balance);
    let balanceNumber = await Web3Result.formatEther(balance);
    console.log("balanceNumber", balanceNumber);
    return balanceNumber;
  }
  
  private async isOwnerOf(tokenId: number): Promise<boolean> {
    console.log(
      "Checking ownership for token",
      tokenId,
      this.web3Result.account
    );
    const owner = await this.contract.ownerOf(tokenId);
    console.log("Ownership check result", owner, this.web3Result.account);
    return owner.toLowerCase() === this.web3Result.account.toLowerCase();
  }

  public async getAllTokensOfOwner(account: string): Promise<number[]> {
    try {
      // Get all Transfer events
      const filter = this.contract.filters.Transfer(null, account);
      const events = await this.contract.queryFilter(filter);
      
      // Also get transfers out to handle cases where tokens were transferred away
      const filterOut = this.contract.filters.Transfer(account, null);
      const eventsOut = await this.contract.queryFilter(filterOut);
      
      // Create a map to track current ownership
      const tokenMap = new Map<number, boolean>();
      
      // Process incoming transfers
      for (const event of events) {
        if (event.args) {
          const tokenId = event.args.tokenId.toNumber();
          tokenMap.set(tokenId, true);
        }
      }
      
      // Process outgoing transfers
      for (const event of eventsOut) {
        if (event.args) {
          const tokenId = event.args.tokenId.toNumber();
          tokenMap.delete(tokenId);
        }
      }
      
      // Convert map keys to array
      const tokenIds = Array.from(tokenMap.keys());
      
      console.log("Found tokens:", tokenIds);
      return tokenIds;
    } catch (error) {
      console.error("Get all tokens of owner failed:", error);
      return [];
    }
  }

  private async isApproved(
    tokenId: number,
    toAddress: string
  ): Promise<boolean> {
    console.log("Checking approval for token", tokenId, toAddress);
    const approvedAddress = await this.contract.getApproved(tokenId);
    console.log("Approval check result", approvedAddress, toAddress);
    return approvedAddress.toLowerCase() === toAddress.toLowerCase();
  }

  private async approve(tokenId: number, toAddress: string): Promise<boolean> {
    console.log("Approving token", tokenId, toAddress);
    try {
      const tx = await this.contract.approve(toAddress, tokenId);
      console.log("Approval transaction", tx);
      return true;
    } catch (error) {
      console.error("Approval failed:", error);
      return false;
    }
  }

  private async wallApproval(
    userAddress: string,
    receiveAddress: string,
    nftNo: number
  ) {
    console.log("Approval transaction", userAddress, receiveAddress, nftNo);
    const tx = this.contract.Approval(userAddress, receiveAddress, nftNo);
    console.log("Approval transaction", tx);
    return tx;
  }

  public async transferTo(
    userAddress: string,
    receiveAddress: string,
    nftNo: number
  ): Promise<ContractResult> {
    try {
      const fromAddress = userAddress.toLowerCase();
      const toAddress = receiveAddress.toLowerCase();

      // Then execute the transfer
      console.log("Executing transfer...");
      const transferTx = await this.contract.transferFrom(
        fromAddress,
        toAddress,
        nftNo
      );

      console.log("Transfer transaction hash:", transferTx.hash);
      await transferTx.wait();
      console.log("Transfer confirmed");

      return ContractResult.SUCCESS;
    } catch (error: any) {
      console.error("Transfer failed:", error);
      if (error.code === "INSUFFICIENT_FUNDS") {
        return ContractResult.GAS_ERROR;
      }
      return ContractResult.EXECUTION_ERROR;
    }
  }

  public async ownerAndApprove(
    tokenId: number,
    toAddress: string
  ): Promise<ContractResult> {
    console.log("Starting owner and approve check for token", tokenId);

    const isOwner = await this.isOwnerOf(tokenId);
    if (!isOwner) {
      return ContractResult.NOT_OWNER;
    }
    console.log("Ownership verified");

    const isApproved = await this.isApproved(tokenId, toAddress);
    if (!isApproved) {
      const approvalSuccess = await this.approve(tokenId, toAddress);
      if (!approvalSuccess) {
        return ContractResult.NOT_APPROVED;
      }
    }
    console.log("Approval verified");

    return ContractResult.SUCCESS;
  }

  public async burnSBTAndSoulNFT(
    sbtAddress: string,
    userId: number,
    sbtId: number,
    soulIds: number[]
  ): Promise<boolean> {
    try {
      const tx = await this.contract.fusionBurn(
        userId,
        sbtAddress,
        sbtId,
        soulIds
      );
      console.log("Burn SBT and Soul NFT transaction", tx);
      return true;
    } catch (error) {
      console.error("Burn SBT and Soul NFT failed:", error);
      return false;
    }
  }

  public async burnSBT(
    sbtAddress: string,
    userId: number,
    sbtId: number
  ): Promise<boolean> {
    try {
      const tx = await this.contract.burnNFT(sbtAddress, sbtId, userId);
      console.log("Burn SBT transaction", tx);
      return true;
    } catch (error) {
      console.error("Burn SBT failed:", error);
      return false;
    }
  }

  public async getBalanceOf(owner: string): Promise<number> {
    try {
      const balance = await this.contract.balanceOf(owner);
      return balance.toNumber();
    } catch (error) {
      console.error("Get balance failed:", error);
      return 0;
    }
  }

  public async getTokenOfOwnerByIndex(owner: string, index: number): Promise<number> {
    try {
      const tokenId = await this.contract.tokenOfOwnerByIndex(owner, index);
      return tokenId.toNumber();
    } catch (error) {
      console.error("Get token of owner by index failed:", error);
      return 0;
    }
  }

  public async getTokenURI(tokenId: number): Promise<string> {
    try {
      return await this.contract.tokenURI(tokenId);
    } catch (error) {
      console.error("Get token URI failed:", error);
      return "";
    }
  }
}
