/*
 This file is part of GNU Taler
 (C) 2022-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/>
 */
import {
  AbsoluteTime,
  AmountJson,
  Amounts,
  Codec,
  HttpStatusCode,
  OperationFail,
  OperationOk,
  TalerError,
  TalerErrorDetail,
  TalerExchangeApi,
  TranslatedString,
  assertUnreachable,
  buildCodecForObject,
  codecForNumber,
  codecForString,
  codecOptional,
} from "@gnu-taler/taler-util";
import {
  DefaultForm,
  ErrorLoading,
  InternationalizationAPI,
  Loading,
  useTranslationContext
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { privatePages } from "../Routing.js";
import { FormMetadata, useUiFormsContext } from "../context/ui-forms.js";
import { useCaseDetails } from "../hooks/useCaseDetails.js";
import { ShowConsolidated } from "./ShowConsolidated.js";
import { preloadedForms } from "../forms/index.js";

export type AmlEvent =
  | AmlFormEvent
  | AmlFormEventError
  | KycCollectionEvent
  | KycExpirationEvent;
type AmlFormEvent = {
  type: "aml-form";
  when: AbsoluteTime;
  title: TranslatedString;
  justification: Justification;
  metadata: FormMetadata;
  state: TalerExchangeApi.AmlState;
  threshold: AmountJson;
};
type AmlFormEventError = {
  type: "aml-form-error";
  when: AbsoluteTime;
  title: TranslatedString;
  justification: undefined;
  metadata: undefined;
  state: TalerExchangeApi.AmlState;
  threshold: AmountJson;
};
type KycCollectionEvent = {
  type: "kyc-collection";
  when: AbsoluteTime;
  title: TranslatedString;
  values: object;
  provider: string;
};
type KycExpirationEvent = {
  type: "kyc-expiration";
  when: AbsoluteTime;
  title: TranslatedString;
  fields: string[];
};

type WithTime = { when: AbsoluteTime };

function selectSooner(a: WithTime, b: WithTime) {
  return AbsoluteTime.cmp(a.when, b.when);
}

function titleForJustification(
  op: ReturnType<typeof parseJustification>,
  i18n: InternationalizationAPI,
): TranslatedString {
  if (op.type === "ok") {
    return op.body.justification.label as TranslatedString;
  }
  switch (op.case) {
    case "not-json":
      return i18n.str`error: the justification is not a form`;
    case "id-not-found":
      return i18n.str`error: justification form's id not found`;
    case "version-not-found":
      return i18n.str`error: justification form's version not found`;
    case "form-not-found":
      return i18n.str`error: justification form not found`;
    default: {
      assertUnreachable(op.case);
    }
  }
}

export function getEventsFromAmlHistory(
  aml: TalerExchangeApi.AmlDecisionDetail[],
  kyc: TalerExchangeApi.KycDetail[],
  i18n: InternationalizationAPI,
  forms: FormMetadata[],
): AmlEvent[] {
  const ae: AmlEvent[] = aml.map((a) => {
    const just = parseJustification(a.justification, forms);
    return {
      type: just.type === "ok" ? "aml-form" : "aml-form-error",
      state: a.new_state,
      threshold: Amounts.parseOrThrow(a.new_threshold),
      title: titleForJustification(just, i18n),
      metadata: just.type === "ok" ? just.body.metadata : undefined,
      justification: just.type === "ok" ? just.body.justification : undefined,
      when: {
        t_ms:
          a.decision_time.t_s === "never"
            ? "never"
            : a.decision_time.t_s * 1000,
      },
    } as AmlEvent;
  });
  const ke = kyc.reduce((prev, k) => {
    prev.push({
      type: "kyc-collection",
      title: i18n.str`collection`,
      when: AbsoluteTime.fromProtocolTimestamp(k.collection_time),
      values: !k.attributes ? {} : k.attributes,
      provider: k.provider_section,
    });
    prev.push({
      type: "kyc-expiration",
      title: i18n.str`expiration`,
      when: AbsoluteTime.fromProtocolTimestamp(k.expiration_time),
      fields: !k.attributes ? [] : Object.keys(k.attributes),
    });
    return prev;
  }, [] as AmlEvent[]);
  return ae.concat(ke).sort(selectSooner);
}

export function CaseDetails({ account }: { account: string }) {
  const [selected, setSelected] = useState<AbsoluteTime>(AbsoluteTime.now());
  const [showForm, setShowForm] = useState<{
    justification: Justification;
    metadata: FormMetadata;
  }>();

  const { i18n } = useTranslationContext();
  const details = useCaseDetails(account);
  const {forms} = useUiFormsContext()

  const allForms = [...forms, ...preloadedForms(i18n)]
  if (!details) {
    return <Loading />;
  }
  if (details instanceof TalerError) {
    return <ErrorLoading error={details} />;
  }
  if (details.type === "fail") {
    switch (details.case) {
      case HttpStatusCode.Unauthorized:
      case HttpStatusCode.Forbidden:
      case HttpStatusCode.NotFound:
      case HttpStatusCode.Conflict:
        return <div />;
      default:
        assertUnreachable(details);
    }
  }
  const { aml_history, kyc_attributes } = details.body;

  const events = getEventsFromAmlHistory(aml_history, kyc_attributes, i18n, allForms);

  if (showForm !== undefined) {
    return (
      <DefaultForm
        readOnly={true}
        initial={showForm.justification.value}
        form={showForm.metadata as any} // FIXME: HERE
      >
        <div class="mt-6 flex items-center justify-end gap-x-6">
          <button
            class="text-sm font-semibold leading-6 text-gray-900"
            onClick={() => {
              setShowForm(undefined);
            }}
          >
            <i18n.Translate>Cancel</i18n.Translate>
          </button>
        </div>
      </DefaultForm>
    );
  }
  return (
    <div>
      <a
        href={privatePages.caseNew.url({ cid: account })}
        class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
      >
        <i18n.Translate>New AML form</i18n.Translate>
      </a>

      <header class="flex items-center justify-between border-b border-white/5 px-4 py-4 sm:px-6 sm:py-6 lg:px-8">
        <h1 class="text-base font-semibold leading-7 text-black">
          <i18n.Translate>
            Case history for account{" "}
            <span title={account}>{account.substring(0, 16)}...</span>
          </i18n.Translate>
        </h1>
      </header>
      <ShowTimeline
        history={events}
        onSelect={(e) => {
          switch (e.type) {
            case "aml-form": {
              const { justification, metadata } = e;
              setShowForm({ justification, metadata });
              break;
            }
            case "kyc-collection":
            case "kyc-expiration": {
              setSelected(e.when);
              break;
            }
            case "aml-form-error":
          }
        }}
      />
      {/* {selected && <ShowEventDetails event={selected} />} */}
      {selected && <ShowConsolidated history={events} until={selected} />}
    </div>
  );
}

function AmlStateBadge({ state }: { state: TalerExchangeApi.AmlState }): VNode {
  switch (state) {
    case TalerExchangeApi.AmlState.normal: {
      return (
        <span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
          Normal
        </span>
      );
    }
    case TalerExchangeApi.AmlState.pending: {
      return (
        <span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20">
          Pending
        </span>
      );
    }
    case TalerExchangeApi.AmlState.frozen: {
      return (
        <span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20">
          Frozen
        </span>
      );
    }
  }
  assertUnreachable(state);
}

function ShowTimeline({
  history,
  onSelect,
}: {
  onSelect: (e: AmlEvent) => void;
  history: AmlEvent[];
}): VNode {
  return (
    <div class="flow-root">
      <ul role="list">
        {history.map((e, idx) => {
          const isLast = history.length - 1 === idx;
          return (
            <li
              key={idx}
              data-ok={e.type !== "aml-form-error"}
              class="hover:bg-gray-200 p-2 rounded data-[ok=true]:cursor-pointer"
              onClick={() => {
                onSelect(e);
              }}
            >
              <div class="relative pb-6">
                {!isLast ? (
                  <span
                    class="absolute left-4 top-4 -ml-px h-full w-1 bg-gray-200"
                    aria-hidden="true"
                  ></span>
                ) : undefined}
                <div class="relative flex space-x-3">
                  {(() => {
                    switch (e.type) {
                      case "aml-form-error":
                      case "aml-form": {
                        return (
                          <div>
                            <AmlStateBadge state={e.state} />
                            <span class="inline-flex items-center  px-2 py-1 text-xs font-medium text-gray-700 ">
                              {e.threshold.currency}{" "}
                              {Amounts.stringifyValue(e.threshold)}
                            </span>
                          </div>
                        );
                      }
                      case "kyc-collection": {
                        return (
                          // <ArrowDownCircleIcon class="h-8 w-8 text-green-700" />
                          <svg
                            xmlns="http://www.w3.org/2000/svg"
                            fill="none"
                            viewBox="0 0 24 24"
                            stroke-width="1.5"
                            stroke="currentColor"
                            class="w-6 h-6"
                          >
                            <path
                              stroke-linecap="round"
                              stroke-linejoin="round"
                              d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                            />
                          </svg>
                        );
                      }
                      case "kyc-expiration": {
                        // return <ClockIcon class="h-8 w-8 text-gray-700" />;
                        return (
                          <svg
                            xmlns="http://www.w3.org/2000/svg"
                            fill="none"
                            viewBox="0 0 24 24"
                            stroke-width="1.5"
                            stroke="currentColor"
                            class="w-6 h-6"
                          >
                            <path
                              stroke-linecap="round"
                              stroke-linejoin="round"
                              d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
                            />
                          </svg>
                        );
                      }
                    }
                    assertUnreachable(e);
                  })()}
                  <div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
                    {e.type === "aml-form" ? (
                      <span
                        // href={Pages.newFormEntry.url({ account })}
                        class="block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
                      >
                        {e.title}
                      </span>
                    ) : (
                      <p class="text-sm text-gray-900">{e.title}</p>
                    )}
                    <div class="whitespace-nowrap text-right text-sm text-gray-500">
                      {e.when.t_ms === "never" ? (
                        "never"
                      ) : (
                        <time dateTime={format(e.when.t_ms, "dd MMM yyyy")}>
                          {format(e.when.t_ms, "dd MMM yyyy")}
                        </time>
                      )}
                    </div>
                  </div>
                </div>
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

export type Justification<T = Record<string, unknown>> = {
  // form values
  value: T;
} & Omit<Omit<FormMetadata, "icon">, "config">;

type SimpleFormMetadata = {
  version?: number;
  id?: string;
};

export const codecForSimpleFormMetadata = (): Codec<SimpleFormMetadata> =>
  buildCodecForObject<SimpleFormMetadata>()
    .property("id", codecOptional(codecForString()))
    .property("version", codecOptional(codecForNumber()))
    .build("SimpleFormMetadata");

type ParseJustificationFail =
  | "not-json"
  | "id-not-found"
  | "form-not-found"
  | "version-not-found";

function parseJustification(
  s: string,
  listOfAllKnownForms: FormMetadata[],
):
  | OperationOk<{
      justification: Justification;
      metadata: FormMetadata;
    }>
  | OperationFail<ParseJustificationFail> {
  try {
    const justification = JSON.parse(s);
    const info = codecForSimpleFormMetadata().decode(justification);
    if (!info.id) {
      return {
        type: "fail",
        case: "id-not-found",
        detail: {} as TalerErrorDetail,
      };
    }
    if (!info.version) {
      return {
        type: "fail",
        case: "version-not-found",
        detail: {} as TalerErrorDetail,
      };
    }
    const found = listOfAllKnownForms.find((f) => {
      return f.id === info.id && f.version === info.version;
    });
    if (!found) {
      return {
        type: "fail",
        case: "form-not-found",
        detail: {} as TalerErrorDetail,
      };
    }
    return {
      type: "ok",
      body: {
        justification,
        metadata: found,
      },
    };
  } catch (e) {
    return {
      type: "fail",
      case: "not-json",
      detail: {} as TalerErrorDetail,
    };
  }
}
