import { useCallback, useState, useEffect, useMemo } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { FormProvider, FieldValues } from 'react-hook-form';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useMediaQuery } from 'usehooks-ts';
import {
  AddEnvironmentVariableInput,
  AuctionParams,
  Deployer,
} from 'gql-client';
import { BigNumber } from 'ethers';

import { Select, MenuItem, FormControl, FormHelperText } from '@mui/material';

import {
  ArrowRightCircleFilledIcon,
  LoadingIcon,
} from 'components/shared/CustomIcon';
import { Heading } from '../../shared/Heading';
import { Button } from '../../shared/Button';
import { Input } from 'components/shared/Input';
import { useToast } from 'components/shared/Toast';
import { useGQLClient } from '../../../context/GQLClientContext';
import ApproveTransactionModal from './ApproveTransactionModal';
import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm';
import { EnvironmentVariablesFormValues } from 'types/types';
import {
  VITE_WALLET_IFRAME_URL,
} from 'utils/constants';
import CheckBalanceIframe from './CheckBalanceIframe';
import { useAddNetwork } from '../../../hooks/useAddNetwork';

type ConfigureDeploymentFormValues = {
  option: string;
  lrn?: string;
  numProviders?: string;
  maxPrice?: string;
};

type ConfigureFormValues = ConfigureDeploymentFormValues &
  EnvironmentVariablesFormValues;

const DEFAULT_MAX_PRICE = '10000';
const TX_APPROVAL_TIMEOUT_MS = 60000;

