import { addDecimal, fromNanoSeconds } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useVegaWallet } from '@vegaprotocol/wallet';
import compact from 'lodash/compact';
import uniqBy from 'lodash/uniqBy';
import type { ChangeEvent } from 'react';
import { useCallback, useMemo, useState } from 'react';
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
import * as Schema from '@vegaprotocol/types';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import {
  AsyncRenderer,
  Splash,
  Toggle,
  TradingButton,
  TradingDropdown,
  TradingDropdownContent,
  TradingDropdownItem,
  TradingDropdownTrigger,
} from '@vegaprotocol/ui-toolkit';
import { AccountTypeMapping } from '@vegaprotocol/types';
import { PriceChart } from 'pennant';
import 'pennant/dist/style.css';
import type { Account } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts';
import {
  useLocalStorageSnapshot,
  useThemeSwitcher,
} from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { getAsset, type Market } from '@vegaprotocol/markets';

export const DateRange = {
  RANGE_1D: '1D',
  RANGE_7D: '7D',
  RANGE_1M: '1M',
  RANGE_3M: '3M',
  RANGE_1Y: '1Y',
  RANGE_YTD: 'YTD',
  RANGE_ALL: 'All',
};

const dateRangeToggleItems = Object.entries(DateRange).map(([_, value]) => ({
  label: t(value),
  value: value,
}));

export const calculateStartDate = (range: string): string | undefined => {
  const now = new Date();
  switch (range) {
    case DateRange.RANGE_1D:
      return new Date(now.setDate(now.getDate() - 1)).toISOString();
    case DateRange.RANGE_7D:
      return new Date(now.setDate(now.getDate() - 7)).toISOString();
    case DateRange.RANGE_1M:
      return new Date(now.setMonth(now.getMonth() - 1)).toISOString();
    case DateRange.RANGE_3M:
      return new Date(now.setMonth(now.getMonth() - 3)).toISOString();
    case DateRange.RANGE_1Y:
      return new Date(now.setFullYear(now.getFullYear() - 1)).toISOString();
    case DateRange.RANGE_YTD:
      return new Date(now.setMonth(0)).toISOString();
    default:
      return undefined;
  }
};

export const AccountHistoryContainer = () => {
  const { pubKey } = useVegaWallet();
  const { data: assets } = useAssetsDataProvider();

  if (!pubKey) {
    return <Splash>{t('Please connect Vega wallet')}</Splash>;
  }

  return (
    <AsyncRenderer loading={!assets} error={undefined} data={assets}>
      {assets && <AccountHistoryManager pubKey={pubKey} assetData={assets} />}
    </AsyncRenderer>
  );
};

