/*
 This file is part of GNU Taler
 (C) 2022-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/>
 */
import {
  AccountProperties,
  AmlSpaDialect,
  GLS_AccountProperties,
  GLS_AML_PROPERTIES,
  LegitimizationRuleSet,
  PropertiesDerivation_TOPS,
  PropertiesDerivationFunctionByPropertyName,
  TalerAmlProperties,
  TalerFormAttributes,
  TOPS_AccountProperties,
} from "@gnu-taler/taler-util";
import {
  ErrorsSummary,
  FormDesign,
  FormUI,
  InternationalizationAPI,
  onComponentUnload,
  UIFormElementConfig,
  UIHandlerId,
  useExchangeApiContext,
  useForm,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import {
  DecisionRequest,
  useCurrentDecisionRequest,
} from "../../hooks/decision-request.js";
import { usePreferences } from "../../hooks/preferences.js";
import { DEFAULT_LIMITS_WHEN_NEW_ACCOUNT } from "./Rules.js";

/**
 * Update account properties
 * @param param0
 * @returns
 */
export function Properties(): VNode {
  const [request] = useCurrentDecisionRequest();
  const { config } = useExchangeApiContext();
  const [pref] = usePreferences();

  const dialect =
    (pref.testingDialect ? undefined : config.config.aml_spa_dialect) ??
    AmlSpaDialect.TESTING;

  const calculatedProps = calculatePropertiesBasedOnState(
    request.original?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT,
    request.original?.properties ?? {},
    request,
    dialect,
  );

  const merged = Object.entries(calculatedProps).reduce(
    (prev, [key, value]) => {
      if (prev[key] === undefined && value !== undefined) {
        prev[key] = value;
      }
      return prev;
    },
    request.properties ?? {},
  );
  return (
    <div>
      <ReloadForm merged={merged} />
    </div>
  );
}

function officerMustCheckInvestigationState(
  data: Record<keyof typeof TalerFormAttributes, string>,
): boolean {
  if (data[TalerFormAttributes.FORM_ID] !== "vqf_902_14") return false;
  if (data[TalerFormAttributes.INCRISK_RESULT] !== "OTHER") return false;
  return true;
}

function ReloadForm({
  merged,
}: {
  merged: Record<string, string | boolean>;
}): VNode {
  const { i18n } = useTranslationContext();
  const [request, updateRequest] = useCurrentDecisionRequest();
  const [pref] = usePreferences();
  const { config } = useExchangeApiContext();
  const dialect =
    (pref.testingDialect ? undefined : config.config.aml_spa_dialect) ??
    AmlSpaDialect.TESTING;
  const design = propertiesForm(
    i18n,
    propertiesByDialect(i18n, dialect, {
      MANDATORY_INVESTIGATION_STATE: officerMustCheckInvestigationState(
        // @ts-expect-error data is the form
        request.attributes?.data ?? {},
      ),
    }),
  );

  // const [id, setId] = useState(new Date().getTime());

  const customProps = Object.entries(request.custom_properties ?? {}).map(
    ([name, value]) => {
      return { name, value };
    },
  );
  const form = useForm<PropertiesForm>(design, {
    defined: merged,
    custom: customProps,
  });
  const errors = form.status.errors;

  onComponentUnload(() => {
    updateRequest("unload properties", {
      properties: (form.status.result.defined ?? {}) as Record<string, boolean>,
      custom_properties: (form.status.result.custom ?? []).reduce(
        (prev, cur) => {
          if (!cur || !cur.name || !cur.value) return prev;
          prev[cur.name] = cur.value;
          return prev;
        },
        {} as Record<string, string>,
      ),
      properties_errors: errors,
    });
  });

  return (
    <div>
      <button
        onClick={() => {
          form.update({
            custom: customProps,
            defined: merged,
          });
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm disabled:bg-gray-500 bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
      >
        <i18n.Translate>Reset to default</i18n.Translate>
      </button>

      {!errors ? undefined : <ErrorsSummary errors={errors.defined} />}
      <FormUI design={design} model={form.model} />
    </div>
  );
}

type PropertiesForm = {
  defined: { [name: string]: string | boolean };
  custom: { name: string; value: string }[];
};

const propertiesForm = (
  i18n: InternationalizationAPI,
  props: UIFormElementConfig[],
): FormDesign => ({
  type: "double-column",
  sections: [
    {
      title: i18n.str`Properties`,
      fields: props.map((f) =>
        "id" in f ? { ...f, id: "defined." + f.id } : f,
      ),
    },
    {
      title: i18n.str`Custom properties`,
      description: i18n.str`Add more properties that not listed above.`,
      fields: [
        {
          id: "custom",
          type: "array",
          label: i18n.str`New properties`,
          labelFieldId: "name",
          fields: [
            {
              type: "text",
              label: i18n.str`Name`,
              id: "name",
            },
            {
              type: "text",
              label: i18n.str`Value`,
              id: "value",
            },
          ],
        },
      ],
    },
  ],
});

function propertiesByDialect(
  i18n: InternationalizationAPI,
  dialect: AmlSpaDialect,
  options: {
    MANDATORY_INVESTIGATION_STATE?: boolean;
  } = {},
): UIFormElementConfig[] {
  if (dialect === AmlSpaDialect.TOPS) {
    return [
      {
        id: TalerAmlProperties.FILE_NOTE,
        label: i18n.str`Current note on the GWG File`,
        type: "text",
      },
      {
        id: TalerAmlProperties.CUSTOMER_LABEL,
        label: i18n.str`Customer name or internal alias.`,
        type: "text",
      },
      {
        id: TalerAmlProperties.ACCOUNT_OPEN,
        label: i18n.str`Is the account active for deposit and payments?`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.ACCOUNT_IDLE,
        label: i18n.str`Is the account idle?`,
        help: i18n.str`No operation for a period of time`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.PEP_DOMESTIC,
        label: i18n.str`Does account belong to a domestic PEP?`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.PEP_FOREIGN,
        label: i18n.str`Does account belong to a foreign PEP?`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.PEP_INTERNATIONAL_ORGANIZATION,
        label: i18n.str`Does account belong to a international organization PEP?`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.HIGH_RISK_CUSTOMER,
        label: i18n.str`Does account belong to a high risk customer?`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.HIGH_RISK_COUNTRY,
        label: i18n.str`Does account belong to a person from a high risk country?`,
        type: "toggle",
      },
      {
        id: TalerAmlProperties.INVESTIGATION_STATE,
        label: i18n.str`The MROS reporting state for the account.`,
        type: "choiceStacked",
        required: options.MANDATORY_INVESTIGATION_STATE,
        choices: [
          {
            label: i18n.str`None`,
            value: "NONE",
          },
          {
            label: i18n.str`Pending investigation`,
            value: "INVESTIGATION_PENDING",
          },
          {
            label: i18n.str`Investigated without suspicion`,
            value: "INVESTIGATION_COMPLETED_WITHOUT_SUSPICION",
          },
          {
            label: i18n.str`Reported simple suspicion`,
            value: "REPORTED_SUSPICION_SIMPLE",
          },
          {
            label: i18n.str`Reported substantaited suspicion`,
            value: "REPORTED_SUSPICION_SUBSTANTIATED",
          },
        ],
      },
      {
        id: TalerAmlProperties.INVESTIGATION_TRIGGER,
        label: i18n.str`Informal reason why the AML investigation was triggered`,
        type: "text",
      },
      {
        id: TalerAmlProperties.SANCTION_LIST_BEST_MATCH,
        label: i18n.str`Identifies the sanction list entry that the account matched against`,
        type: "text",
        help: i18n.str`Best match, does not mean it was a good match.`,
      },
      {
        id: TalerAmlProperties.SANCTION_LIST_RATING,
        label: i18n.str`Score for how good the sanction list match was`,
        type: "integer",
      },
      {
        id: TalerAmlProperties.SANCTION_LIST_CONFIDENCE,
        label: i18n.str`Score for how much supporting data we had for the sanction list match`,
        type: "integer",
      },
      {
        id: TalerAmlProperties.SANCTION_LIST_SUPPRESS,
        label: i18n.str`Suppress flagging this account when it creates a hit on a sanctions list, this is a false-positive`,
        type: "toggle",
        threeState: true,
      },
    ];
  }

  if (dialect === AmlSpaDialect.GLS) {
    return [
      {
        id: "GLS_CUSTOMER_ID",
        label: i18n.str`GLS Customer ID`,
        type: "text",
      },
    ];
  }

  return [];
}

function calculatePropertiesBasedOnState(
  currentLimits: LegitimizationRuleSet,
  state: AccountProperties,
  request: DecisionRequest,
  dialect: AmlSpaDialect,
): Record<UIHandlerId, string | boolean | undefined> {
  const newNewAttributes = (
    request.attributes ? request.attributes.data : {}
  ) as Record<string, unknown>;
  type Result = Record<UIHandlerId, string | boolean | undefined>;
  const initial: Result = {};

  const formId = request.attributes?.formId;

  if (!formId) {
    return state;
  }

  const FORM_ID = formId;

  function mergeProperties<T extends string>(
    iv: Result,
    list: readonly T[],
    info: PropertiesDerivationFunctionByPropertyName<T>,
  ): Result {
    return list.reduce((result, prop) => {
      result[prop] = info[prop].deriveProperty(
        FORM_ID,
        newNewAttributes,
        currentLimits,
        state,
      );
      return result;
    }, iv);
  }

  switch (dialect) {
    case AmlSpaDialect.TOPS: {
      return mergeProperties(
        initial,
        TOPS_AccountProperties,
        PropertiesDerivation_TOPS,
      );
    }
    case AmlSpaDialect.GLS: {
      return mergeProperties(
        initial,
        GLS_AccountProperties,
        GLS_AML_PROPERTIES,
      );
    }
    case AmlSpaDialect.TESTING: {
      return mergeProperties(
        initial,
        TOPS_AccountProperties,
        PropertiesDerivation_TOPS,
      );
    }
  }
}
