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

/**
 *
 * @author Martin Schanzenbach
 */

import {
  AmountJson,
  Amounts,
  HttpStatusCode,
  InternationalizationAPI,
  StatisticBucketRange,
  StatisticsAmount,
  TalerError,
  TranslatedString,
} from "@gnu-taler/taler-util";
import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser";
import type { ChartDataset, ChartOptions, Point } from "chart.js";
import { format } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { BarCanvas } from "../../../../components/ChartJS.js";
import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
import { FormProvider } from "../../../../components/form/FormProvider.js";
import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import {
  MerchantRevenueStatsSlug,
  useInstanceStatisticsAmount,
} from "../../../../hooks/statistics.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
import { RelevantTimeUnit, RevenueChartFilter } from "./index.js";

const TALER_SCREEN_ID = 59;

export interface RevenueChartProps {
  colors: Map<MerchantRevenueStatsSlug, string>;
  availableCurrencies: string[];
  activeFilter: {
    range: RelevantTimeUnit;
    currency: string;
    rangeCount: number;
  };
  onUpdateFilter: (filter: RevenueChartFilter) => void;
}

export function RevenueChart({
  colors,
  onUpdateFilter,
  availableCurrencies,
  activeFilter: activeFilter,
}: RevenueChartProps): VNode {
  const { i18n } = useTranslationContext();
  const [showTable, setShowTable] = useState<boolean>(false);
  const translateMap = new Map<
    RelevantTimeUnit,
    [TranslatedString, TranslatedString]
  >([
    ["hour", [i18n.str`hour`, i18n.str`hours`]],
    ["day", [i18n.str`day`, i18n.str`days`]],
    ["week", [i18n.str`week`, i18n.str`weeks`]],
    ["month", [i18n.str`month`, i18n.str`months`]],
    ["quarter", [i18n.str`quarter`, i18n.str`quarters`]],
    ["year", [i18n.str`years`, i18n.str`years`]],
  ]);
  function translateRange(range: {
    rangeCount: number;
    range?: RelevantTimeUnit;
  }): TranslatedString {
    if (!translateMap.has(range.range!)) {
      return "" as TranslatedString;
    }
    return range.rangeCount < 2
      ? translateMap.get(range.range!)![0]
      : translateMap.get(range.range!)![1];
  }
  const revenues = useInstanceStatisticsAmount();
  if (!revenues) {
    return <Loading />;
  }
  if (revenues instanceof TalerError) {
    return <ErrorLoadingMerchant error={revenues} />;
  }
  if (revenues.type === "fail") {
    switch (revenues.case) {
      case HttpStatusCode.Unauthorized:
        return <LoginPage />;
      case HttpStatusCode.NotFound:
        return <NotFoundPageOrAdminCreate />;
      case HttpStatusCode.BadGateway:
        return (
          <NotificationCard
            notification={{
              message: i18n.str`Bad gateway`,
              type: "ERROR",
            }}
          />
        );

      case HttpStatusCode.ServiceUnavailable:
        return (
          <NotificationCard
            notification={{
              message: i18n.str`Service unavailable`,
              type: "ERROR",
            }}
          />
        );
    }
  }
  const chart = filterRevenueCharData(colors, revenues.body, activeFilter);

  const form = activeFilter;
  return (
    <Fragment>
      <div class="tabs" style={{ overflow: "inherit" }}>
        <ul>
          <li class={!showTable ? "is-active" : ""}>
            <div class="has-tooltip-right" data-tooltip={i18n.str`Show chart`}>
              <a
                onClick={() => {
                  setShowTable(false);
                }}
              >
                <i18n.Translate>Revenue chart</i18n.Translate>
              </a>
            </div>
          </li>
          <li class={showTable ? "is-active" : ""}>
            <div class="has-tooltip-right" data-tooltip={i18n.str`Show table`}>
              <a
                onClick={() => {
                  setShowTable(true);
                }}
              >
                <i18n.Translate>Revenue table</i18n.Translate>
              </a>
            </div>
          </li>
        </ul>
      </div>
      <div class="columns">
        <div class="column">
          <div class="buttons is-right">
            <FormProvider
              object={form}
              valueHandler={(updater) =>
                onUpdateFilter({
                  rangeCount:
                    updater(form).rangeCount ?? activeFilter.rangeCount,
                  range: updater(form).range ?? activeFilter.range,
                  currency: updater(form).currency ?? activeFilter.currency,
                })
              }
            >
              <InputGroup
                name="rangeFilter"
                label={i18n.str`Revenue statistics filter`}
              >
                <InputSelector
                  name="range"
                  label={i18n.str`Time range`}
                  values={revenueRanges}
                  fromStr={(d) => {
                    const idx = revenueRanges.indexOf(
                      d as StatisticBucketRange,
                    );
                    if (idx === -1) return undefined;
                    return d;
                  }}
                  tooltip={i18n.str`Select time range to group dataset by`}
                />
                <InputNumber
                  name="rangeCount"
                  label={translateMap.get(activeFilter.range)![0]}
                  tooltip={i18n.str`Select the number of ranges to include in data set`}
                />
                <InputSelector
                  name="currency"
                  label={i18n.str`Currency`}
                  values={availableCurrencies}
                  fromStr={(c) => {
                    const idx = availableCurrencies.indexOf(c);
                    if (idx === -1) return undefined;
                    return c;
                  }}
                  tooltip={i18n.str`Select the currency to show statistics for`}
                />
              </InputGroup>
            </FormProvider>
          </div>
        </div>
      </div>
      <div class="card has-table">
        <header class="card-header">
          <p class="card-header-title">
            <span class="icon">
              <i class="mdi mdi-shopping" />
            </span>
            {i18n.str`Revenue statistics over the past ${
              activeFilter.rangeCount
            } ${translateRange(activeFilter)} for currency '${
              activeFilter.currency
            }'`}
          </p>
        </header>
        <div class="card-content">
          {chart.datasets.length > 0 && chart.labels.length > 0 ? (
            !showTable ? (
              <BarCanvas
                data={chart}
                options={revenueChartOptions(i18n, activeFilter)}
              />
            ) : (
              <div class="b-table has-pagination">
                <div class="table-wrapper has-mobile-cards">
                  <div class="table-container">
                    <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
                      <thead>
                        <tr>
                          <th>
                            <i18n.Translate>Start time</i18n.Translate>
                          </th>
                          {chart.datasets.map((d) => {
                            return (
                              <Fragment key={d.label}>
                                <th>{d.label}</th>
                              </Fragment>
                            );
                          })}
                        </tr>
                      </thead>
                      <tbody>
                        {chart.labels.map((l) => {
                          return (
                            <Fragment key={l}>
                              <tr key="info">
                                <td>
                                  {format(
                                    new Date(l),
                                    formatMap.get(activeFilter.range)!,
                                  )}
                                </td>
                                {chart.datasets.map((d) => {
                                  return (
                                    <Fragment key={d.label}>
                                      <td>
                                        {getRevenueForLabelAndDataset(l, d)}
                                      </td>
                                    </Fragment>
                                  );
                                })}
                              </tr>
                            </Fragment>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                </div>
              </div>
            )
          ) : (
            <i>{i18n.str`No revenue statistics yet.`}</i>
          )}
        </div>
      </div>
    </Fragment>
  );
}

const revenueRanges = Object.values(StatisticBucketRange);
const formatMap = new Map<RelevantTimeUnit, string>([
  ["hour", "yyyy MMMM d ha"],
  ["day", "yyyy MMM d"],
  ["week", "yyyy MMM w"],
  ["month", "yyyy MMM"],
  ["quarter", "yyyy qqq"],
  ["year", "yyyy"],
]);
function getRevenueForLabelAndDataset(
  label: number,
  ds: ChartDataset<"bar", (Point & AmountJson)[]>,
): string {
  for (let d of ds.data) {
    if (d.x === label) {
      return Amounts.toPretty(d);
    }
  }
  return "-";
}

function revenueChartOptions(
  i18n: InternationalizationAPI,
  filter: RevenueChartFilter,
): ChartOptions<"bar"> {
  // FIXME: End date picker?
  // FIXME: Intelligent date range selection?
  // revenueChartOptions.scales.y.title.text = revenueChartFilter.currency;
  // revenueChartOptions.scales.x.time.unit = revenueChartFilter.range;
  return {
    plugins: {
      title: {
        display: true,
        text: i18n.str`Revenue`,
      },
    },
    responsive: true,
    scales: {
      x: {
        type: "time",
        time: { unit: filter.range },
        ticks: { source: "labels" },
        stacked: true,
        title: { display: true, text: i18n.str`Time range`, align: "end" },
      },
      y: {
        stacked: true,
        title: { display: true, text: filter.currency, align: "end" },
      },
    },
  };
}

function filterRevenueCharData(
  colors: Map<MerchantRevenueStatsSlug, string>,
  revenueStats: Map<MerchantRevenueStatsSlug, StatisticsAmount>,
  filter: RevenueChartFilter,
): {
  datasets: ChartDataset<"bar", (Point & AmountJson)[]>[];
  labels: number[];
} {
  const datasets: ChartDataset<"bar", (Point & AmountJson)[]>[] = [];
  const labels: number[] = [];

  revenueStats.forEach((stat, slug) => {
    // var datasetForCurrency = new Map<string, ChartDataset<"line" | "bar">>();
    const datasetColor = colors.get(slug ?? "") ?? "#eeeeee";
    const info: ChartDataset<"bar", (Point & AmountJson)[]> = {
      label: stat.buckets_description,
      data: [],
      backgroundColor: datasetColor,
    };
    for (let b of stat.buckets) {
      if (b.start_time.t_s == "never") {
        continue;
      }

      for (let c of b.cumulative_amounts) {
        const a = Amounts.parse(c);
        if (!a) {
          continue;
        }
        if (filter.range !== b.range) {
          continue;
        }
        const amount = a.value + a.fraction / Math.pow(10, 8);
        const timeRangeStart = b.start_time.t_s * 1000;
        info.data.push({ x: timeRangeStart, y: amount, ...a });
        if (filter.rangeCount < info.data.length) {
          info.data.shift(); // Remove oldest entry
          labels.shift(); // Remove oldest entry
        }
        if (labels.indexOf(timeRangeStart) === -1) {
          labels.push(timeRangeStart);
        }
      }
    }
    if (info.data.length) {
      datasets.push(info);
    }
  });
  return { datasets, labels };
}
