/*
 This file is part of GNU Taler
 (C) 2025 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 {
  assertUnreachable,
  ScopeInfo,
  NotificationType,
  decodeCrock,
  eddsaGetPublic,
  encodeCrock,
  MailboxConfiguration,
  TalerMailboxInstanceHttpClient,
  sha512,
  MailboxMessageRecord,
  TranslatedString,
  succeedOrValue,
  TalerProtocolTimestamp,
  TalerUris,
  TalerUriAction,
  TalerUri
} from "@gnu-taler/taler-util";
import { SafeHandler } from "../mui/handlers.js";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { BrowserFetchHttpLib, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js";
import {
  BoldLight,
  Centered,
  SmallText,
  LightText,
} 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 { Alert } from "../mui/Alert.js";
import { Button } from "../mui/Button.js";
import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js";
import { TextField } from "../mui/TextField.js";
import { TextFieldHandler } from "../mui/handlers.js";

interface Props {
  scope?: ScopeInfo;
  search?: boolean;
  onTalerUri: (url: string) => void;
}

export function MailboxPage({
  scope,
  search: showSearch,
  onTalerUri,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const api = useBackendContext();
  const [error, setError] = useState<TranslatedString | undefined>();
  const [search, setSearch] = useState<string>();
  // FIXME get from settings;
  const mailboxBaseUrl = "https://mailbox.gnunet.org";
  var mailboxClient: TalerMailboxInstanceHttpClient;
  var activeMailbox: MailboxConfiguration | undefined;
  const httpClient = new BrowserFetchHttpLib();
  mailboxClient = new TalerMailboxInstanceHttpClient(mailboxBaseUrl, httpClient);

  const state = useAsyncAsHook(async () => {
    const b = await api.wallet.call(WalletApiOperation.GetMailboxMessages, {});
    activeMailbox = await api.wallet.call(WalletApiOperation.GetMailbox, mailboxBaseUrl);

    var mailboxUrl;
    if (activeMailbox) {
    // FIXME put this into the mailbox config directly?
      const privKey = decodeCrock(activeMailbox.privateKey);
      const pubKey = eddsaGetPublic(privKey);
      mailboxUrl = activeMailbox.mailboxBaseUrl + '/' + encodeCrock(sha512(pubKey));
    }
    return {
      messages: b.messages,
      mailbox: activeMailbox,
      mailboxUrl: mailboxUrl,
    };
  }, [search]);

  const { pushAlertOnError } = useAlertContext();

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

  useEffect(() => {
    return api.listener.onUpdateNotification(
      [NotificationType.MailboxMessageAdded, NotificationType.MailboxMessageDeleted],
      state?.retry,
    );
  });
  if (state.hasError) {
    return (
      <ErrorAlertView
        error={alertFromError(
          i18n,
          i18n.str`Could not load the list of messages`,
          state,
        )}
      />
    );
  }
  const onInitializeMailbox = async () => {
    try {
      await api.wallet.call(WalletApiOperation.InitializeMailbox, mailboxBaseUrl);
      // FIXME the returned mailbox may have a payto URI set
      // In that case we need to display the option to pay AND
      // Properly check on reload if we can clear the property (and store the
      // updated mailbox struct)
      // https://bugs.gnunet.org/view.php?id=10605
      state?.retry();
    } catch (err) {
      setError(i18n.str`Unexpected error when trying to initialize mailbox: ${err}`);
    }
  };
  const onDeleteMessage = async (m: MailboxMessageRecord) => {
    try {
      await api.wallet.call(WalletApiOperation.DeleteMailboxMessage, { message: m });
    } catch (e) {
      setError(i18n.str`Unexpected error when trying to delete message: ${e}`);
    }
  };
  const onFetchMessages = async (mailbox?: MailboxConfiguration) => {
    try {
      if (!mailbox) {
        return;
      }
      await api.wallet.call(WalletApiOperation.RefreshMailbox, mailbox);
    } catch (err) {
      setError(i18n.str`Unexpected error when trying to fetch messages: ${err}`);
    }
   };
  return (
    <MessagesView
      search={{
        value: search ?? "",
        onInput: pushAlertOnError(async (d: string) => {
          setSearch(d);
        }),
      }}
      error={error}
      messages={state.response.messages}
      mailboxUrl={state.response.mailboxUrl}
      mailbox={state.response.mailbox}
      onFetchMessages={onFetchMessages}
      onDeleteMessage={onDeleteMessage}
      onInitializeMailbox={onInitializeMailbox}
      onTalerUri={onTalerUri}
    />
  );
}

interface MessageProps {
  message: MailboxMessageRecord;
  onDeleteMessage: (m: MailboxMessageRecord) => Promise<void>;
  onTalerUri: (url: string) => void;
}


function MailboxMessageLayout(props: MessageProps): VNode {
  const { i18n } = useTranslationContext();
  const uri = succeedOrValue(TalerUris.fromString(props.message.talerUri), undefined);
  function toDateString(t: TalerProtocolTimestamp) : string {
    if (t.t_s === "never") {
      return t.t_s;
    }
    return new Date(t.t_s * 1000).toLocaleString();
  }
  return (
    <Paper style={{ padding: 8 }}>
      <p>
      <span><i18n.Translate>Date</i18n.Translate>: {toDateString(props.message.downloadedAt)}</span>
         <SmallText style={{ marginTop: 5 }}>
          <i18n.Translate>URI</i18n.Translate>: {props.message.talerUri}
        </SmallText>
      </p>
      {uri &&
        <Button
          variant="contained"
          color="success"
          onClick={async () => {
            if (uri) props.onTalerUri(props.message.talerUri);
          }}
        >
          {(function (talerUri: TalerUri): VNode {
            switch (talerUri.type) {
              case TalerUriAction.Pay:
                return <i18n.Translate>Pay invoice</i18n.Translate>;
              case TalerUriAction.Withdraw:
                return (
                  <i18n.Translate>Withdrawal from bank</i18n.Translate>
                );
              case TalerUriAction.Refund:
                return <i18n.Translate>Claim refund</i18n.Translate>;
              case TalerUriAction.PayPull:
                return <i18n.Translate>Pay invoice</i18n.Translate>;
              case TalerUriAction.PayPush:
                return <i18n.Translate>Accept payment</i18n.Translate>;
              case TalerUriAction.PayTemplate:
                return <i18n.Translate>Complete order</i18n.Translate>;
              case TalerUriAction.Restore:
                return <i18n.Translate>Restore wallet</i18n.Translate>;
              case TalerUriAction.DevExperiment:
                return (
                  <i18n.Translate>Enable experiment</i18n.Translate>
                );
              case TalerUriAction.WithdrawExchange:
                return (
                  <i18n.Translate>
                    Withdraw from exchange
                  </i18n.Translate>
                );
              case TalerUriAction.AddExchange:
                return <i18n.Translate>Add exchange</i18n.Translate>;
              case TalerUriAction.WithdrawalTransferResult:
                return (
                  <i18n.Translate>Notify transaction</i18n.Translate>
                );
              case TalerUriAction.AddContact:
                return <i18n.Translate>Add contact</i18n.Translate>;
              default: {
                assertUnreachable(talerUri);
              }
            }
          })(uri)}
        </Button>
      }
      <Button variant="contained" onClick={() => { return props.onDeleteMessage(props.message)}} color="error">
        <i18n.Translate>Delete</i18n.Translate>
      </Button>
    </Paper>
  )

}

export function MessagesView({
  search,
  error,
  messages,
  mailboxUrl,
  mailbox,
  onFetchMessages,
  onDeleteMessage,
  onInitializeMailbox,
  onTalerUri,
}: {
  search: TextFieldHandler;
  error: TranslatedString | undefined;
  messages: MailboxMessageRecord[];
  mailboxUrl: string | undefined;
  mailbox: MailboxConfiguration | undefined;
  onFetchMessages: (mb?: MailboxConfiguration) => Promise<void>;
  onDeleteMessage: (m: MailboxMessageRecord) => Promise<void>;
  onInitializeMailbox: () => Promise<void>;
  onTalerUri: (url: string) => void;
}): VNode {
  const [showing, setShowing] = useState<boolean>();
  const { i18n } = useTranslationContext();
  async function copy(): Promise<void> {
    if (mailboxUrl) {
      navigator.clipboard.writeText(mailboxUrl);
    }
  }
  async function toggle(): Promise<void> {
    setShowing((s) => !s);
  }
  return (
    <Fragment>
      <section>
        <p>{error && <Alert severity="error">{error}</Alert>}</p>
        {mailboxUrl ? (
        <p>
          <Alert severity="info">
            <i18n.Translate>
              You may copy your mailbox URI from below and register it at <a href="https://taldir.gnunet.org">the Taler Directory</a> with your alias(es) to make it easier for others to find you.
            </i18n.Translate>
          {(showing) ? (
            <div>
              <SmallText style={{ marginTop: 5 }}>
                <LightText>
                  { mailboxUrl }
                </LightText>
              </SmallText>
              <Button onClick={copy as SafeHandler<void>}>
                <i18n.Translate>copy uri</i18n.Translate>
              </Button>
              <Button onClick={toggle as SafeHandler<void>}>
                <i18n.Translate>hide uri</i18n.Translate>
              </Button>
            </div>
          ) : (
            <div>
              <Button onClick={copy as SafeHandler<void>}>
                <i18n.Translate>copy uri</i18n.Translate>
              </Button>
               <Button onClick={toggle as SafeHandler<void>}>
                <i18n.Translate>show uri</i18n.Translate>
              </Button>
            </div>
          )}
          </Alert>
          <br/>
          <Centered style={{ margin: 10 }}>
            <Button variant="contained" onClick={() => { return onFetchMessages(mailbox) }}>
              <i18n.Translate>Fetch messages</i18n.Translate>
            </Button>
          </Centered>
        </p>
        ) : (
        <Centered style={{ margin: 10 }}>
          <p>
            <SmallText style={{ marginTop: 5 }}>
              <LightText>
                <i18n.Translate>Mailbox not initialized</i18n.Translate>
              </LightText>
            </SmallText>
          </p>
          <Button variant="contained" onClick={() => { return onInitializeMailbox() }}>
            <i18n.Translate>Initialize mailbox</i18n.Translate>
          </Button>
        </Centered>

        )}
        {(messages.length == 0) ? (
          <Centered style={{ marginTop: 20 }}>
            <BoldLight>
              <i18n.Translate>No messages yet.</i18n.Translate>
            </BoldLight>
          </Centered>
        ) :
         (
          <TextField
            label="Search"
            variant="filled"
            error={search.error}
            required
            fullWidth
            value={search.value}
            onChange={search.onInput}
          />
        )}
        <Grid item container columns={1} spacing={1} style={{ marginTop: 20 }}>
        {
          messages.map((m, i) => (
            <Grid item xs={1}>
              <MailboxMessageLayout
                message={m}
                onDeleteMessage={onDeleteMessage}
                onTalerUri={onTalerUri}
              />
            </Grid>
          ))
        }
        </Grid>
       </section>
    </Fragment>
  );
}


