import { type JsonRpcProvider, parseUnits, type Signer, toBigInt } from "ethers";
import { getAxieContract, getAxieDelegationContract } from "./contracts";

// Delegation context hash
const ctxHash = "0xfa4914198408a3840a0a751c221614143f885428607953c71fa37ad0dd52f940";

async function approveAxieDelegation(signer: Signer) {
  const axieContract = getAxieContract(signer);
  const delegationContract = getAxieDelegationContract(signer);
  const delegationAddress = await delegationContract.getAddress();

  // Check if already approved
  const isApproved = await axieContract.isApprovedForAll(
    await signer.getAddress(),
    delegationAddress
  );

  if (!isApproved) {
    console.log("Approving Axie delegation contract...");
    const tx = await axieContract.setApprovalForAll(delegationAddress, true);
    await tx.wait();
    console.log("✅ Delegation contract approved");
  }
}

export async function getAxieIdsFromAccount(
  address: string,
  provider: JsonRpcProvider,
) {
  // get axie contract
  const axieContract = getAxieContract(provider);

  // get axies balance for the address
  const axiesBalance = await axieContract.balanceOf(address);

  // get axie ids
  const axieIds: number[] = [];
  for (let i = 0; i < axiesBalance; i++) {
    try {
      const axieId = await axieContract.tokenOfOwnerByIndex(address, i);
      axieIds.push(Number(axieId));
    } catch (error: unknown) {
      const errorMessage =
        error instanceof Error ? error.message : "Unknown error";
      console.error(`Error fetching axie id at index ${i}: ${errorMessage}`);
    }
  }

  return axieIds;
}

export async function delegateAxie(
  signer: Signer,
  tokenId: string | number,
  delegatee: string,
) {
  const axieContract = getAxieContract(signer);
  const delegationContract = getAxieDelegationContract(signer);

  // Format token ID
  const formattedTokenId = typeof tokenId === "string" ? tokenId : tokenId.toString();

  try {
    // Check if context is already attached
    const context = await axieContract.contextOf(formattedTokenId);
    if (context === ctxHash) {
      console.log(`ℹ️ Context already attached for Axie ${formattedTokenId}`);
    } else {
      // If context exists but is different, we need to attach the correct one
      console.log(`Attaching context for Axie ${formattedTokenId}`);
      const tx1 = await axieContract.attachContext(
        ctxHash,
        formattedTokenId,
        "0x",
        {
          gasPrice: parseUnits("20", "gwei"),
        }
      );
      await tx1.wait();
      console.log(`✅ Context attached for Axie ${formattedTokenId}`);
    }
  } catch (error) {
    // If error contains "execution reverted", context is already attached
    if (error instanceof Error && error.message.includes('execution reverted')) {
      console.log(`ℹ️ Context already attached for Axie ${formattedTokenId}`);
    } else {
      // If any other error, try to attach context
      try {
        console.log(`Attaching context for Axie ${formattedTokenId}`);
        const tx1 = await axieContract.attachContext(
          ctxHash,
          formattedTokenId,
          "0x",
          {
            gasPrice: parseUnits("20", "gwei"),
          }
        );
        await tx1.wait();
        console.log(`✅ Context attached for Axie ${formattedTokenId}`);
      } catch (attachError) {
        if (attachError instanceof Error && attachError.message.includes('execution reverted')) {
          console.log(`ℹ️ Context already attached for Axie ${formattedTokenId}`);
        } else {
          throw attachError;
        }
      }
    }
  }

  // Calculate expiry timestamp (1 year from now)
  const oneYearFromNow = Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60);

  // Format the delegatee address
  const cleanDelegatee = delegatee.replace("ronin:", "0x").toLowerCase();

  console.log(`Delegating Axie ${formattedTokenId} to ${cleanDelegatee}`);

  try {
    // Call delegate function with standard method call
    const tx2 = await delegationContract.delegate(
      formattedTokenId,
      cleanDelegatee,
      oneYearFromNow,
      1, // permissionBitMap as uint64
      {
        gasPrice: parseUnits("20", "gwei"),
      }
    );

    const receipt = await tx2.wait();
    return receipt;
  } catch (error) {
    console.error("Delegation error:", error);
    throw error;
  }
}

