/*
 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,
  TalerMerchantApi,
} from "@gnu-taler/taler-util";
import * as tests from "@gnu-taler/web-util/testing";
import { expect } from "chai";
import { useInstanceOrders, useOrderDetails } from "./order.js";
import { ApiMockEnvironment } from "./testing.js";
import {
  API_CREATE_ORDER,
  API_DELETE_ORDER,
  API_FORGET_ORDER_BY_ID,
  API_GET_ORDER_BY_ID,
  API_LIST_ORDERS,
  API_REFUND_ORDER_BY_ID,
} from "./urls.js";
import { useMerchantApiContext } from "@gnu-taler/web-util/browser";

describe("order api interaction with listing", () => {
  it("should evict cache when creating an order", async () => {
    const env = new ApiMockEnvironment();

    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: -20, paid: "yes" },
      response: {
        orders: [
          { order_id: "1" },
          { order_id: "2" } as TalerMerchantApi.OrderHistoryEntry,
        ],
      },
    });

    const newDate = (_d: string | undefined) => {
      //console.log("new date", d);
    };

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const query = useInstanceOrders({ paid: true }, newDate);
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query }) => {
          expect(query).undefined;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [{ order_id: "1" }, { order_id: "2" }],
          // });

          env.addRequestExpectation(API_CREATE_ORDER, {
            request: {
              order: { amount: "ARS:12" as AmountString, summary: "pay me" },
              lock_uuids: [],
            },
            response: { order_id: "3", pay_deadline: { t_s: "never" } },
          });

          env.addRequestExpectation(API_LIST_ORDERS, {
            qparam: { delta: -20, paid: "yes" },
            response: {
              orders: [
                { order_id: "1" },
                { order_id: "2" } as any,
                { order_id: "3" } as any,
              ],
            },
          });

          api.instance.createOrder(undefined, {
            order: { amount: "ARS:12" as AmountString, summary: "pay me" },
          });
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });

          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [{ order_id: "1" }, { order_id: "2" }, { order_id: "3" }],
          // });
        },
      ],
      env.buildTestingContext(),
    );
    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });

  it("should evict cache when doing a refund", async () => {
    const env = new ApiMockEnvironment();

    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: -20, paid: "yes" },
      response: {
        orders: [
          {
            order_id: "1",
            amount: "EUR:12",
            refundable: true,
          } as TalerMerchantApi.OrderHistoryEntry,
        ],
      },
    });

    const newDate = (_d: string | undefined) => {
      //console.log("new date", d);
    };

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const query = useInstanceOrders({ paid: true }, newDate);
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query, api }) => {
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });

          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [
          //     {
          //       order_id: "1",
          //       amount: "EUR:12",
          //       refundable: true,
          //     },
          //   ],
          // });
          env.addRequestExpectation(API_REFUND_ORDER_BY_ID("1"), {
            request: {
              reason: "double pay",
              refund: "EUR:1",
            },
          });

          env.addRequestExpectation(API_LIST_ORDERS, {
            qparam: { delta: -20, paid: "yes" },
            response: {
              orders: [
                { order_id: "1", amount: "EUR:12", refundable: false } as any,
              ],
            },
          });

          api.instance.addRefund(undefined, "1", {
            reason: "double pay",
            refund: "EUR:1" as AmountString,
          });
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });

          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [
          //     {
          //       order_id: "1",
          //       amount: "EUR:12",
          //       refundable: false,
          //     },
          //   ],
          // });
        },
      ],
      env.buildTestingContext(),
    );

    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });

  it("should evict cache when deleting an order", async () => {
    const env = new ApiMockEnvironment();

    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: -20, paid: "yes" },
      response: {
        orders: [
          { order_id: "1" },
          { order_id: "2" } as TalerMerchantApi.OrderHistoryEntry,
        ],
      },
    });

    const newDate = (_d: string | undefined) => {
      //console.log("new date", d);
    };

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const query = useInstanceOrders({ paid: true }, newDate);
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query, api }) => {
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [{ order_id: "1" }, { order_id: "2" }],
          // });

          env.addRequestExpectation(API_DELETE_ORDER("1"), {});

          env.addRequestExpectation(API_LIST_ORDERS, {
            qparam: { delta: -20, paid: "yes" },
            response: {
              orders: [{ order_id: "2" } as any],
            },
          });

          api.instance.deleteOrder(undefined, "1");
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [{ order_id: "2" }],
          // });
        },
      ],
      env.buildTestingContext(),
    );
    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });
});

describe("order api interaction with details", () => {
  it("should evict cache when doing a refund", async () => {
    const env = new ApiMockEnvironment();

    env.addRequestExpectation(API_GET_ORDER_BY_ID("1"), {
      // qparam: { delta: 0, paid: "yes" },
      response: {
        summary: "description",
        refund_amount: "EUR:0",
      } as unknown as TalerMerchantApi.CheckPaymentPaidResponse,
    });

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const query = useOrderDetails("1");
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query, api }) => {
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).false;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   summary: "description",
          //   refund_amount: "EUR:0",
          // });
          env.addRequestExpectation(API_REFUND_ORDER_BY_ID("1"), {
            request: {
              reason: "double pay",
              refund: "EUR:1",
            },
          });

          env.addRequestExpectation(API_GET_ORDER_BY_ID("1"), {
            response: {
              summary: "description",
              refund_amount: "EUR:1",
            } as unknown as TalerMerchantApi.CheckPaymentPaidResponse,
          });

          api.instance.addRefund(undefined, "1", {
            reason: "double pay",
            refund: "EUR:1" as AmountString,
          });
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).false;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   summary: "description",
          //   refund_amount: "EUR:1",
          // });
        },
      ],
      env.buildTestingContext(),
    );

    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });

  it("should evict cache when doing a forget", async () => {
    const env = new ApiMockEnvironment();

    env.addRequestExpectation(API_GET_ORDER_BY_ID("1"), {
      // qparam: { delta: 0, paid: "yes" },
      response: {
        summary: "description",
        refund_amount: "EUR:0",
      } as unknown as TalerMerchantApi.CheckPaymentPaidResponse,
    });

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const query = useOrderDetails("1");
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query, api }) => {
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).false;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   summary: "description",
          //   refund_amount: "EUR:0",
          // });
          env.addRequestExpectation(API_FORGET_ORDER_BY_ID("1"), {
            request: {
              fields: ["$.summary"],
            },
          });

          env.addRequestExpectation(API_GET_ORDER_BY_ID("1"), {
            response: {
              summary: undefined,
            } as unknown as TalerMerchantApi.CheckPaymentPaidResponse,
          });

          api.instance.forgetOrder(undefined, "1", {
            fields: ["$.summary"],
          });
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).false;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   summary: undefined,
          // });
        },
      ],
      env.buildTestingContext(),
    );
    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });
});

describe("order listing pagination", () => {
  it("should not load more if has reach the end", async () => {
    const env = new ApiMockEnvironment();
    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: 20, wired: "yes", date_s: 12 },
      response: {
        orders: [{ order_id: "1" } as any],
      },
    });

    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: -20, wired: "yes", date_s: 13 },
      response: {
        orders: [{ order_id: "2" } as any],
      },
    });

    const newDate = (_d: string | undefined) => {
      //console.log("new date", d);
    };

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const date = new Date(12000);
        const query = useInstanceOrders(
          { wired: true, date: AbsoluteTime.fromMilliseconds(date.getTime()) },
          newDate,
        );
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query, api }) => {
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [{ order_id: "1" }, { order_id: "2" }],
          // });
          // expect(query.isReachingEnd).true;
          // expect(query.isReachingStart).true;
        },
      ],
      env.buildTestingContext(),
    );

    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });

  it("should load more if result brings more that PAGINATED_LIST_REQUEST", async () => {
    const env = new ApiMockEnvironment();

    const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({
      order_id: String(i),
    }));
    const ordersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({
      order_id: String(i + 20),
    }));

    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: 20, wired: "yes", date_s: 12 },
      response: {
        orders: ordersFrom0to20,
      },
    });

    env.addRequestExpectation(API_LIST_ORDERS, {
      qparam: { delta: -20, wired: "yes", date_s: 13 },
      response: {
        orders: ordersFrom20to40,
      },
    });

    const newDate = (_d: string | undefined) => {
      //console.log("new date", d);
    };

    const hookBehavior = await tests.hookBehaveLikeThis(
      () => {
        const date = new Date(12000);
        const query = useInstanceOrders(
          { wired: true, date: AbsoluteTime.fromMilliseconds(date.getTime()) },
          newDate,
        );
        const { lib: api } = useMerchantApiContext();
        return { query, api };
      },
      {},
      [
        ({ query, api }) => {
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [...ordersFrom20to0, ...ordersFrom20to40],
          // });
          // expect(query.isReachingEnd).false;
          // expect(query.isReachingStart).false;

          env.addRequestExpectation(API_LIST_ORDERS, {
            qparam: { delta: -40, wired: "yes", date_s: 13 },
            response: {
              orders: [...ordersFrom20to40, { order_id: "41" }],
            },
          });

          // query.loadMore();
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [
          //     ...ordersFrom20to0,
          //     ...ordersFrom20to40,
          //     { order_id: "41" },
          //   ],
          // });

          env.addRequestExpectation(API_LIST_ORDERS, {
            qparam: { delta: 40, wired: "yes", date_s: 12 },
            response: {
              orders: [...ordersFrom0to20, { order_id: "-1" }],
            },
          });

          // query.loadMorePrev();
        },
        ({ query, api }) => {
          expect(env.assertJustExpectedRequestWereMade()).deep.eq({
            result: "ok",
          });
          // expect(query.loading).true;
        },
        ({ query, api }) => {
          // expect(query.loading).undefined;
          // expect(query.ok).true;
          // if (!query.ok) return;
          // expect(query.data).deep.equals({
          //   orders: [
          //     { order_id: "-1" },
          //     ...ordersFrom20to0,
          //     ...ordersFrom20to40,
          //     { order_id: "41" },
          //   ],
          // });
        },
      ],
      env.buildTestingContext(),
    );
    expect(hookBehavior).deep.eq({ result: "ok" });
    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
  });
});
