/*
 This file is part of GNU Taler
 (C) 2021-2023 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, Amounts, Duration, TalerProtocolDuration } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { format, isFuture } from "date-fns";
import { ComponentChildren, 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 { useConfigContext } from "../../../../context/config.js";
import { MerchantBackend, WithId } from "../../../../declaration.js";
import { useSettings } from "../../../../hooks/useSettings.js";
import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
import { rate } from "../../../../utils/amount.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";

interface Props {
  onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void;
  onBack?: () => void;
  instanceConfig: InstanceConfig;
  instanceInventory: (MerchantBackend.Products.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: MerchantBackend.Products.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?: MerchantBackend.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: MerchantBackend.Product[];
  pricing: Partial<Pricing>;
  payments: Partial<Payments>;
  shipping: Partial<Shipping>;
  extra: Record<string, string>;
}

const stringIsValidJSON = (value: string) => {
  try {
    JSON.parse(value.trim());
    return true;
  } catch {
    return false;
  }
};

export function CreatePage({
  onCreate,
  onBack,
  instanceConfig,
  instanceInventory,
}: Props): VNode {
  const config = useConfigContext();
  const instance_default = with_defaults(instanceConfig, config.currency)
  const [value, valueHandler] = useState(instance_default);
  const zero = Amounts.zeroOfCurrency(config.currency);
  const [settings, updateSettings] = useSettings()
  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: FormErrors<Entity> = {
    pricing: undefinedIfEmpty({
      summary: !value.pricing?.summary ? i18n.str`required` : undefined,
      order_price: !value.pricing?.order_price
        ? i18n.str`required`
        : !parsedPrice
          ? i18n.str`not valid`
          : 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 cannot 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 cannot 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 cannot 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`should have a refund deadline`
          : Duration.cmp(
            value.payments.refund_deadline,
            value.payments.auto_refund_deadline,
          ) == -1
            ? i18n.str`auto refund cannot be after refund deadline`
            : undefined,

    }),
    shipping: undefinedIfEmpty({
      delivery_date: !value.shipping?.delivery_date
        ? undefined
        : !isFuture(value.shipping.delivery_date)
          ? i18n.str`should be in the future`
          : undefined,
    }),
  };
  const hasErrors = Object.keys(errors).some(
    (k) => (errors as any)[k] !== undefined,
  );

  const submit = (): void => {
    const order = value as any; //schema.cast(value);
    if (!value.payments) return;
    if (!value.shipping) return;

    const request: MerchantBackend.Orders.PostOrderRequest = {
      order: {
        amount: order.pricing.order_price,
        summary: order.pricing.summary,
        products: productList,
        extra: undefinedIfEmpty(value.extra),
        pay_deadline: !value.payments.pay_deadline ?
          i18n.str`required` :
          AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), value.payments.pay_deadline))
        ,// : undefined,
        wire_transfer_deadline: value.payments.wire_transfer_deadline
          ? AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), value.payments.wire_transfer_deadline))
          : undefined,
        refund_deadline: value.payments.refund_deadline
          ? AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), value.payments.refund_deadline))
          : undefined,
        auto_refund: value.payments.auto_refund_deadline
          ? Duration.toTalerProtocolDuration(value.payments.auto_refund_deadline)
          : undefined,
        max_fee: value.payments.max_fee as string,

        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: MerchantBackend.Products.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: MerchantBackend.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<
    MerchantBackend.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).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 = allProducts.reduce(
    (cur, prev) =>
      !prev.minimum_age || cur > prev.minimum_age ? cur : prev.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={!settings.advanceOrderMode ? "is-active" : ""} onClick={() => {
              updateSettings({
                ...settings,
                advanceOrderMode: false
              })
            }}>
              <a >
                <span><i18n.Translate>Simple</i18n.Translate></span>
              </a>
            </li>
            <li class={settings.advanceOrderMode ? "is-active" : ""} onClick={() => {
              updateSettings({
                ...settings,
                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>
                    {allProducts.length} products with a total price of{" "}
                    {totalAsString}.
                  </p>
                )
              }
              tooltip={i18n.str`Manage list of products in the order.`}
            >
              <InventoryProductForm
                currentProducts={value.inventoryProducts || {}}
                onAddProduct={addProductToTheInventoryList}
                inventory={instanceInventory}
              />

              {settings.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`}
              />

              {settings.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>
              }

              {(settings.advanceOrderMode || requiresSomeTalerOptions) &&
                <InputGroup
                  name="payments"
                  label={i18n.str`Taler payment options`}
                  tooltip={i18n.str`Override default Taler payment settings for this order`}
                >
                  {(settings.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>
                    }
                  />}
                  {settings.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>
                    }
                  />}
                  {(settings.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>
                    }
                  />}
                  {settings.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
                  />}

                  {settings.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.`}
                  />}
                  {settings.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.`}
                  />}
                  {settings.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>
              }

              {settings.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) => {

                    return <Input
                      name={`extra.${key}`}
                      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) {
                            console.log(value.extra)
                            delete value.extra[key]
                          }
                          valueHandler({
                            ...value,
                          })
                        }}>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]: ""
                        }
                      })
                    }}>add</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): MerchantBackend.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,
    minimum_age: p.product.minimum_age,
  };
}


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>
}
