import { ref, reactive, computed } from 'vue';
import { getAccount, watchAccount, watchChainId, type Config } from '@wagmi/core';
import { type WatchAccountReturnType, type GetAccountReturnType, type GetBalanceReturnType } from '@wagmi/core';
import { config, KRAIKEN_LOCAL_CHAIN } from '@/wagmi';
import { getAllowance, HarbContract, getNonce } from '@/contracts/harb';
import logger from '@/utils/logger';
import { setHarbContract } from '@/contracts/harb';
import { setStakeContract } from '@/contracts/stake';
import { chainsData, DEFAULT_CHAIN_ID } from '@/config';
import { createPublicClient, custom, formatUnits, type EIP1193Provider, type PublicClient, type Transport, type WalletClient } from 'viem';
import { getWalletPublicClient, setWalletPublicClient } from '@/services/walletRpc';
import { trackWalletConnect } from '@harb/analytics';

const balance = ref<GetBalanceReturnType>({
  value: 0n,
  decimals: 0,
  symbol: '',
  formatted: '',
});
const account = ref<GetAccountReturnType>(getAccount(config as Config));
if (!account.value.chainId) {
  account.value.chainId = DEFAULT_CHAIN_ID;
}

const selectedChainId = computed(() => account.value.chainId ?? DEFAULT_CHAIN_ID);

export const chainData = computed(() => {
  return chainsData.find(obj => obj.id === selectedChainId.value);
});

const TOKEN_DECIMALS = 18;
const TOKEN_SYMBOL = 'KRK';

async function syncWalletPublicClient(data: GetAccountReturnType | undefined = account.value): Promise<PublicClient | null> {
  if (!data?.address || !data.connector) {
    setWalletPublicClient(null);
    return null;
  }
  try {
    const connector = data.connector;
    if (!connector || typeof connector.getProvider !== 'function') {
      setWalletPublicClient(null);
      return null;
    }

    const connectorAny = connector as {
      getProvider?: () => Promise<unknown>;
      getWalletClient?: (args: { chainId: number }) => Promise<WalletClient | null>;
    };
    const provider = connectorAny.getProvider ? await connectorAny.getProvider() : null;
    const walletClient = connectorAny.getWalletClient
      ? await connectorAny.getWalletClient({ chainId: data.chainId ?? selectedChainId.value })
      : null;

    if (!provider || typeof (provider as { request?: unknown }).request !== 'function') {
      setWalletPublicClient(null);
      return null;
    }

    const chain = (walletClient as WalletClient | null)?.chain ?? KRAIKEN_LOCAL_CHAIN;
    const transport = custom(provider as EIP1193Provider) as Transport;
    const publicClient = createPublicClient({
      chain,
      transport,
    }) as PublicClient;
    setWalletPublicClient(publicClient);
    return publicClient;
  } catch (_error) {
    logger.contract('Failed to create wallet public client');
    setWalletPublicClient(null);
    return null;
  }
}

let unwatch: WatchAccountReturnType | null = null;
let unwatchChain: WatchAccountReturnType | null = null;
export function useWallet() {
  async function loadBalance() {
    logger.contract('loadBalance');
    const userAddress = account.value.address;
    if (!userAddress) {
      return 0n;
    }
    let publicClient = getWalletPublicClient();
    if (!publicClient) {
      publicClient = await syncWalletPublicClient();
    }
    if (!publicClient) {
      return 0n;
    }
    const value = (await publicClient.readContract({
      abi: HarbContract.abi,
      address: HarbContract.contractAddress,
      functionName: 'balanceOf',
      args: [userAddress],
    })) as bigint;

    balance.value = {
      value,
      decimals: TOKEN_DECIMALS,
      symbol: TOKEN_SYMBOL,
      formatted: formatUnits(value, TOKEN_DECIMALS),
    } as GetBalanceReturnType;

    return value;
  }

  if (!unwatch) {
    unwatch = watchAccount(config as Config, {
      async onChange(data) {
        if (!data.address) {
          logger.info(`disconnected`);
          setWalletPublicClient(null);
          balance.value = {
            value: 0n,
            decimals: 0,
            symbol: '',
            formatted: '',
          };
        } else if (account.value.address !== data.address || account.value.chainId !== data.chainId) {
          logger.info(`Account changed!:`, data.address);
          if (!account.value.address && data.address) trackWalletConnect();
          account.value = data;
          await syncWalletPublicClient(data);
          await loadBalance();
          await getAllowance();
          // await loadPositions();
          await getNonce();
          setHarbContract();
          setStakeContract();
        }
      },
    });
  }

  //funzt nicht mehr-> library Änderung?
  if (account.value.address) {
    void syncWalletPublicClient(account.value);
  }

  if (!unwatchChain) {
    unwatchChain = watchChainId(config as Config, {
      async onChange(_chainId) {
        await syncWalletPublicClient(account.value);
        await loadBalance();
        await getAllowance();
        await getNonce();
      },
    });
  }

  return reactive({ balance, account, loadBalance });
}
