/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

import {
  AbsoluteTime,
  Amounts,
  NotificationType,
  ScopeInfo,
  ScopeType,
  Transaction,
  URL,
  WalletBalance,
  parseScopeInfoShort,
  stringifyScopeInfoShort,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { startOfDay } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { HistoryItem } from "../components/HistoryItem.js";
import { Loading } from "../components/Loading.js";
import { Time } from "../components/Time.js";
import {
  CenteredBoldText,
  CenteredText,
  DateSeparator,
  NiceSelect,
} from "../components/styled/index.js";
import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useSettings } from "../hooks/useSettings.js";
import { Button } from "../mui/Button.js";
import { TextField } from "../mui/TextField.js";
import { TextFieldHandler } from "../mui/handlers.js";
import { NoBalanceHelp } from "../popup/NoBalanceHelp.js";
import DownloadIcon from "../svg/download_24px.inline.svg";
import UploadIcon from "../svg/upload_24px.inline.svg";

interface Props {
  scope?: ScopeInfo;
  search?: boolean;
  goToWalletDeposit: (scope: ScopeInfo) => Promise<void>;
  goToWalletManualWithdraw: (scope?: ScopeInfo) => Promise<void>;
}
export function HistoryPage({
  scope,
  search: showSearch,
  goToWalletManualWithdraw,
  goToWalletDeposit,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const api = useBackendContext();
  const [selectedScope, setSelectedScope] = useState(scope);
  const [search, setSearch] = useState<string>();

  const [settings] = useSettings();
  const state = useAsyncAsHook(async () => {
    const b = await api.wallet.call(WalletApiOperation.GetBalances, {});
    const balances = b.balances;
    const tx = await api.wallet.call(WalletApiOperation.GetTransactionsV2, {
      scopeInfo: showSearch ? undefined : selectedScope,
      includeRefreshes: settings.showRefeshTransactions,
      limit: -100
    });
    return { balances, transactions: tx.transactions };
  }, [selectedScope, search]);

  useEffect(() => {
    return api.listener.onUpdateNotification(
      [NotificationType.TransactionStateTransition],
      state?.retry,
    );
  });
  const { pushAlertOnError } = useAlertContext();

  if (!state) {
    return <Loading />;
  }

  if (state.hasError) {
    return (
      <ErrorAlertView
        error={alertFromError(
          i18n,
          i18n.str`Could not load the list of transactions`,
          state,
        )}
      />
    );
  }

  if (!state.response.balances.length) {
    return (
      <NoBalanceHelp
        goToWalletManualWithdraw={{
          onClick: pushAlertOnError(() =>
            goToWalletManualWithdraw(selectedScope),
          ),
        }}
      />
    );
  }

  const txsByDate = state.response.transactions.reduce(
    (rv, x) => {
      const startDay =
        x.timestamp.t_s === "never"
          ? 0
          : startOfDay(x.timestamp.t_s * 1000).getTime();
      if (startDay) {
        if (!rv[startDay]) {
          rv[startDay] = [];
          // datesWithTransaction.push(String(startDay));
        }
        rv[startDay].push(x);
      }

      return rv;
    },
    {} as { [x: string]: Transaction[] },
  );

  if (showSearch) {
    return (
      <FilteredHistoryView
        search={{
          value: search ?? "",
          onInput: pushAlertOnError(async (d: string) => {
            setSearch(d);
          }),
        }}
        transactionsByDate={txsByDate}
      />
    );
  }

  return (
    <HistoryView
      scope={selectedScope ?? state.response.balances[0].scopeInfo}
      changeScope={(b) => setSelectedScope(b)}
      balances={state.response.balances}
      goToWalletManualWithdraw={goToWalletManualWithdraw}
      goToWalletDeposit={goToWalletDeposit}
      transactionsByDate={txsByDate}
    />
  );
}

export function HistoryView({
  balances,
  scope,
  changeScope,
  transactionsByDate,
  goToWalletManualWithdraw,
  goToWalletDeposit,
}: {
  scope: ScopeInfo;
  changeScope: (scope: ScopeInfo) => void;
  goToWalletDeposit: (scope: ScopeInfo) => Promise<void>;
  goToWalletManualWithdraw: (scope: ScopeInfo) => Promise<void>;
  transactionsByDate: Record<string, Transaction[]>;
  balances: WalletBalance[];
}): VNode {
  const { i18n } = useTranslationContext();

  const scopeStr = stringifyScopeInfoShort(scope);
  const balanceIndex = balances.findIndex(
    (b) => stringifyScopeInfoShort(b.scopeInfo) === scopeStr,
  );
  const balance = balances[balanceIndex];
  if (!balance) {
    return (
      <div>
        <i18n.Translate>unknown scope</i18n.Translate>
      </div>
    );
  }

  const available = balance
    ? Amounts.jsonifyAmount(balance.available)
    : undefined;

  const datesWithTransaction: string[] = Object.keys(transactionsByDate);

  const groupByScope = balances.reduce(
    (prev, cur) => {
      const key = stringifyScopeInfoShort(cur.scopeInfo);
      const value = prev[key] ?? [];
      value.push(cur);
      prev[key] = value;
      return prev;
    },
    {} as Record<string, WalletBalance[]>,
  );
  // scopes.sort((a, b) => {
  //   return a[1]
  // });
  return (
    <Fragment>
      <section>
        <div
          style={{
            display: "flex",
            flexWrap: "wrap",
            alignItems: "center",
            justifyContent: "space-between",
            marginRight: 20,
          }}
        >
          <div>
            <Button
              tooltip="Transfer money to the wallet"
              startIcon={DownloadIcon}
              variant="contained"
              onClick={() => goToWalletManualWithdraw(balance.scopeInfo)}
            >
              <i18n.Translate>Receive</i18n.Translate>
            </Button>
            {available && Amounts.isNonZero(available) && (
              <Button
                tooltip="Transfer money from the wallet"
                startIcon={UploadIcon}
                variant="outlined"
                color="primary"
                onClick={() => goToWalletDeposit(balance.scopeInfo)}
              >
                <i18n.Translate>Send</i18n.Translate>
              </Button>
            )}
          </div>
          <div style={{ display: "flex", flexDirection: "column" }}>
            <h3 style={{ marginBottom: 0 }}>
              <i18n.Translate>Balance</i18n.Translate>
            </h3>
            <div
              style={{
                width: "fit-content",
                display: "flex",
              }}
            >
              {balances.length === 1 ? (
                <CenteredText style={{ fontSize: "x-large", margin: 8 }}>
                  {balance.scopeInfo.currency}
                  <div style={{ fontSize: "small", color: "grey" }}>
                    {balance.scopeInfo.type === ScopeType.Exchange ||
                    balance.scopeInfo.type === ScopeType.Auditor
                      ? balance.scopeInfo.url
                      : undefined}
                  </div>
                </CenteredText>
              ) : (
                <NiceSelect style={{ flexDirection: "column" }}>
                  <select
                    style={{
                      fontSize: "x-large",
                    }}
                    value={scopeStr}
                    onChange={(e) => {
                      const value = e.currentTarget.value;
                      const sc = parseScopeInfoShort(value);
                      if (!sc) {
                        console.error("no scope for", value);
                        return;
                      }
                      changeScope(sc);
                    }}
                  >
                    {Object.keys(groupByScope).map((key, si) => {
                      const entry = groupByScope[key][0];
                      const st = stringifyScopeInfoShort(entry.scopeInfo);
                      const sc = entry.scopeInfo;
                      if (sc.type === ScopeType.Global) {
                        return (
                          <option value={st} key={st}>
                            <b>{sc.currency}</b>
                          </option>
                        );
                      }

                      return (
                        <Fragment>
                          {groupByScope[key].map((ex) => {
                            const st = stringifyScopeInfoShort(ex.scopeInfo);
                            if (ex.scopeInfo.type === ScopeType.Global) {
                              return <Fragment />;
                            }
                            return (
                              <optgroup
                                label={new URL(ex.scopeInfo.url).hostname}
                              >
                                <option value={st} key={st}>
                                  {ex.scopeInfo.currency}
                                </option>
                              </optgroup>
                            );
                          })}
                        </Fragment>
                      );
                    })}
                  </select>
                  <div style={{ fontSize: "small", color: "grey" }}>
                    {balance.scopeInfo.type === ScopeType.Exchange ||
                    balance.scopeInfo.type === ScopeType.Auditor
                      ? balance.scopeInfo.url
                      : undefined}
                  </div>
                </NiceSelect>
              )}
              {available && (
                <CenteredBoldText
                  style={{
                    display: "inline-block",
                    fontSize: "x-large",
                    margin: 8,
                  }}
                >
                  {Amounts.stringifyValue(available, 2)}
                </CenteredBoldText>
              )}
            </div>
          </div>
        </div>
      </section>
      {datesWithTransaction.length === 0 ? (
        <section>
          <i18n.Translate>
            Your transaction history is empty for this currency.
          </i18n.Translate>
        </section>
      ) : (
        <section>
          {datesWithTransaction.map((d, i) => {
            return (
              <Fragment key={i}>
                <DateSeparator>
                  <Time
                    timestamp={AbsoluteTime.fromMilliseconds(
                      Number.parseInt(d, 10),
                    )}
                    format="dd MMMM yyyy"
                  />
                </DateSeparator>
                {transactionsByDate[d].map((tx, i) => (
                  <HistoryItem key={i} tx={tx} />
                ))}
              </Fragment>
            );
          })}
        </section>
      )}
    </Fragment>
  );
}

export function FilteredHistoryView({
  search,
  transactionsByDate,
}: {
  search: TextFieldHandler;
  transactionsByDate: Record<string, Transaction[]>;
}): VNode {
  const { i18n } = useTranslationContext();

  const datesWithTransaction: string[] = Object.keys(transactionsByDate);

  return (
    <Fragment>
      <section>
        <div
          style={{
            display: "flex",
            flexWrap: "wrap",
            alignItems: "center",
            justifyContent: "space-between",
            marginRight: 20,
          }}
        >
          <TextField
            label="Search"
            variant="filled"
            error={search.error}
            required
            fullWidth
            value={search.value}
            onChange={search.onInput}
          />
        </div>
      </section>
      {datesWithTransaction.length === 0 ? (
        <section>
          <i18n.Translate>
            Your transaction history is empty for this currency.
          </i18n.Translate>
        </section>
      ) : (
        <section>
          {datesWithTransaction.map((d, i) => {
            return (
              <Fragment key={i}>
                <DateSeparator>
                  <Time
                    timestamp={AbsoluteTime.fromMilliseconds(
                      Number.parseInt(d, 10),
                    )}
                    format="dd MMMM yyyy"
                  />
                </DateSeparator>
                {transactionsByDate[d].map((tx, i) => (
                  <HistoryItem key={i} tx={tx} />
                ))}
              </Fragment>
            );
          })}
        </section>
      )}
    </Fragment>
  );
}