const Configure = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [deployers, setDeployers] = useState<Deployer[]>([]);
  const [selectedAccount, setSelectedAccount] = useState<string>();
  const [selectedDeployer, setSelectedDeployer] = useState<Deployer>();
  const [isPaymentLoading, setIsPaymentLoading] = useState(false);
  const [isPaymentDone, setIsPaymentDone] = useState(false);
  const [isFrameVisible, setIsFrameVisible] = useState(false);
  const [isAccountsDataReceived, setIsAccountsDataReceived] = useState(false);
  const [balanceMessage, setBalanceMessage] = useState<string>();
  const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>();

  const [searchParams] = useSearchParams();
  const templateId = searchParams.get('templateId');
  const queryParams = new URLSearchParams(location.search);

  const owner = queryParams.get('owner');
  const name = queryParams.get('name');
  const defaultBranch = queryParams.get('defaultBranch');
  const fullName = queryParams.get('fullName');
  const orgSlug = queryParams.get('orgSlug');
  const templateOwner = queryParams.get('templateOwner');
  const templateRepo = queryParams.get('templateRepo');
  const isPrivate = queryParams.get('isPrivate') === 'true';

  const navigate = useNavigate();
  const { toast, dismiss } = useToast();
  const client = useGQLClient();
  const { networkData } = useAddNetwork()

  const methods = useForm<ConfigureFormValues>({
    defaultValues: {
      option: 'Auction',
      maxPrice: DEFAULT_MAX_PRICE,
      lrn: '',
      numProviders: '1',
      variables: [],
    },
  });

  const selectedOption = methods.watch('option');
  const selectedNumProviders = methods.watch('numProviders') ?? '1';
  const selectedMaxPrice = methods.watch('maxPrice') ?? DEFAULT_MAX_PRICE;

  const isTabletView = useMediaQuery('(min-width: 720px)'); // md:
  const buttonSize = isTabletView ? { size: 'lg' as const } : {};

  const amountToBePaid = useMemo(() => {
    let amount: string;

    if (selectedOption === 'LRN') {
      amount = selectedDeployer?.minimumPayment?.replace(/\D/g, '') ?? '0';
    } else {
      if (!selectedNumProviders || !selectedMaxPrice) {
        return '';
      }

      const bigMaxPrice = BigNumber.from(selectedMaxPrice);
      amount = bigMaxPrice.mul(selectedNumProviders).toString();
    }

    return amount;
  }, [
    selectedOption,
    selectedDeployer?.minimumPayment,
    selectedMaxPrice,
    selectedNumProviders,
  ]);

  const createProject = async (
    data: FieldValues,
    envVariables: AddEnvironmentVariableInput[],
    senderAddress: string,
    txHash: string,
  ): Promise<string> => {
    setIsLoading(true);
    let projectId: string | null = null;

    try {
      let lrn: string | undefined;
      let auctionParams: AuctionParams | undefined;

      if (data.option === 'LRN') {
        lrn = data.lrn;
      } else if (data.option === 'Auction') {
        auctionParams = {
          numProviders: Number(data.numProviders!),
          maxPrice: data.maxPrice!.toString(),
        };
      }

      if (templateId) {
        const projectData: any = {
          templateOwner,
          templateRepo,
          owner,
          name,
          isPrivate,
          paymentAddress: senderAddress,
          txHash,
        };

        const { addProjectFromTemplate } = await client.addProjectFromTemplate(
          orgSlug!,
          projectData,
          lrn,
          auctionParams,
          envVariables,
        );

        projectId = addProjectFromTemplate.id;
      } else {
        const { addProject } = await client.addProject(
          orgSlug!,
          {
            name: `${owner}-${name}`,
            prodBranch: defaultBranch!,
            repository: fullName!,
            template: 'webapp',
            paymentAddress: senderAddress,
            txHash,
          },
          lrn,
          auctionParams,
          envVariables,
        );

        projectId = addProject.id;
      }
    } catch (error) {
      console.error('Error creating project:', error);
      toast({
        id: 'error-creating-project',
        title: 'Error creating project',
        variant: 'error',
        onDismiss: dismiss,
      });
    } finally {
      setIsLoading(false);
    }

    if (projectId) {
      return projectId;
    } else {
      throw new Error('Project creation failed');
    }
  };

  const verifyTx = async (
    senderAddress: string,
    txHash: string,
    amount: string,
  ): Promise<boolean> => {
    const isValid = await client.verifyTx(
      txHash,
      `${amount.toString()}alnt`,
      senderAddress,
    );

    return isValid;
  };

  const handleFormSubmit = useCallback(
    async (createFormData: FieldValues) => {
      try {
        const deployerLrn = createFormData.lrn;
        const deployer = deployers.find(
          (deployer) => deployer.deployerLrn === deployerLrn,
        );

        let senderAddress: string;
        let txHash: string | null = null;
        if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
          toast({
            id: 'no-payment-required',
            title: 'No payment required. Deploying app...',
            variant: 'info',
            onDismiss: dismiss,
          });

          txHash = '';
          senderAddress = '';
        } else {
          if (!selectedAccount) return;

          senderAddress = selectedAccount;

          txHash = await cosmosSendTokensHandler(senderAddress, amountToBePaid);

          if (!txHash) {
            toast({
              id: 'unsuccessful-tx',
              title: 'Transaction rejected',
              variant: 'error',
              onDismiss: dismiss,
            });
            setIsFrameVisible(false);
            setIsPaymentLoading(false);
            throw new Error('Transaction rejected');
          }

          // Validate transaction hash
          const isTxHashValid = await verifyTx(
            senderAddress,
            txHash,
            amountToBePaid,
          );
          setIsPaymentLoading(false);

          if (isTxHashValid) {
            toast({
              id: 'payment-successful',
              title: 'Payment successful',
              variant: 'success',
              onDismiss: dismiss,
            });
            setIsPaymentDone(true);
          } else {
            toast({
              id: 'invalid-tx-hash',
              title: 'Transaction validation failed',
              variant: 'error',
              onDismiss: dismiss,
            });
            throw new Error('Transaction validation failed');
          }
        }

        const environmentVariables = createFormData.variables.map(
          (variable: any) => {
            return {
              key: variable.key,
              value: variable.value,
              environments: Object.entries(createFormData.environment)
                .filter(([, value]) => value === true)
                .map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)),
            };
          },
        );

        const projectId = await createProject(
          createFormData,
          environmentVariables,
          senderAddress,
          txHash!,
        );

        await client.getEnvironmentVariables(projectId);

        if (templateId) {
          createFormData.option === 'Auction'
            ? navigate(
                `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`,
              )
            : navigate(
                `/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}`,
              );
        } else {
          createFormData.option === 'Auction'
            ? navigate(
                `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`,
              )
            : navigate(
                `/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
              );
        }
      } catch (error: any) {
        toast({
          id: 'error-deploying-app',
          title: 'Error deploying app',
          variant: 'error',
          onDismiss: dismiss,
        });
        throw new Error(error);
      }
    },
    [client, createProject, dismiss, toast, amountToBePaid],
  );

  const fetchDeployers = useCallback(async () => {
    const res = await client.getDeployers();
    setDeployers(res.deployers);
  }, [client]);

  const onDeployerChange = useCallback(
    (selectedLrn: string) => {
      const deployer = deployers.find((d) => d.deployerLrn === selectedLrn);
      setSelectedDeployer(deployer);
    },
    [deployers],
  );

  const cosmosSendTokensHandler = useCallback(
    async (selectedAccount: string, amount: string) => {
      if (!selectedAccount) {
        throw new Error('Account not selected');
      }

      const senderAddress = selectedAccount;
      const snowballAddress = await client.getAddress();
      let timeoutId;

      try {
        setIsPaymentDone(false);
        setIsPaymentLoading(true);

        await requestTx(senderAddress, snowballAddress, amount);

        const txHash = await new Promise<string | null>((resolve, reject) => {
          // Call cleanup method only if appropriate event type is recieved
          const cleanup = () => {
            setIsFrameVisible(false);
            window.removeEventListener('message', handleTxStatus);
          };

          const handleTxStatus = async (event: MessageEvent) => {
            if (event.origin !== VITE_WALLET_IFRAME_URL) return;

            if (event.data.type === 'TRANSACTION_RESPONSE') {
              const txResponse = event.data.data;
              resolve(txResponse);

              cleanup();
            } else if (event.data.type === 'ERROR') {
              console.error('Error from wallet:', event.data.message);
              reject(new Error('Transaction failed'));
              toast({
                id: 'error-transaction',
                title: 'Error during transaction',
                variant: 'error',
                onDismiss: dismiss,
              });

              cleanup();
            }
          };

          window.addEventListener('message', handleTxStatus);

          // Set a timeout, consider unsuccessful after 1 min
          timeoutId = setTimeout(() => {
            reject(new Error('Transaction timeout'));
            window.removeEventListener('message', handleTxStatus);
            toast({
              id: 'transaction-timeout',
              title: 'The transaction request timed out. Please try again',
              variant: 'error',
              onDismiss: dismiss,
            });
            setIsFrameVisible(false);
            setIsPaymentLoading(false);
          }, TX_APPROVAL_TIMEOUT_MS);
        });
        return txHash;
      } catch (error) {
        console.error('Error in transaction:', error);
        throw new Error('Error in transaction');
      } finally {
        clearTimeout(timeoutId);
      }
    },
    [client, dismiss, toast],
  );

  const requestTx = async (
    sender: string,
    recipient: string,
    amount: string,
  ) => {
    const iframe = document.getElementById('walletIframe') as HTMLIFrameElement;

    if (!iframe.contentWindow) {
      console.error('Iframe not found or not loaded');
      throw new Error('Iframe not found or not loaded');
    }

    iframe.contentWindow.postMessage(
      {
        type: 'REQUEST_TX',
        chainId: networkData?.chainId,
        fromAddress: sender,
        toAddress: recipient,
        amount,
      },
      VITE_WALLET_IFRAME_URL,
    );

    setIsFrameVisible(true);
  };

  useEffect(() => {
    fetchDeployers();
  }, []);

  useEffect(() => {
    if (isBalanceSufficient) {
      setBalanceMessage(undefined);
    }
  }, [isBalanceSufficient]);

  return (
    <div className="space-y-7 px-4 py-6">
      <div className="flex justify-between mb-6">
        <div className="space-y-1.5">
          <Heading as="h4" className="md:text-lg font-medium">
            Configure deployment
          </Heading>
          <Heading
            as="h5"
            className="text-sm font-sans text-elements-low-em dark:text-foreground-secondaryu"
          >
            The app can be deployed by setting the deployer LRN for a single
            deployment or by creating a deployer auction for multiple
            deployments
          </Heading>
        </div>
      </div>

      <div className="flex flex-col gap-6 lg:gap-8 w-full">
        <FormProvider {...methods}>
          <form onSubmit={methods.handleSubmit(handleFormSubmit)}>
            <div className="flex flex-col justify-start gap-4 mb-6">
              <Controller
                name="option"
                control={methods.control}
                render={({ field: { value, onChange } }) => (
                  <Select
                    value={value}
                    onChange={(event) => onChange(event.target.value)}
                    size="small"
                    displayEmpty
                    className="dark:bg-overlay2 dark:text-foreground"
                    sx={{
                      fontFamily: 'inherit',
                      '& .MuiOutlinedInput-notchedOutline': {
                        borderColor: '#e0e0e0',
                        borderRadius: '8px',
                      },
                    }}
                  >
                    <MenuItem value="Auction">Create Auction</MenuItem>
                    <MenuItem value="LRN">Deployer LRN</MenuItem>
                  </Select>
                )}
              />
            </div>

            {selectedOption === 'LRN' && (
              <div className="flex flex-col justify-start gap-4 mb-6">
                <Heading
                  as="h5"
                  className="text-sm font-sans text-elements-low-em dark:text-foreground-secondary"
                >
                  The app will be deployed by the configured deployer
                </Heading>
                <Controller
                  name="lrn"
                  control={methods.control}
                  rules={{ required: true }}
                  render={({ field: { value, onChange }, fieldState }) => (
                    <FormControl fullWidth error={Boolean(fieldState.error)}>
                      <span className="text-sm dark:text-foreground text-elements-high-em dark:text-foreground mb-4">
                        Select deployer LRN
                      </span>
                      <Select
                        value={value}
                        onChange={(event) => {
                          onChange(event.target.value);
                          onDeployerChange(event.target.value);
                        }}
                        displayEmpty
                        size="small"
                        className="dark:bg-overlay2 dark:text-foreground"
                      >
                        {deployers.map((deployer) => (
                          <MenuItem
                            key={deployer.deployerLrn}
                            value={deployer.deployerLrn}
                          >
                            {`${deployer.deployerLrn} ${deployer.minimumPayment ? `(${deployer.minimumPayment})` : ''}`}
                          </MenuItem>
                        ))}
                      </Select>
                      {fieldState.error && (
                        <FormHelperText>
                          {fieldState.error.message}
                        </FormHelperText>
                      )}
                    </FormControl>
                  )}
                />
              </div>
            )}

            {selectedOption === 'Auction' && (
              <>
                <div className="flex flex-col justify-start gap-4 mb-6">
                  <Heading
                    as="h5"
                    className="text-sm font-sans text-elements-low-em dark:text-foreground-secondary"
                  >
                    Set the number of deployers and maximum price for each
                    deployment
                  </Heading>
                  <span className="text-sm text-elements-high-em dark:text-foreground">
                    Number of Deployers
                  </span>
                  <Controller
                    name="numProviders"
                    control={methods.control}
                    rules={{ required: true }}
                    render={({ field: { value, onChange } }) => (
                      <Input
                        type="number"
                        value={value}
                        onChange={(e) => onChange(e)}
                        min={1}
                      />
                    )}
                  />
                </div>
                <div className="flex flex-col justify-start gap-4 mb-6">
                  <span className="text-sm text-elements-high-em dark:text-foreground">
                    Maximum Price (alnt)
                  </span>
                  <Controller
                    name="maxPrice"
                    control={methods.control}
                    rules={{ required: true }}
                    render={({ field: { value, onChange } }) => (
                      <Input type="number" value={value} onChange={onChange} min={1} />
                    )}
                  />
                </div>
              </>
            )}

            <Heading as="h4" className="md:text-lg font-medium mb-3">
              Environment Variables
            </Heading>
            <div className="p-4 bg-slate-100 dark:bg-overlay3 rounded-lg mb-6">
              <EnvironmentVariablesForm />
            </div>

            {selectedOption === 'LRN' && !selectedDeployer?.minimumPayment ? (
              <div>
                <Button
                  {...buttonSize}
                  type="submit"
                  disabled={isLoading || !selectedDeployer}
                  rightIcon={
                    isLoading ? (
                      <LoadingIcon className="animate-spin" />
                    ) : (
                      <ArrowRightCircleFilledIcon />
                    )
                  }
                >
                  {isLoading ? 'Deploying' : 'Deploy'}
                </Button>
              </div>
            ) : (
              <div className="flex gap-4">
                <Button
                  {...buttonSize}
                  type="submit"
                  shape="default"
                  disabled={
                    isLoading ||
                    isPaymentLoading ||
                    !selectedAccount ||
                    !isBalanceSufficient ||
                    amountToBePaid === '' ||
                    selectedNumProviders === ''
                  }
                  rightIcon={
                    isLoading || isPaymentLoading ? (
                      <LoadingIcon className="animate-spin" />
                    ) : (
                      <ArrowRightCircleFilledIcon />
                    )
                  }
                >
                  {!isPaymentDone
                    ? isPaymentLoading
                      ? 'Transaction Requested'
                      : 'Pay and Deploy'
                    : isLoading
                      ? 'Deploying'
                      : 'Deploy'}
                </Button>
                {isAccountsDataReceived && isBalanceSufficient !== undefined ? (
                  !selectedAccount || !isBalanceSufficient ? (
                    <div className="flex items-center gap-4">
                      <Button
                        {...buttonSize}
                        shape="default"
                        onClick={(e: any) => {
                          e.preventDefault();
                          setBalanceMessage('Waiting for payment');
                          window.open(
                            'https://store.laconic.com',
                            '_blank',
                            'noopener,noreferrer',
                          );
                        }}
                      >
                        Buy prepaid service
                      </Button>
                      <p className="text-gray-700 dark:text-gray-300">
                        {balanceMessage !== undefined ? (
                          <div className="flex items-center gap-2 text-white">
                            <LoadingIcon className="animate-spin w-5 h-5" />
                            <p>{balanceMessage}</p>
                          </div>
                        ) : !selectedAccount ? (
                          'No accounts found. Create a wallet.'
                        ) : (
                          'Insufficient funds.'
                        )}
                      </p>
                    </div>
                  ) : null
                ) : null}
              </div>
            )}
          </form>
        </FormProvider>

        <ApproveTransactionModal
          setAccount={setSelectedAccount}
          setIsDataReceived={setIsAccountsDataReceived}
          isVisible={isFrameVisible}
        />
        <CheckBalanceIframe
          onBalanceChange={setIsBalanceSufficient}
          amount={amountToBePaid}
          isPollingEnabled={true}
        />
      </div>
    </div>
  );
};

export default Configure;