const AccountHistoryManager = ({
  pubKey,
  assetData,
}: {
  pubKey: string;
  assetData: AssetFieldsFragment[];
}) => {
  const [accountType, setAccountType] = useState<Schema.AccountType>(
    Schema.AccountType.ACCOUNT_TYPE_GENERAL
  );

  const variablesForOneTimeQuery = useMemo(
    () => ({
      partyId: pubKey,
    }),
    [pubKey]
  );

  const { data: accounts } = useDataProvider({
    dataProvider: accountsDataProvider,
    variables: variablesForOneTimeQuery,
    skip: !pubKey,
  });

  const assetIds = useMemo(
    () => accounts?.map((e) => e?.asset?.id) || [],
    [accounts]
  );

  const assets = useMemo(
    () =>
      assetData
        .filter((a) => assetIds.includes(a.id))
        .sort((a, b) => a.name.localeCompare(b.name)),
    [assetData, assetIds]
  );
  const [assetId, setAssetId] = useLocalStorageSnapshot(
    'account-history-active-asset-id'
  );

  const asset = useMemo(
    () => assets.find((a) => a.id === assetId) || assets[0],
    [assetId, assets]
  );

  const [range, setRange] = useState<typeof DateRange[keyof typeof DateRange]>(
    DateRange.RANGE_1M
  );
  const [market, setMarket] = useState<Market | null>(null);

  const marketFilterCb = useCallback(
    (item: Market) => {
      const itemAsset = getAsset(item);
      return !asset?.id || itemAsset?.id === asset?.id;
    },
    [asset?.id]
  );
  const markets = useMemo<Market[] | null>(() => {
    const arr =
      accounts
        ?.filter((item: Account) => Boolean(item && item.market))
        .map<Market>((item) => item.market as Market) ?? null;
    return arr
      ? uniqBy(arr.filter(marketFilterCb), 'id').sort((a, b) =>
          a.tradableInstrument.instrument.code.localeCompare(
            b.tradableInstrument.instrument.code
          )
        )
      : null;
  }, [accounts, marketFilterCb]);

  const resolveMarket = useCallback(
    (m: Market) => {
      setMarket(m);
      const itemAsset = getAsset(m);
      const newAssetId = itemAsset?.id;
      const newAsset = assets.find((item) => item.id === newAssetId);
      if ((!asset || (assets && newAssetId !== asset.id)) && newAsset) {
        setAssetId(newAsset.id);
      }
    },
    [asset, assets, setAssetId]
  );

  const variables = useMemo(
    () => ({
      partyId: pubKey,
      assetId: asset?.id || '',
      accountTypes: accountType ? [accountType] : undefined,
      dateRange:
        range === 'All' ? undefined : { start: calculateStartDate(range) },
      marketIds: market?.id ? [market.id] : undefined,
    }),
    [pubKey, asset, accountType, range, market?.id]
  );

  const { data } = useAccountHistoryQuery({
    variables,
    skip: !asset || !pubKey,
  });

  return (
    <div className="flex flex-col h-full gap-2">
      <div className="flex flex-wrap justify-between px-1 pt-2 gap-2">
        <div className="flex items-center gap-1 shrink-0">
          <TradingDropdown
            trigger={
              <TradingDropdownTrigger>
                <TradingButton size="small">
                  {accountType
                    ? `${
                        AccountTypeMapping[
                          accountType as keyof typeof Schema.AccountType
                        ]
                      } Account`
                    : t('Select account type')}
                </TradingButton>
              </TradingDropdownTrigger>
            }
          >
            <TradingDropdownContent>
              {[
                Schema.AccountType.ACCOUNT_TYPE_GENERAL,
                Schema.AccountType.ACCOUNT_TYPE_BOND,
                Schema.AccountType.ACCOUNT_TYPE_MARGIN,
              ].map((type) => (
                <TradingDropdownItem
                  key={type}
                  onClick={() => {
                    setAccountType(type as Schema.AccountType);

                    // if not a margin account clear any market selection
                    if (type !== Schema.AccountType.ACCOUNT_TYPE_MARGIN) {
                      setMarket(null);
                    }
                  }}
                >
                  {AccountTypeMapping[type as keyof typeof Schema.AccountType]}
                </TradingDropdownItem>
              ))}
            </TradingDropdownContent>
          </TradingDropdown>

          <TradingDropdown
            trigger={
              <TradingDropdownTrigger>
                <TradingButton size="small">
                  {asset ? asset.symbol : t('Select asset')}
                </TradingButton>
              </TradingDropdownTrigger>
            }
          >
            <TradingDropdownContent>
              {assets.map((a) => (
                <TradingDropdownItem
                  key={a.id}
                  onClick={() => {
                    setAssetId(a.id);

                    // if the selected asset is different to the selected market clear the market
                    if (market && a.id !== getAsset(market).id) {
                      setMarket(null);
                    }
                  }}
                >
                  {a.symbol}
                </TradingDropdownItem>
              ))}
            </TradingDropdownContent>
          </TradingDropdown>
          <TradingDropdown
            trigger={
              <TradingDropdownTrigger>
                <TradingButton size="small">
                  {market
                    ? market.tradableInstrument.instrument.code
                    : t('Select market')}
                </TradingButton>
              </TradingDropdownTrigger>
            }
          >
            <TradingDropdownContent>
              {market && (
                <TradingDropdownItem key="0" onClick={() => setMarket(null)}>
                  {t('All markets')}
                </TradingDropdownItem>
              )}
              {markets?.map((m) => (
                <TradingDropdownItem
                  key={m.id}
                  onClick={() => resolveMarket(m)}
                >
                  {m.tradableInstrument.instrument.code}
                </TradingDropdownItem>
              ))}
            </TradingDropdownContent>
          </TradingDropdown>
        </div>
        <div className="justify-items-end">
          <Toggle
            id="account-history-date-range"
            name="account-history-date-range"
            toggles={dateRangeToggleItems}
            checkedValue={range}
            onChange={(e: ChangeEvent<HTMLInputElement>) =>
              setRange(e.target.value as keyof typeof DateRange)
            }
            size="sm"
          />
        </div>
      </div>
      <div className="flex-1">
        {asset && (
          <div className="h-full">
            <AccountHistoryChart
              data={data}
              accountType={accountType}
              asset={asset}
            />
          </div>
        )}
      </div>
    </div>
  );
};

export const AccountHistoryChart = ({
  data,
  accountType,
  asset,
}: {
  data: AccountHistoryQuery | undefined;
  accountType: Schema.AccountType;
  asset: AssetFieldsFragment;
}) => {
  const { theme } = useThemeSwitcher();
  const values: { cols: [string, string]; rows: [Date, number][] } | null =
    useMemo(() => {
      if (!data?.balanceChanges.edges.length) {
        return null;
      }

      const valuesData = compact(data.balanceChanges.edges)
        .reduce((acc, edge) => {
          if (edge.node.accountType === accountType) {
            acc?.push({
              datetime: fromNanoSeconds(edge.node.timestamp),
              balance: Number(addDecimal(edge.node.balance, asset.decimals)),
            });
          }
          return acc;
        }, [] as { datetime: Date; balance: number }[])
        .reverse();
      return {
        cols: ['Date', `${asset.symbol} account balance`],
        rows: compact(valuesData).map((d) => [d.datetime, d.balance]),
      };
    }, [accountType, asset.decimals, asset.symbol, data?.balanceChanges.edges]);

  if (!data || !values?.rows.length) {
    return <Splash> {t('No account history data')}</Splash>;
  }

  return <PriceChart data={values} theme={theme} />;
};
