import React, { useEffect, useState, useCallback, useRef } from 'react';

import { ScrollView, View } from 'react-native';
import {
  Button,
  Text,
  TextInput,
} from 'react-native-paper';

import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
import {
  calculateFee,
  GasPrice,
  SigningStargateClient,
} from '@cosmjs/stargate';

import { createWallet, retrieveAccounts } from '../utils/accounts';
import AccountDetails from '../components/AccountDetails';
import styles from '../styles/stylesheet';
import { retrieveSingleAccount } from '../utils/accounts';
import DataBox from '../components/DataBox';
import { getPathKey } from '../utils/misc';
import { useNetworks } from '../context/NetworksContext';
import TxErrorDialog from '../components/TxErrorDialog';
import { BigNumber } from 'ethers';
import { MEMO } from '../screens/ApproveTransfer';

export const WalletEmbed = () => {
  const [isTxRequested, setIsTxRequested] = useState<boolean>(false);
  const [transactionDetails, setTransactionDetails] = useState<any>(null);
  const [fees, setFees] = useState('');
  const [gasLimit, setGasLimit] = useState('');
  const [isTxLoading, setIsTxLoading] = useState(false);
  const [txError, setTxError] = useState<string | null>(null);
  const txEventRef = useRef<MessageEvent | null>(null);

  const { networksData } = useNetworks();

  const getAccountsData = useCallback(async (chainId: string) => {
    const targetNetwork = networksData.find(network => network.chainId === "laconic-testnet-2");

    if (!targetNetwork) {
      return '';
    }

    const accounts = await retrieveAccounts(targetNetwork);

    if (!accounts || accounts.length === 0) {
      return '';
    }

    const accountsData = accounts.map(account => account.address).join(',');
    return accountsData;
  }, [networksData]);


  const getAddressesFromData = (accountsData: string): string[] => {
    return accountsData
      ? accountsData.split(',')
      : [];
  };

  const sendMessage = (
    source: Window | null,
    type: string,
    data: any,
    origin: string
  ): void => {
    source?.postMessage({ type, data }, origin);
  };

  useEffect(() => {
    const handleGetAccounts = async (event: MessageEvent) => {
      if (event.data.type !== 'REQUEST_WALLET_ACCOUNTS') return;

      const accountsData = await getAccountsData(event.data.chainId);
      if (!accountsData) {
        sendMessage(event.source as Window, 'ERROR', 'Wallet accounts not found', event.origin);
        return;
      }

      const addresses = getAddressesFromData(accountsData);
      sendMessage(event.source as Window, 'WALLET_ACCOUNTS_DATA', addresses, event.origin);
    };

    window.addEventListener('message', handleGetAccounts);

    return () => {
      window.removeEventListener('message', handleGetAccounts);
    };
  }, [getAccountsData]);

  useEffect(() => {
    const handleCreateAccounts = async (event: MessageEvent) => {
      if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return;

      let accountsData = await getAccountsData(event.data.chainId);

      if (!accountsData) {
        console.log("Accounts not found, creating wallet...");
        await createWallet(networksData);

        // Re-fetch newly created accounts
        accountsData = await getAccountsData(event.data.chainId);
      }

      const addresses = getAddressesFromData(accountsData);
      sendMessage(event.source as Window, 'WALLET_ACCOUNTS_DATA', addresses, event.origin);
    };

    window.addEventListener('message', handleCreateAccounts);

    return () => {
      window.removeEventListener('message', handleCreateAccounts);
    };
  }, [networksData, getAccountsData]);

  const handleTxRequested = async (event: MessageEvent) => {
    if (event.data.type !== 'REQUEST_TX') return;

    txEventRef.current = event;

    const { chainId, fromAddress, toAddress, amount } = event.data;
    const network = networksData.find(
      net => net.chainId === chainId
    );

    if (!network) {
      console.error('Network not found');
      return;
    }

    const account = await retrieveSingleAccount(network.namespace, network.chainId, fromAddress);
    if (!account) {
      throw new Error('Account not found');
    }

    const cosmosPrivKey = (
      await getPathKey(`${network.namespace}:${chainId}`, account.index)
    ).privKey;
    const sender = await DirectSecp256k1Wallet.fromKey(
      Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
      network.addressPrefix
    );

    const client = await SigningStargateClient.connectWithSigner(
      network.rpcUrl!,
      sender
    );

    const balance = await client?.getBalance(
      account.address,
      network!.nativeDenom!.toLowerCase(),
    );

    const gasPrice = GasPrice.fromString(
      network.gasPrice! + network.nativeDenom,
    );

    const sendMsg = {
      typeUrl: '/cosmos.bank.v1beta1.MsgSend',
      value: {
        fromAddress: fromAddress,
        toAddress: toAddress,
        amount: [
          {
            amount: String(amount),
            denom: network.nativeDenom!,
          },
        ],
      },
    };

    const gasEstimation = await client.simulate(
      fromAddress,
      [sendMsg],
      MEMO,
    );

    const gasLimit =
      String(
        Math.round(gasEstimation * Number(process.env.REACT_APP_GAS_ADJUSTMENT)),
      )

    const cosmosFees = calculateFee(Number(gasLimit), gasPrice);
    setGasLimit(gasLimit);

    setFees(cosmosFees.amount[0].amount);

    setTransactionDetails({
      chainId,
      fromAddress,
      toAddress,
      amount,
      account,
      balance: balance.amount,
      requestedNetwork: network,
    });

    setIsTxRequested(true);
  };

  useEffect(() => {
    window.addEventListener('message', handleTxRequested);
    return () => window.removeEventListener('message', handleTxRequested);
  }, [networksData, handleTxRequested]);

  const acceptRequestHandler = async () => {
    try {
      setIsTxLoading(true);
      const { chainId, fromAddress, toAddress, amount, requestedNetwork } =
        transactionDetails;

      const balanceBigNum = BigNumber.from(transactionDetails.balance);
      const amountBigNum = BigNumber.from(String(amount));
      if (amountBigNum.gte(balanceBigNum)) {
        throw new Error('Insufficient funds');
      }

      const cosmosPrivKey = (
        await getPathKey(`${requestedNetwork.namespace}:${chainId}`, transactionDetails.account.index)
      ).privKey;

      const sender = await DirectSecp256k1Wallet.fromKey(
        Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
        requestedNetwork.addressPrefix
      );

      const client = await SigningStargateClient.connectWithSigner(
        requestedNetwork.rpcUrl!,
        sender
      );

      const fee = calculateFee(
        Number(gasLimit),
        GasPrice.fromString(`${requestedNetwork.gasPrice}${requestedNetwork.nativeDenom}`)
      );

      const txResult = await client.sendTokens(
        fromAddress,
        toAddress,
        [{ amount: String(amount), denom: requestedNetwork.nativeDenom }],
        fee
      );

      const event = txEventRef.current;
      if (event?.source) {
        sendMessage(event.source as Window, 'TRANSACTION_SUCCESS', txResult.transactionHash, '*');
      } else {
        console.error('No event source available to send message');
      }
    } catch (error) {
      console.error('Transaction error:', error);
      setTxError('Transaction failed');
    } finally {
      setIsTxLoading(false);
    }
  };

  const rejectRequestHandler = () => {
    setIsTxRequested(false);
    setTransactionDetails(null);
    sendMessage(window, 'TRANSACTION_REJECTED', null, '*');
  };

  return (
    <>
      {isTxRequested && transactionDetails ? (
        <>
          <ScrollView contentContainerStyle={styles.appContainer}>
            <View style={styles.dataBoxContainer}>
              <Text style={styles.dataBoxLabel}>From</Text>
              <View style={styles.dataBox}>
                <AccountDetails account={transactionDetails.account} />
              </View>
            </View>
            <DataBox
              label={`Balance (${transactionDetails.requestedNetwork.nativeDenom})`}
              data={
                transactionDetails.balance === '' ||
                  transactionDetails.balance === undefined
                  ? 'Loading balance...'
                  : `${transactionDetails.balance}`
              }
            />
            <View style={styles.approveTransfer}>
              <DataBox label="To" data={transactionDetails.toAddress} />
              <DataBox
                label={`Amount (${transactionDetails.requestedNetwork.nativeDenom})`}
                data={transactionDetails.amount}
              />
              <TextInput
                mode="outlined"
                label="Fee"
                value={fees}
                onChangeText={setFees}
                style={styles.transactionFeesInput}
              />
              <TextInput
                mode="outlined"
                label="Gas Limit"
                value={gasLimit}
                onChangeText={value =>
                  /^\d+$/.test(value) ? setGasLimit(value) : null
                }
              />
            </View>
          </ScrollView>
          <View style={styles.buttonContainer}>
            <Button
              mode="contained"
              onPress={acceptRequestHandler}
              loading={isTxLoading}
              disabled={!transactionDetails.balance || !fees}
            >
              {isTxLoading ? 'Processing' : 'Yes'}
            </Button>
            <Button
              mode="contained"
              onPress={rejectRequestHandler}
              buttonColor="#B82B0D"
            >
              No
            </Button>
          </View>
        </>
      ) : null}

      <TxErrorDialog
        error={txError!}
        visible={!!txError}
        hideDialog={() => setTxError(null)}
      />
    </>
  );
};
