/*
 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 {
  NotificationType,
  ObservabilityEventType,
  TalerErrorCode,
  TalerErrorDetail,
  TransactionMajorState,
  WalletNotification,
  assertUnreachable
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, JSX, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { Pages } from "../NavigationBar.js";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
import { Modal } from "./Modal.js";
import { Time } from "./Time.js";
import { useSettings } from "../hooks/useSettings.js";

interface Props extends JSX.HTMLAttributes {
}


export function WalletActivity({ }: Props): VNode {
  const { i18n } = useTranslationContext()
  const [settings, updateSettings] = useSettings()
  useEffect(() => {
    document.body.style.marginBottom = "250px"
    return () => {
      document.body.style.marginBottom = "0px"
    }
  })
  const [table, setTable] = useState<"tasks" | "events" | "calls">("tasks")
  return (
    <div style={{ position: "fixed", bottom: 0, background: "white", zIndex: 1, height: 250, overflowY: "scroll", width: "100%" }}>
      <div style={{ display: "flex", justifyContent: "space-between", float: "right" }}>
        <div />
        <div onClick={() => {
          updateSettings("showWalletActivity", false)
        }}>
          close
        </div>
      </div>
      <div>
        <Button variant={table === "tasks" ? "contained" : "outlined"}
          onClick={async () => {
            setTable("tasks")
          }}
        >
          <i18n.Translate>Tasks</i18n.Translate>
        </Button>
        <Button variant={table === "events" ? "contained" : "outlined"}
          onClick={async () => {
            setTable("events")
          }}
        >
          <i18n.Translate>Events</i18n.Translate>
        </Button>

        <Button variant={table === "calls" ? "contained" : "outlined"}
          onClick={async () => {
            setTable("calls")
          }}
        >
          <i18n.Translate>Calls</i18n.Translate>
        </Button>
      </div>
      {(function (): VNode {
        switch (table) {
          case "events": {
            return <ObservavilityEventsTable />
          }
          case "calls": {
            return <WalletCallsTable />
          }
          case "tasks": {
            return <ActiveTasksTable />
          }
          default: {
            assertUnreachable(table)
          }
        }
      })()}
    </div>
  );
}

export function WalletCallsTable({ }: {}): VNode {
  return <div />
}
const notifications: WalletNotification[] = []
export function ObservavilityEventsTable({ }: {}): VNode {
  const { i18n } = useTranslationContext()
  const listenAllEvents = Array.from<NotificationType>({ length: 1 });
  listenAllEvents.includes = () => true
  const api = useBackendContext();
  const [lastEvent, setLastEvent] = useState<Date>(new Date())
  useEffect(() => {
    return api.listener.onUpdateNotification(listenAllEvents, (notif) => {
      notifications.unshift(notif)
      setLastEvent(new Date())
    });
  });
  const [showError, setShowError] = useState<TalerErrorDetail>()
  return <div>
    {showError && <ErroDetailModal error={showError} onClose={(async () => { setShowError(undefined) })} />}
    {notifications.map((not) => {
      return (
        <details>
          <summary>{not.type}</summary>
          {(function () {
            switch (not.type) {
              case NotificationType.BalanceChange: {
                return <Fragment>
                  <dt>Transaction</dt>
                  <dd>
                    <a title={not.hintTransactionId} href={Pages.balanceTransaction({ tid: not.hintTransactionId })}>{not.hintTransactionId.substring(0, 10)}</a>
                  </dd>
                </Fragment>
              }
              case NotificationType.BackupOperationError: {
                return <Fragment>
                  <dt>Error</dt>
                  <dd>
                    <a href="#" onClick={(e) => { e.preventDefault(); setShowError(not.error) }}>{TalerErrorCode[not.error.code]}</a>
                  </dd>
                </Fragment>
              }
              case NotificationType.TransactionStateTransition: {
                return <Fragment>
                  <dt>Old state</dt>
                  <dd>
                    {not.oldTxState.major} - {not.oldTxState.minor ?? ""}
                  </dd>
                  <dt>New state</dt>
                  <dd>
                    {not.newTxState.major} - {not.newTxState.minor ?? ""}
                  </dd>
                  <dt>Transaction</dt>
                  <dd>
                    <a title={not.transactionId} href={Pages.balanceTransaction({ tid: not.transactionId })}>{not.transactionId.substring(0, 10)}</a>
                  </dd>
                  {not.errorInfo ? <Fragment>
                    <dt>Error</dt>
                    <dd>
                      <a href="#" onClick={(e) => {
                        e.preventDefault(); setShowError({
                          code: not.errorInfo!.code,
                          hint: not.errorInfo!.hint,
                          message: not.errorInfo!.message,
                        })
                      }}>{TalerErrorCode[not.errorInfo!.code]}</a>
                    </dd>
                  </Fragment> : undefined}
                  <dt>Experimental</dt>
                  <dd>
                    <pre>
                      {JSON.stringify(not.experimentalUserData, undefined, 2)}
                    </pre>
                  </dd>

                </Fragment>
              }
              case NotificationType.ExchangeStateTransition: {
                return <Fragment>
                  <dt>Exchange</dt>
                  <dd>
                    {not.exchangeBaseUrl}
                  </dd>
                  <dt>Entry status</dt>
                  <dd>
                    {not.newExchangeState.exchangeEntryStatus}
                  </dd>
                  <dt>Update status</dt>
                  <dd>
                    {not.newExchangeState.exchangeUpdateStatus}
                  </dd>
                  <dt>Tos status</dt>
                  <dd>
                    {not.newExchangeState.tosStatus}
                  </dd>
                </Fragment>
              }
              case NotificationType.TaskObservabilityEvent: {
                return <Fragment>
                  <dt>Task</dt>
                  <dd>
                    {not.taskId}
                  </dd>
                  <dt>Event</dt>
                  <dd>
                    {not.event.type}
                  </dd>
                  {(function () {
                    switch (not.event.type) {
                      case ObservabilityEventType.HttpFetchStart:
                      case ObservabilityEventType.HttpFetchFinishError:
                      case ObservabilityEventType.HttpFetchFinishSuccess: {
                        return <Fragment>
                          <dt>Request</dt>
                          <dd>{not.event.url}</dd>
                        </Fragment>
                      }
                      case ObservabilityEventType.DbQueryStart:
                      case ObservabilityEventType.DbQueryFinishSuccess:
                      case ObservabilityEventType.DbQueryFinishError: {
                        return <Fragment>
                          <dt>Location</dt>
                          <dd>{not.event.location}</dd>
                          <dt>Name</dt>
                          <dd>{not.event.name}</dd>
                        </Fragment>
                      }

                      case ObservabilityEventType.TaskStart:
                      case ObservabilityEventType.TaskStop:
                      case ObservabilityEventType.DeclareTaskDependency:
                      case ObservabilityEventType.TaskReset: {
                        return <Fragment>
                          <dt>Task</dt>
                          <dd>{not.event.taskId}</dd>
                        </Fragment>
                      }
                      case ObservabilityEventType.ShepherdTaskResult: {
                        return <Fragment>
                          <dt>result</dt>
                          <dd>{not.event.resultType}</dd>
                        </Fragment>

                      }
                      case ObservabilityEventType.CryptoStart:
                      case ObservabilityEventType.CryptoFinishSuccess:
                      case ObservabilityEventType.CryptoFinishError: {
                        return <Fragment>
                          <dt>operation</dt>
                          <dd>{not.event.operation}</dd>
                        </Fragment>
                      }
                      case ObservabilityEventType.RequestStart:
                      case ObservabilityEventType.RequestFinishSuccess:
                      case ObservabilityEventType.RequestFinishError: {
                        return <Fragment />
                      }
                    }
                  })()}
                </Fragment>
              }
              case NotificationType.RequestObservabilityEvent: {
                return <Fragment>
                  <dt>Operation</dt>
                  <dd>
                    {not.operation}
                  </dd>
                  <dt>Request</dt>
                  <dd>
                    {not.requestId}
                  </dd>
                  <dt>Event type</dt>
                  <dd>
                    {not.event.type}
                  </dd>
                  {(function () {
                    switch (not.event.type) {
                      case ObservabilityEventType.HttpFetchStart:
                      case ObservabilityEventType.HttpFetchFinishError:
                      case ObservabilityEventType.HttpFetchFinishSuccess: {
                        return <Fragment>
                          <dt>Request</dt>
                          <dd>{not.event.url}</dd>
                        </Fragment>
                      }
                      case ObservabilityEventType.DbQueryStart:
                      case ObservabilityEventType.DbQueryFinishSuccess:
                      case ObservabilityEventType.DbQueryFinishError: {
                        return <Fragment>
                          <dt>Location</dt>
                          <dd>{not.event.location}</dd>
                          <dt>Name</dt>
                          <dd>{not.event.name}</dd>
                        </Fragment>
                      }

                      case ObservabilityEventType.TaskStart:
                      case ObservabilityEventType.TaskStop:
                      case ObservabilityEventType.DeclareTaskDependency:
                      case ObservabilityEventType.TaskReset: {
                        return <Fragment>
                          <dt>Task</dt>
                          <dd>{not.event.taskId}</dd>
                        </Fragment>
                      }
                      case ObservabilityEventType.ShepherdTaskResult: {
                        return <Fragment>
                          <dt>result</dt>
                          <dd>{not.event.resultType}</dd>
                        </Fragment>

                      }
                      case ObservabilityEventType.CryptoStart:
                      case ObservabilityEventType.CryptoFinishSuccess:
                      case ObservabilityEventType.CryptoFinishError: {
                        return <Fragment>
                          <dt>operation</dt>
                          <dd>{not.event.operation}</dd>
                        </Fragment>
                      }
                      case ObservabilityEventType.RequestStart:
                      case ObservabilityEventType.RequestFinishSuccess:
                      case ObservabilityEventType.RequestFinishError: {
                        return <Fragment />
                      }
                    }
                  })()}

                </Fragment>
              }
            }
          })()}

        </details>
      );
    })}
  </div >
}

function ErroDetailModal({ error, onClose }: { error: TalerErrorDetail, onClose: () => void }): VNode {
  return <Modal title="Full detail" onClose={{
    onClick: onClose as any
  }}>
    <dl>
      <dt>Code</dt>
      <dd>{TalerErrorCode[error.code]} ({error.code})</dd>
      <dt>Hint</dt>
      <dd>{error.hint ?? "--"}</dd>
      <dt>Time</dt>
      <dd><Time
        timestamp={error.when}
        format="yyyy/MM/dd HH:mm:ss"
      /></dd>
    </dl>
    <pre>
      {JSON.stringify(error, undefined, 2)}
    </pre>
  </Modal>
}

export function ActiveTasksTable({ }: {}): VNode {
  const { i18n } = useTranslationContext()
  const listenAllEvents = Array.from<NotificationType>({ length: 1 });
  listenAllEvents.includes = () => true
  const api = useBackendContext();
  const state = useAsyncAsHook(() =>
    api.wallet.call(WalletApiOperation.GetActiveTasks, {}),
  );
  const [showError, setShowError] = useState<TalerErrorDetail>()
  const tasks = state && !state.hasError ? state.response.tasks : [];
  useEffect(() => {
    return api.listener.onUpdateNotification(listenAllEvents, (notif) => {
      state?.retry()
    });
  });
  return <Fragment>
    {showError && <ErroDetailModal error={showError} onClose={(async () => { setShowError(undefined) })} />}
    <table>
      <thead>
        <tr>
          <th>
            <i18n.Translate>Type</i18n.Translate>
          </th>
          <th>
            <i18n.Translate>Id</i18n.Translate>
          </th>
          <th>
            <i18n.Translate>Since</i18n.Translate>
          </th>
          <th>
            <i18n.Translate>Next try</i18n.Translate>
          </th>
          <th>
            <i18n.Translate>Error</i18n.Translate>
          </th>
          <th>
            <i18n.Translate>Transaction</i18n.Translate>
          </th>
        </tr>
      </thead>
      <tbody>
        {tasks.map((task) => {
          const [type, id] = task.id.split(":")
          return (
            <tr>
              <td>{type}</td>
              <td title={id}>{id.substring(0, 10)}</td>
              <td>
                <Time
                  timestamp={task.firstTry}
                  format="yyyy/MM/dd HH:mm:ss"
                />
              </td>
              <td>
                <Time
                  timestamp={task.nextTry}
                  format="yyyy/MM/dd HH:mm:ss"
                />
              </td>
              <td>{!task.lastError?.code ? "" : <a href="#" onClick={(e) => { e.preventDefault(); setShowError(task.lastError) }}>{TalerErrorCode[task.lastError.code]}</a>}</td>
              <td>
                {task.transaction ? <a title={task.transaction} href={Pages.balanceTransaction({ tid: task.transaction })}>{task.transaction.substring(0, 10)}</a> : "--"}
              </td>
            </tr>
          );
        })}
      </tbody>
    </table>
  </Fragment>
}