import { type Address, getContract, isAddress as isEvmAddress } from 'viem';
import { ChainID, type SupportedEvmChain } from '../../chains.js';
import { isNativeEvmToken, MAX_UINT_128 } from '../../constants.js';
import { InsufficientBalanceError, ValidationError } from '../../errors/index.js';
import { BaseValidator } from '../../utils/base-validator.js';
import type { CreateCrossChainOrderParams } from '../orders/cross-chain.js';
import { ERC20ABI } from './abi/erc20.js';
import { ChainProvider } from './chain-provider.js';
import type { CreateSingleChainOrderParams } from '../orders/single-chain.js';

/**
 * Chain-specific validator implementations
 */
export class EvmValidator extends BaseValidator {
  isValidAddress(address: string): boolean {
    return isEvmAddress(address);
  }

  isValidTokenAddress(tokenAddress: string): boolean {
    return isEvmAddress(tokenAddress);
  }

  isValidAmount(amount: bigint): boolean {
    return amount > 0n && amount < MAX_UINT_128;
  }

  protected getChainName(): string {
    return 'EVM';
  }

  public async validateCrossChainOrderFeasability(
    order: CreateCrossChainOrderParams & { user: string },
  ): Promise<void> {
    const chain = order.sourceChainId as SupportedEvmChain;
    const client = ChainProvider.getClient(chain);
    const user = order.user as Address;

    if (isNativeEvmToken(order.sourceTokenAddress)) {
      throw new ValidationError('Native token is not supported.');
    }

    const ERC20Contract = getContract({
      address: order.sourceTokenAddress as Address,
      abi: ERC20ABI,
      client: {
        public: client,
        chain,
      },
    });

    const balance = await ERC20Contract.read.balanceOf([user]);

    if (balance < order.sourceTokenAmount) {
      throw new InsufficientBalanceError(
        `Insufficient balance for token ${order.sourceTokenAddress}. Current balance: ${balance}. Required balance: ${order.sourceTokenAmount}`,
      );
    }

    // Temporary allowance turn off. This should be handled in the frontend.
    // const permit2Address = PERMIT2_ADDRESS[chain];
    // const allowance = await ERC20Contract.read.allowance([user, permit2Address]);

    // if (allowance < order.sourceTokenAmount) {
    //   throw new InsufficientAllowanceError(
    //     `Insufficient allowance for token ${order.sourceTokenAddress} on permit2 address ${permit2Address}. Current allowance: ${allowance}. Required allowance: ${order.sourceTokenAmount}`,
    //   );
    // }
  }

  override async validateSingleChainOrderFeasability(
    order: CreateSingleChainOrderParams & { user: string },
  ): Promise<void> {
    const chain = order.chainId as SupportedEvmChain;

    const validSingleChainChains = [ChainID.Hyperliquid, ChainID.Base];

    if (!validSingleChainChains.includes(chain)) {
      throw new ValidationError(`Chain ${chain} is not supported for single chain orders`);
    }

    const client = ChainProvider.getClient(chain);
    const user = order.user as Address;

    if (isNativeEvmToken(order.tokenIn)) {
      throw new ValidationError('Native token is not supported.');
    }

    const ERC20Contract = getContract({
      address: order.tokenIn as Address,
      abi: ERC20ABI,
      client: {
        public: client,
        chain,
      },
    });

    const balance = await ERC20Contract.read.balanceOf([user]);

    if (balance < order.amountIn) {
      throw new InsufficientBalanceError(
        `Insufficient balance for token ${order.tokenIn}. Current balance: ${balance}. Required balance: ${order.amountIn}`,
      );
    }

    // const permit2Address = PERMIT2_ADDRESS[chain];
    // const allowance = await ERC20Contract.read.allowance([user, permit2Address]);

    // if (allowance < order.amountIn) {
    //   throw new InsufficientAllowanceError(
    //     `Insufficient allowance for token ${order.tokenIn} on permit2 address ${permit2Address}. Current allowance: ${allowance}. Required allowance: ${order.amountIn}`,
    //   );
    // }
  }
}
