/*
 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 {
  AbsoluteTime,
  AmountString,
  Amounts,
  Duration,
  TalerMerchantApi,
  TalerProtocolDuration,
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { format, isFuture } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useEffect, 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 { InputDate } from "../../../../components/form/InputDate.js";
import { InputDuration } from "../../../../components/form/InputDuration.js";
import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputLocation } from "../../../../components/form/InputLocation.js";
import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InputToggle } from "../../../../components/form/InputToggle.js";
import { InventoryProductForm } from "../../../../components/product/InventoryProductForm.js";
import { NonInventoryProductFrom } from "../../../../components/product/NonInventoryProductForm.js";
import { ProductList } from "../../../../components/product/ProductList.js";
import { useSessionContext } from "../../../../context/session.js";
import { usePreference } from "../../../../hooks/preference.js";
import { rate } from "../../../../utils/amount.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
import { WithId } from "../../../../declaration.js";

interface Props {
  onCreate: (d: TalerMerchantApi.PostOrderRequest) => void;
  onBack?: () => void;
  instanceConfig: InstanceConfig;
  instanceInventory: (TalerMerchantApi.ProductDetail & WithId)[];
}
interface InstanceConfig {
  use_stefan: boolean;
  default_pay_delay: TalerProtocolDuration;
  default_wire_transfer_delay: TalerProtocolDuration;
}

function with_defaults(
  config: InstanceConfig,
  _currency: string,
): Partial<Entity> {
  const defaultPayDeadline = Duration.fromTalerProtocolDuration(
    config.default_pay_delay,
  );
  const defaultWireDeadline = Duration.fromTalerProtocolDuration(
    config.default_wire_transfer_delay,
  );

  return {
    inventoryProducts: {},
    products: [],
    pricing: {},
    payments: {
      max_fee: undefined,
      createToken: true,
      pay_deadline: defaultPayDeadline,
      refund_deadline: defaultPayDeadline,
      wire_transfer_deadline: defaultWireDeadline,
    },
    shipping: {},
    extra: {},
  };
}

interface ProductAndQuantity {
  product: TalerMerchantApi.ProductDetail & WithId;
  quantity: number;
}
export interface ProductMap {
  [id: string]: ProductAndQuantity;
}

interface Pricing {
  products_price: string;
  order_price: string;
  summary: string;
}
interface Shipping {
  delivery_date?: Date;
  delivery_location?: TalerMerchantApi.Location;
  fullfilment_url?: string;
}
interface Payments {
  refund_deadline: Duration;
  pay_deadline: Duration;
  wire_transfer_deadline: Duration;
  auto_refund_deadline: Duration;
  max_fee?: string;
  createToken: boolean;
  minimum_age?: number;
}
interface Entity {
  inventoryProducts: ProductMap;
  products: TalerMerchantApi.Product[];
  pricing: Partial<Pricing>;
  payments: Partial<Payments>;
  shipping: Partial<Shipping>;
  extra: Record<string, string>;
}

export function CreatePage({
  onCreate,
  onBack,
  instanceConfig,
  instanceInventory,
}: Props): VNode {
  const { config } = useSessionContext();
  const instance_default = with_defaults(instanceConfig, config.currency);
  const [value, valueHandler] = useState(instance_default);
  const zero = Amounts.zeroOfCurrency(config.currency);
  const [pref, updatePrefs] = usePreference();
  const inventoryList = Object.values(value.inventoryProducts || {});
  const productList = Object.values(value.products || {});

  const { i18n } = useTranslationContext();

  const parsedPrice = !value.pricing?.order_price
    ? undefined
    : Amounts.parse(value.pricing.order_price);

  const errors = undefinedIfEmpty<FormErrors<Entity>>({
    pricing: undefinedIfEmpty({
      summary: !value.pricing?.summary ? i18n.str`Required` : undefined,
      order_price: !value.pricing?.order_price
        ? i18n.str`Required`
        : !parsedPrice
          ? i18n.str`Invalid`
          : Amounts.isZero(parsedPrice)
            ? i18n.str`Must be greater than 0`
            : undefined,
    }),
    payments: undefinedIfEmpty({
      refund_deadline: !value.payments?.refund_deadline
        ? undefined
        : value.payments.pay_deadline &&
            Duration.cmp(
              value.payments.refund_deadline,
              value.payments.pay_deadline,
            ) === -1
          ? i18n.str`Refund deadline can't be before pay deadline`
          : value.payments.wire_transfer_deadline &&
              Duration.cmp(
                value.payments.wire_transfer_deadline,
                value.payments.refund_deadline,
              ) === -1
            ? i18n.str`Wire transfer deadline can't be before refund deadline`
            : undefined,
      pay_deadline: !value.payments?.pay_deadline
        ? i18n.str`Required`
        : value.payments.wire_transfer_deadline &&
            Duration.cmp(
              value.payments.wire_transfer_deadline,
              value.payments.pay_deadline,
            ) === -1
          ? i18n.str`Wire transfer deadline can't be before pay deadline`
          : undefined,
      wire_transfer_deadline: !value.payments?.wire_transfer_deadline
        ? i18n.str`Required`
        : undefined,
      auto_refund_deadline: !value.payments?.auto_refund_deadline
        ? undefined
        : !value.payments?.refund_deadline
          ? i18n.str`Must have a refund deadline`
          : Duration.cmp(
                value.payments.refund_deadline,
                value.payments.auto_refund_deadline,
              ) == -1
            ? i18n.str`Auto refund can't be after refund deadline`
            : undefined,
    }),
    shipping: undefinedIfEmpty({
      delivery_date: !value.shipping?.delivery_date
        ? undefined
        : !isFuture(value.shipping.delivery_date)
          ? i18n.str`Must be in the future`
          : undefined,
    }),
  });
  const hasErrors = errors !== undefined;

  const submit = (): void => {
    const order = value;
    const price = order.pricing?.order_price as AmountString | undefined;
    const summary = order.pricing?.summary;
    if (!value.payments) return;
    if (!value.shipping) return;
    if (!price || ! summary) return;

    const request: TalerMerchantApi.PostOrderRequest = {
      order: {
        amount: price,
        summary: summary,
        products: productList,
        extra: undefinedIfEmpty(value.extra),
        pay_deadline: AbsoluteTime.toProtocolTimestamp(
          AbsoluteTime.addDuration(
            AbsoluteTime.now(),
            value.payments.pay_deadline!,
          ),
        ),
        wire_transfer_deadline: AbsoluteTime.toProtocolTimestamp(
          AbsoluteTime.addDuration(
            AbsoluteTime.now(),
            value.payments.wire_transfer_deadline!,
          ),
        ),
        refund_deadline: AbsoluteTime.toProtocolTimestamp(
          AbsoluteTime.addDuration(
            AbsoluteTime.now(),
            value.payments.refund_deadline!,
          ),
        ),
        auto_refund: value.payments.auto_refund_deadline
          ? Duration.toTalerProtocolDuration(
              value.payments.auto_refund_deadline,
            )
          : undefined,
        max_fee: value.payments.max_fee as AmountString,
        delivery_date: value.shipping.delivery_date
          ? { t_s: value.shipping.delivery_date.getTime() / 1000 }
          : undefined,
        delivery_location: value.shipping.delivery_location,
        fulfillment_url: value.shipping.fullfilment_url,
        minimum_age: value.payments.minimum_age,
      },
      inventory_products: inventoryList.map((p) => ({
        product_id: p.product.id,
        quantity: p.quantity,
      })),
      create_token: value.payments.createToken,
    };

    onCreate(request);
  };

  const addProductToTheInventoryList = (
    product: TalerMerchantApi.ProductDetail & WithId,
    quantity: number,
  ) => {
    valueHandler((v) => {
      const inventoryProducts = { ...v.inventoryProducts };
      inventoryProducts[product.id] = { product, quantity };
      return { ...v, inventoryProducts };
    });
  };

  const removeProductFromTheInventoryList = (id: string) => {
    valueHandler((v) => {
      const inventoryProducts = { ...v.inventoryProducts };
      delete inventoryProducts[id];
      return { ...v, inventoryProducts };
    });
  };

  const addNewProduct = async (product: TalerMerchantApi.Product) => {
    return valueHandler((v) => {
      const products = v.products ? [...v.products, product] : [];
      return { ...v, products };
    });
  };

  const removeFromNewProduct = (index: number) => {
    valueHandler((v) => {
      const products = v.products ? [...v.products] : [];
      products.splice(index, 1);
      return { ...v, products };
    });
  };

  const [editingProduct, setEditingProduct] = useState<
    TalerMerchantApi.Product | undefined
  >(undefined);

  const totalPriceInventory = inventoryList.reduce((prev, cur) => {
    const p = Amounts.parseOrThrow(cur.product.price);
    return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount;
  }, zero);

  const totalPriceProducts = productList.reduce((prev, cur) => {
    if (!cur.price) return zero;
    const p = Amounts.parseOrThrow(cur.price);
    return Amounts.add(prev, Amounts.mult(p, cur.quantity ?? 0).amount).amount;
  }, zero);

  const hasProducts = inventoryList.length > 0 || productList.length > 0;
  const totalPrice = Amounts.add(totalPriceInventory, totalPriceProducts);

  const totalAsString = Amounts.stringify(totalPrice.amount);
  const allProducts = productList.concat(inventoryList.map(asProduct));

  const [newField, setNewField] = useState("");

  useEffect(() => {
    valueHandler((v) => {
      return {
        ...v,
        pricing: {
          ...v.pricing,
          products_price: hasProducts ? totalAsString : undefined,
          order_price: hasProducts ? totalAsString : undefined,
        },
      };
    });
  }, [hasProducts, totalAsString]);

  const discountOrRise = rate(
    parsedPrice ?? Amounts.zeroOfCurrency(config.currency),
    totalPrice.amount,
  );

  const minAgeByProducts = inventoryList.reduce(
    (cur, prev) =>
      !prev.product.minimum_age || cur > prev.product.minimum_age
        ? cur
        : prev.product.minimum_age,
    0,
  );

  // if there is no default pay deadline
  const noDefault_payDeadline =
    !instance_default.payments || !instance_default.payments.pay_deadline;
  // and there is no default wire deadline
  const noDefault_wireDeadline =
    !instance_default.payments ||
    !instance_default.payments.wire_transfer_deadline;
  // user required to set the taler options
  const requiresSomeTalerOptions =
    noDefault_payDeadline || noDefault_wireDeadline;

  return (
    <div>
      <section class="section is-main-section">
        <div class="tabs is-toggle is-fullwidth is-small">
          <ul>
            <li
              class={!pref.advanceOrderMode ? "is-active" : ""}
              onClick={() => {
                updatePrefs("advanceOrderMode", false);
              }}
            >
              <a>
                <span>
                  <i18n.Translate>Simple</i18n.Translate>
                </span>
              </a>
            </li>
            <li
              class={pref.advanceOrderMode ? "is-active" : ""}
              onClick={() => {
                updatePrefs("advanceOrderMode", true);
              }}
            >
              <a>
                <span>
                  <i18n.Translate>Advanced</i18n.Translate>
                </span>
              </a>
            </li>
          </ul>
        </div>
        <div class="columns">
          <div class="column" />
          <div class="column is-four-fifths">
            {/* // FIXME: translating plural singular */}
            <InputGroup
              name="inventory_products"
              label={i18n.str`Manage products in order`}
              alternative={
                allProducts.length > 0 && (
                  <p>
                    <i18n.Translate>
                      {allProducts.length} products with a total price of{" "}
                      {totalAsString}.
                    </i18n.Translate>
                  </p>
                )
              }
              tooltip={i18n.str`Manage list of products in the order.`}
            >
              <InventoryProductForm
                currentProducts={value.inventoryProducts || {}}
                onAddProduct={addProductToTheInventoryList}
                inventory={instanceInventory}
              />

              {pref.advanceOrderMode && (
                <NonInventoryProductFrom
                  productToEdit={editingProduct}
                  onAddProduct={(p) => {
                    setEditingProduct(undefined);
                    return addNewProduct(p);
                  }}
                />
              )}

              {allProducts.length > 0 && (
                <ProductList
                  list={allProducts}
                  actions={[
                    {
                      name: i18n.str`Remove`,
                      tooltip: i18n.str`Remove this product from the order.`,
                      handler: (e, index) => {
                        if (e.product_id) {
                          removeProductFromTheInventoryList(e.product_id);
                        } else {
                          removeFromNewProduct(index);
                          setEditingProduct(e);
                        }
                      },
                    },
                  ]}
                />
              )}
            </InputGroup>

            <FormProvider<Entity>
              errors={errors}
              object={value}
              valueHandler={valueHandler as any}
            >
              {hasProducts ? (
                <Fragment>
                  <InputCurrency
                    name="pricing.products_price"
                    label={i18n.str`Total price`}
                    readonly
                    tooltip={i18n.str`Total product price added up`}
                  />
                  <InputCurrency
                    name="pricing.order_price"
                    label={i18n.str`Total price`}
                    addonAfter={
                      discountOrRise > 0 &&
                      (discountOrRise < 1
                        ? `discount of %${Math.round(
                            (1 - discountOrRise) * 100,
                          )}`
                        : `rise of %${Math.round((discountOrRise - 1) * 100)}`)
                    }
                    tooltip={i18n.str`Amount to be paid by the customer`}
                  />
                </Fragment>
              ) : (
                <InputCurrency
                  name="pricing.order_price"
                  label={i18n.str`Order price`}
                  tooltip={i18n.str`Final order price`}
                />
              )}

              <Input
                name="pricing.summary"
                inputType="multiline"
                label={i18n.str`Summary`}
                tooltip={i18n.str`Title of the order to be shown to the customer`}
              />

              {pref.advanceOrderMode && (
                <InputGroup
                  name="shipping"
                  label={i18n.str`Shipping and Fulfillment`}
                  initialActive
                >
                  <InputDate
                    name="shipping.delivery_date"
                    label={i18n.str`Delivery date`}
                    tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`}
                  />
                  {value.shipping?.delivery_date && (
                    <InputGroup
                      name="shipping.delivery_location"
                      label={i18n.str`Location`}
                      tooltip={i18n.str`Address where the products will be delivered`}
                    >
                      <InputLocation name="shipping.delivery_location" />
                    </InputGroup>
                  )}
                  <Input
                    name="shipping.fullfilment_url"
                    label={i18n.str`Fulfillment URL`}
                    tooltip={i18n.str`URL to which the user will be redirected after successful payment.`}
                  />
                </InputGroup>
              )}

              {(pref.advanceOrderMode || requiresSomeTalerOptions) && (
                <InputGroup
                  name="payments"
                  label={i18n.str`Taler payment options`}
                  tooltip={i18n.str`Override default Taler payment settings for this order`}
                >
                  {(pref.advanceOrderMode || noDefault_payDeadline) && (
                    <InputDuration
                      name="payments.pay_deadline"
                      label={i18n.str`Payment time`}
                      help={
                        <DeadlineHelp duration={value.payments?.pay_deadline} />
                      }
                      withForever
                      withoutClear
                      tooltip={i18n.str`Time for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline. Time start to run after the order is created.`}
                      side={
                        <span>
                          <button
                            class="button"
                            onClick={() => {
                              const c = {
                                ...value,
                                payments: {
                                  ...(value.payments ?? {}),
                                  pay_deadline:
                                    instance_default.payments?.pay_deadline,
                                },
                              };
                              valueHandler(c);
                            }}
                          >
                            <i18n.Translate>Default</i18n.Translate>
                          </button>
                        </span>
                      }
                    />
                  )}
                  {pref.advanceOrderMode && (
                    <InputDuration
                      name="payments.refund_deadline"
                      label={i18n.str`Refund time`}
                      help={
                        <DeadlineHelp
                          duration={value.payments?.refund_deadline}
                        />
                      }
                      withForever
                      withoutClear
                      tooltip={i18n.str`Time while the order can be refunded by the merchant. Time starts after the order is created.`}
                      side={
                        <span>
                          <button
                            class="button"
                            onClick={() => {
                              valueHandler({
                                ...value,
                                payments: {
                                  ...(value.payments ?? {}),
                                  refund_deadline:
                                    instance_default.payments?.refund_deadline,
                                },
                              });
                            }}
                          >
                            <i18n.Translate>Default</i18n.Translate>
                          </button>
                        </span>
                      }
                    />
                  )}
                  {(pref.advanceOrderMode || noDefault_wireDeadline) && (
                    <InputDuration
                      name="payments.wire_transfer_deadline"
                      label={i18n.str`Wire transfer time`}
                      help={
                        <DeadlineHelp
                          duration={value.payments?.wire_transfer_deadline}
                        />
                      }
                      withoutClear
                      withForever
                      tooltip={i18n.str`Time for the exchange to make the wire transfer. Time starts after the order is created.`}
                      side={
                        <span>
                          <button
                            class="button"
                            onClick={() => {
                              valueHandler({
                                ...value,
                                payments: {
                                  ...(value.payments ?? {}),
                                  wire_transfer_deadline:
                                    instance_default.payments
                                      ?.wire_transfer_deadline,
                                },
                              });
                            }}
                          >
                            <i18n.Translate>Default</i18n.Translate>
                          </button>
                        </span>
                      }
                    />
                  )}
                  {pref.advanceOrderMode && (
                    <InputDuration
                      name="payments.auto_refund_deadline"
                      label={i18n.str`Auto-refund time`}
                      help={
                        <DeadlineHelp
                          duration={value.payments?.auto_refund_deadline}
                        />
                      }
                      tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
                      withForever
                    />
                  )}

                  {pref.advanceOrderMode && (
                    <InputCurrency
                      name="payments.max_fee"
                      label={i18n.str`Maximum fee`}
                      tooltip={i18n.str`Maximum fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`}
                    />
                  )}
                  {pref.advanceOrderMode && (
                    <InputToggle
                      name="payments.createToken"
                      label={i18n.str`Create token`}
                      tooltip={i18n.str`If the order ID is easy to guess the token will prevent user to steal orders from others.`}
                    />
                  )}
                  {pref.advanceOrderMode && (
                    <InputNumber
                      name="payments.minimum_age"
                      label={i18n.str`Minimum age required`}
                      tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`}
                      help={
                        minAgeByProducts > 0
                          ? i18n.str`Min age defined by the producs is ${minAgeByProducts}`
                          : i18n.str`No product with age restriction in this order`
                      }
                    />
                  )}
                </InputGroup>
              )}

              {pref.advanceOrderMode && (
                <InputGroup
                  name="extra"
                  label={i18n.str`Additional information`}
                  tooltip={i18n.str`Custom information to be included in the contract for this order.`}
                >
                  {Object.keys(value.extra ?? {}).map((key, idx) => {
                    return (
                      <Input
                        name={`extra.${key}`}
                        key={String(idx)}
                        inputType="multiline"
                        label={key}
                        tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`}
                        side={
                          <button
                            class="button"
                            onClick={(e) => {
                              if (
                                value.extra &&
                                value.extra[key] !== undefined
                              ) {
                                delete value.extra[key];
                              }
                              valueHandler({
                                ...value,
                              });
                              e.preventDefault();
                            }}
                          >
                            remove
                          </button>
                        }
                      />
                    );
                  })}
                  <div class="field is-horizontal">
                    <div class="field-label is-normal">
                      <label class="label">
                        <i18n.Translate>Custom field name</i18n.Translate>
                        <span
                          class="icon has-tooltip-right"
                          data-tooltip={"new extra field"}
                        >
                          <i class="mdi mdi-information" />
                        </span>
                      </label>
                    </div>
                    <div class="field-body is-flex-grow-3">
                      <div class="field">
                        <p class="control">
                          <input
                            class="input "
                            value={newField}
                            onChange={(e) => setNewField(e.currentTarget.value)}
                          />
                        </p>
                      </div>
                    </div>
                    <button
                      class="button"
                      onClick={(e) => {
                        setNewField("");
                        valueHandler({
                          ...value,
                          extra: {
                            ...(value.extra ?? {}),
                            [newField]: "",
                          },
                        });
                        e.preventDefault();
                      }}
                    >
                      <i18n.Translate>Add</i18n.Translate>
                    </button>
                  </div>
                </InputGroup>
              )}
            </FormProvider>

            <div class="buttons is-right mt-5">
              {onBack && (
                <button class="button" onClick={onBack}>
                  <i18n.Translate>Cancel</i18n.Translate>
                </button>
              )}
              <button
                class="button is-success"
                onClick={submit}
                disabled={hasErrors}
              >
                <i18n.Translate>Confirm</i18n.Translate>
              </button>
            </div>
          </div>
          <div class="column" />
        </div>
      </section>
    </div>
  );
}

function asProduct(p: ProductAndQuantity): TalerMerchantApi.Product {
  return {
    product_id: p.product.id,
    image: p.product.image,
    price: p.product.price,
    unit: p.product.unit,
    quantity: p.quantity,
    description: p.product.description,
    taxes: p.product.taxes,
  };
}

function DeadlineHelp({ duration }: { duration?: Duration }): VNode {
  const { i18n } = useTranslationContext();
  const [now, setNow] = useState(AbsoluteTime.now());
  useEffect(() => {
    const iid = setInterval(() => {
      setNow(AbsoluteTime.now());
    }, 60 * 1000);
    return () => {
      clearInterval(iid);
    };
  });
  if (!duration) return <i18n.Translate>Disabled</i18n.Translate>;
  const when = AbsoluteTime.addDuration(now, duration);
  if (when.t_ms === "never")
    return <i18n.Translate>No deadline</i18n.Translate>;
  return (
    <i18n.Translate>
      Deadline at {format(when.t_ms, "dd/MM/yy HH:mm")}
    </i18n.Translate>
  );
}
