/*
 This file is part of GNU Taler
 (C) 2021-2024 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/>
 */

/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */

import {
  AccessToken,
  Amounts,
  AmountString,
  HttpStatusCode,
  MerchantContractVersion,
  TalerErrorCode,
  TalerMerchantApi,
} from "@gnu-taler/taler-util";
import {
  ButtonBetterBulma,
  LocalNotificationBannerBulma,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { Fragment, h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks";
import {
  FormErrors,
  FormProvider,
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { HtmlPersonaFlag } from "../../../../components/menu/SideBar.js";
import { ConfirmModal } from "../../../../components/modal/index.js";
import { useSessionContext } from "../../../../context/session.js";
import { WithId } from "../../../../declaration.js";
import {
  datetimeFormatForSettings,
  UIElement,
  usePreference,
} from "../../../../hooks/preference.js";
import { mergeRefunds } from "../../../../utils/amount.js";
import { getOrderAmountAndWirefee } from "../details/DetailPage.js";
import { OrderListSection } from "./index.js";

const TALER_SCREEN_ID = 48;

type Entity = TalerMerchantApi.OrderHistoryEntry & WithId;
interface Props {
  orders: Entity[];
  onRefund: (value: Entity) => void;
  onCreate: () => void;
  section?: OrderListSection;
  onSelect: (order: Entity) => void;
  onLoadMoreBefore?: () => void;
  onLoadMoreAfter?: () => void;
}

export function CardTable({
  orders,
  section,
  onCreate,
  onRefund,
  onSelect,
  onLoadMoreAfter,
  onLoadMoreBefore,
}: Props): VNode {
  const [rowSelection, rowSelectionHandler] = useState<string[]>([]);

  const { i18n } = useTranslationContext();

  return (
    <div class="card has-table">
      <header class="card-header">
        <p class="card-header-title">
          <span class="icon">
            <i class="mdi mdi-cash-register" />
          </span>
          <i18n.Translate>Orders</i18n.Translate>
        </p>

        <div class="card-header-icon" aria-label="more options" />

        <HtmlPersonaFlag
          htmlElement="div"
          point={UIElement.action_createOrderManually}
          class="card-header-icon"
          aria-label="more options"
        >
          <span class="has-tooltip-left" data-tooltip={i18n.str`Create order`}>
            <button
              class="button is-info"
              type="button"
              accessKey="+"
              onClick={onCreate}
            >
              <span class="icon is-small">
                <i class="mdi mdi-plus mdi-36px" />
              </span>
            </button>
          </span>
        </HtmlPersonaFlag>
      </header>
      <div class="card-content">
        <div class="b-table has-pagination">
          <div class="table-wrapper has-mobile-cards">
            {orders.length > 0 ? (
              <Table
                instances={orders}
                onSelect={onSelect}
                onRefund={onRefund}
                section={section}
                rowSelection={rowSelection}
                rowSelectionHandler={rowSelectionHandler}
                onLoadMoreAfter={onLoadMoreAfter}
                onLoadMoreBefore={onLoadMoreBefore}
              />
            ) : (
              <EmptyTable />
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
interface TableProps {
  rowSelection: string[];
  instances: Entity[];
  onRefund: (id: Entity) => void;
  onSelect: (id: Entity) => void;
  rowSelectionHandler: StateUpdater<string[]>;
  onLoadMoreBefore?: () => void;
  onLoadMoreAfter?: () => void;
  section?: OrderListSection;
}

function Table({
  instances,
  section,
  onSelect,
  onRefund,
  onLoadMoreAfter,
  onLoadMoreBefore,
}: TableProps): VNode {
  const { i18n } = useTranslationContext();
  const [settings] = usePreference();
  const { state: session, lib } = useSessionContext();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();
  const copyUrl = safeFunctionHandler((token: AccessToken, id: string) =>
    lib.instance.getOrderDetails(token, id),
  );
  copyUrl.onSuccess = (success) => {
    copyToClipboard(success.order_status_url);
  };
  copyUrl.onFail = (fail) => {
    switch (fail.case) {
      case TalerErrorCode.MERCHANT_GENERIC_INSTANCE_UNKNOWN:
        return i18n.str`The instance doesn't exist`;
      case TalerErrorCode.MERCHANT_GENERIC_ORDER_UNKNOWN:
        return i18n.str`The order doesn't exist`;
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized.`;
    }
  };
  return (
    <Fragment>
      <LocalNotificationBannerBulma notification={notification} />

      <div class="table-container">
        {onLoadMoreBefore && (
          <button class="button is-fullwidth" onClick={onLoadMoreBefore}>
            <i18n.Translate>Load first page</i18n.Translate>
          </button>
        )}
        <table class="table is-striped is-hoverable is-fullwidth">
          <thead>
            <tr>
              <th style={{ minWidth: 100 }}>
                <i18n.Translate>Date</i18n.Translate>
              </th>
              <th style={{ minWidth: 100 }}>
                <i18n.Translate>Amount</i18n.Translate>
              </th>
              {section === OrderListSection.REFUNDED ? (
                <th style={{ minWidth: 100 }}>
                  <i18n.Translate>Refunded</i18n.Translate>
                </th>
              ) : undefined}

              <th style={{ minWidth: 400 }}>
                <i18n.Translate>Summary</i18n.Translate>
              </th>
              <th style={{ minWidth: 50 }} />
            </tr>
          </thead>
          <tbody>
            {instances.map((i) => {
              return (
                <tr key={i.id}>
                  <td
                    onClick={(): void => onSelect(i)}
                    style={{ cursor: "pointer" }}
                  >
                    {i.timestamp.t_s === "never"
                      ? i18n.str`Never`
                      : format(
                          new Date(i.timestamp.t_s * 1000),
                          datetimeFormatForSettings(settings),
                        )}
                  </td>
                  <td
                    onClick={(): void => onSelect(i)}
                    style={{ cursor: "pointer" }}
                  >
                    {i.amount}
                  </td>
                  {section === OrderListSection.REFUNDED ? (
                    <td
                      onClick={(): void => onSelect(i)}
                      style={{ cursor: "pointer" }}
                    >
                      {i.refund_amount}
                    </td>
                  ) : undefined}
                  <td
                    onClick={(): void => onSelect(i)}
                    style={{ cursor: "pointer" }}
                  >
                    {i.summary}
                  </td>
                  <td class="is-actions-cell right-sticky">
                    <div class="buttons is-right">
                      {i.refundable && (
                        <button
                          class="button is-small is-danger jb-modal"
                          type="button"
                          onClick={(): void => onRefund(i)}
                        >
                          <i18n.Translate>Refund</i18n.Translate>
                        </button>
                      )}
                      {!i.paid && (
                        <ButtonBetterBulma
                          class="button is-small is-info jb-modal"
                          type="button"
                          onClick={
                            !session.token
                              ? copyUrl
                              : copyUrl.withArgs(session.token, i.id)
                          }
                        >
                          <i18n.Translate>copy url</i18n.Translate>
                        </ButtonBetterBulma>
                      )}
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {onLoadMoreAfter && (
          <button
            type="button"
            class="button is-fullwidth"
            data-tooltip={i18n.str`Load more orders after the last one`}
            onClick={onLoadMoreAfter}
          >
            <i18n.Translate>Load next page</i18n.Translate>
          </button>
        )}
      </div>
    </Fragment>
  );
}

function EmptyTable(): VNode {
  const { i18n } = useTranslationContext();
  return (
    <div class="content has-text-grey has-text-centered">
      <p>
        <span class="icon is-large">
          <i class="mdi mdi-magnify mdi-48px" />
        </span>
      </p>
      <p>
        <i18n.Translate>
          No orders have been found matching your query!
        </i18n.Translate>
      </p>
    </div>
  );
}

interface RefundModalProps {
  onCancel: () => void;
  onConfirmed: () => void;
  order: TalerMerchantApi.MerchantOrderStatusResponse;
  id: string;
}

export function RefundModal({
  order,
  id,
  onCancel,
  onConfirmed,
}: RefundModalProps): VNode {
  type State = { mainReason?: string; description?: string; refund?: string };
  const [form, setValue] = useState<State>({});
  const [settings] = usePreference();
  const { i18n } = useTranslationContext();
  // const [errors, setErrors] = useState<FormErrors<State>>({});
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();
  const { state: session, lib } = useSessionContext();

  let amount: AmountString | undefined;
  if (order.order_status === "paid") {
    const orderam = getOrderAmountAndWirefee(order);
    amount =
      typeof orderam === "string"
        ? undefined
        : Amounts.stringify(orderam.amount);
  }

  const refunds = (
    order.order_status === "paid" ? order.refund_details : []
  ).reduce(mergeRefunds, []);

  const { config } = useSessionContext();
  const totalRefunded = refunds
    .map((r) => r.amount)
    .reduce(
      (p, c) => Amounts.add(p, Amounts.parseOrThrow(c)).amount,
      Amounts.zeroOfCurrency(config.currency),
    );
  const orderPrice =
    order.order_status === "paid" ? Amounts.parseOrThrow(amount!) : undefined;
  const totalRefundable = !orderPrice
    ? Amounts.zeroOfCurrency(totalRefunded.currency)
    : refunds.length
      ? Amounts.sub(orderPrice, totalRefunded).amount
      : orderPrice;

  const isRefundable = Amounts.isNonZero(totalRefundable);
  const duplicatedText = i18n.str`Duplicated`;

  const errors: FormErrors<State> = {
    mainReason: !form.mainReason ? i18n.str`Required` : undefined,
    description:
      !form.description && form.mainReason !== duplicatedText
        ? i18n.str`Required`
        : undefined,
    refund: !form.refund
      ? i18n.str`Required`
      : !Amounts.parse(form.refund)
        ? i18n.str`Invalid`
        : Amounts.cmp(totalRefundable, Amounts.parse(form.refund)!) === -1
          ? i18n.str`This value exceeds the refundable amount.`
          : undefined,
  };
  const hasErrors = Object.keys(errors).some(
    (k) => (errors as Record<string, unknown>)[k] !== undefined,
  );

  const req: TalerMerchantApi.RefundRequest | undefined = !form.refund
    ? undefined
    : {
        refund: Amounts.stringify(
          Amounts.add(Amounts.parse(form.refund)!, totalRefunded).amount,
        ),
        reason:
          form.description === undefined
            ? form.mainReason || ""
            : `${form.mainReason}: ${form.description}`,
      };

  const refund = safeFunctionHandler(
    (token: AccessToken, id: string, request: TalerMerchantApi.RefundRequest) =>
      lib.instance.addRefund(token, id, request),
    !session.token || !req ? undefined : [session.token, id, req],
  );
  refund.onSuccess = onConfirmed;
  refund.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized`;
      case HttpStatusCode.Forbidden:
        return i18n.str`Forbidden`;
      case HttpStatusCode.NotFound:
        return i18n.str`Not found`;
      case HttpStatusCode.Conflict:
        return i18n.str`Conflict`;
      case HttpStatusCode.Gone:
        return i18n.str`Gone`;
      case HttpStatusCode.UnavailableForLegalReasons:
        return i18n.str`There are pending KYC requirements.`;
    }
  };
  //FIXME: parameters in the translation
  return (
    <ConfirmModal
      description={i18n.str`refund`}
      label={i18n.str`Confirm`}
      danger
      active
      onCancel={onCancel}
      confirm={refund}
    >
      <LocalNotificationBannerBulma notification={notification} />
      {refunds.length > 0 && (
        <div class="columns">
          <div class="column is-12">
            <InputGroup
              name="asd"
              label={`${Amounts.stringify(totalRefunded)} was already refunded`}
            >
              <table class="table is-fullwidth">
                <thead>
                  <tr>
                    <th>
                      <i18n.Translate>Date</i18n.Translate>
                    </th>
                    <th>
                      <i18n.Translate>Amount</i18n.Translate>
                    </th>
                    <th>
                      <i18n.Translate>Reason</i18n.Translate>
                    </th>
                  </tr>
                </thead>
                <tbody>
                  {refunds.map((r) => {
                    return (
                      <tr key={r.timestamp.t_s}>
                        <td>
                          {r.timestamp.t_s === "never"
                            ? i18n.str`Never`
                            : format(
                                new Date(r.timestamp.t_s * 1000),
                                datetimeFormatForSettings(settings),
                              )}
                        </td>
                        <td>{r.amount}</td>
                        <td>{r.reason}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </InputGroup>
          </div>
        </div>
      )}

      {isRefundable && (
        <FormProvider<State>
          errors={errors}
          object={form}
          valueHandler={(d) => setValue(d)}
        >
          <InputCurrency<State>
            name="refund"
            label={i18n.str`Refund`}
            tooltip={i18n.str`Amount to be refunded`}
          >
            <i18n.Translate>Max refundable:</i18n.Translate>{" "}
            {Amounts.stringify(totalRefundable)}
          </InputCurrency>
          <InputSelector
            name="mainReason"
            label={i18n.str`Reason`}
            values={[
              i18n.str`Choose one...`,
              duplicatedText,
              i18n.str`Requested by the customer`,
              i18n.str`Other`,
            ]}
            tooltip={i18n.str`Why this order is being refunded`}
          />
          {form.mainReason && form.mainReason !== duplicatedText ? (
            <Input<State>
              label={i18n.str`Description`}
              name="description"
              tooltip={i18n.str`More information to give context`}
            />
          ) : undefined}
        </FormProvider>
      )}
    </ConfirmModal>
  );
}

async function copyToClipboard(text: string): Promise<void> {
  return navigator.clipboard.writeText(text);
}