export async function batchDelegateAxies(
  signer: Signer,
  axieIds: Array<string | number>,
  delegatee: string,
) {
  const axieContract = getAxieContract(signer);
  const delegationContract = getAxieDelegationContract(signer);

  // Format token IDs and delegatee address
  const tokenIds = axieIds.map(id => {
    try {
      return BigInt(id.toString().replace(/[^0-9]/g, ''));
    } catch (e) {
      throw new Error(`Invalid Axie ID format: ${id}`);
    }
  });

  // Format the delegatee address
  const formattedDelegatee = delegatee.replace("ronin:", "0x").toLowerCase();

  // Check and attach contexts individually
  console.log(`Checking/attaching context for ${tokenIds.length} Axies`);
  for (const tokenId of tokenIds) {
    try {
      // Check if context is already attached
      const context = await axieContract.contextOf(tokenId.toString());
      if (context === ctxHash) {
        console.log(`ℹ️ Context already attached for Axie ${tokenId}`);
      } else {
        console.log(`- Attaching context for Axie ${tokenId}`);
        const tx1 = await axieContract.attachContext(ctxHash, tokenId.toString(), "0x");
        await tx1.wait();
        console.log(`✅ Context attached for Axie ${tokenId}`);
      }
    } catch (error) {
      // If error contains "execution reverted", context is already attached
      if (error instanceof Error && error.message.includes('execution reverted')) {
        console.log(`ℹ️ Context already attached for Axie ${tokenId}`);
      } else {
        try {
          console.log(`- Attaching context for Axie ${tokenId}`);
          const tx1 = await axieContract.attachContext(ctxHash, tokenId.toString(), "0x");
          await tx1.wait();
          console.log(`✅ Context attached for Axie ${tokenId}`);
        } catch (attachError) {
          if (attachError instanceof Error && attachError.message.includes('execution reverted')) {
            console.log(`ℹ️ Context already attached for Axie ${tokenId}`);
          } else {
            console.log(`❌ Failed to attach context for Axie ${tokenId}`, attachError);
            throw attachError;
          }
        }
      }
    }
  }

  // Calculate expiry timestamp (1 year from now)
  const oneYearFromNow = BigInt(Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60));
  const permissionBitMap = 1n;

  // Now do the bulk delegation
  console.log(`\nDelegating ${tokenIds.length} Axies to ${delegatee}`);
  try {
    const tx2 = await delegationContract.bulkDelegate(
      tokenIds,
      Array(tokenIds.length).fill(formattedDelegatee),
      Array(tokenIds.length).fill(oneYearFromNow),
      Array(tokenIds.length).fill(permissionBitMap)
    );

    const receipt = await tx2.wait();
    return receipt;
  } catch (error: unknown) {
    // If bulk delegation fails, try individual delegations
    console.log("\n⚠️ Bulk delegation failed:", error);
    console.log("Falling back to individual delegations...\n");

    for (const tokenId of tokenIds) {
      try {
        const tx = await delegationContract.delegate(
          tokenId,
          formattedDelegatee,
          oneYearFromNow,
          permissionBitMap
        );
        await tx.wait();
        console.log(`✅ Successfully delegated Axie ${tokenId}`);
      } catch (err: unknown) {
        console.log(`❌ Failed to delegate Axie ${tokenId}`, err);
      }
    }
  }
}

export async function batchRevokeDelegations(
  signer: Signer,
  axieIds: Array<string | number>,
) {
  const delegationContract = getAxieDelegationContract(signer);

  // Format token IDs - ensure they are clean numeric strings
  const tokenIds = axieIds.map(id => id.toString().replace(/[^0-9]/g, ''));

  // Then do the bulk revocation
  console.log(`\nRevoking delegations for ${tokenIds.length} Axies`);
  try {
    const tx2 = await delegationContract.bulkRevokeDelegations(
      tokenIds
    );

    const receipt = await tx2.wait();
    return receipt;
  } catch (error: unknown) {
    console.log("\n⚠️ Bulk revocation failed:", error);
    console.log("Falling back to individual revocations...\n");

    for (const tokenId of tokenIds) {
      try {
        const tx = await delegationContract.revokeDelegation(
          tokenId
        );
        await tx.wait();
        console.log(`✅ Successfully revoked delegation for Axie ${tokenId}`);
      } catch (err: unknown) {
        console.log(`❌ Failed to revoke delegation for Axie ${tokenId}`, err);
      }
    }
  }
}

export async function revokeDelegation(
  signer: Signer,
  tokenId: string | number,
) {
  // // First approve the delegation contract if needed
  // await approveAxieDelegation(signer);

  // const axieContract = getAxieContract(signer);
  const delegationContract = getAxieDelegationContract(signer);

  // // First request detachment
  // console.log(`Requesting detachment for Axie ${Number(tokenId)}...`);
  // const tx1 = await axieContract.requestDetachContext(
  //   ctxHash,
  //   Number(tokenId),
  //   "0x",
  //   {
  //     gasPrice: parseUnits("20", "gwei"),
  //   }
  // );
  // await tx1.wait();

  // Then revoke the delegation
  console.log(`Revoking delegation for Axie ${Number(tokenId)}`);
  const tx2 = await delegationContract.revokeDelegation(
    Number(tokenId),
    {
      gasPrice: parseUnits("20", "gwei"),
    }
  );

  const receipt = await tx2.wait();
  return receipt;
}
