import { AmountString, Amounts, PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { CopyButton, ShowInputErrorLabel, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useBankCoreApiContext } from "../../context/config.js";
import { ErrorMessageMappingFor, PartialButDefined, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { getRandomPassword } from "../rnd.js";

const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
const EMAIL_REGEX =
  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;

export type AccountFormData = TalerCorebankApi.AccountData & { 
  username: string,
  isExchange: boolean,
  isPublic: boolean,
}

type ChangeByPurposeType = {
  "create": (a: TalerCorebankApi.RegisterAccountRequest | undefined) => void,
  "update": (a: TalerCorebankApi.AccountReconfiguration | undefined) => void,
  "show": undefined
}
/**
 * FIXME:
 * is_public is missing on PATCH
 * account email/password should require 2FA
 * 
 * 
 * @param param0
 * @returns
 */
export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
  template,
  username,
  purpose,
  onChange,
  focus,
  admin,
  noCashout,
  children,
}: {
  focus?: boolean,
  children: ComponentChildren,
  username?: string,
  noCashout?: boolean,
  admin?: boolean,
  template: TalerCorebankApi.AccountData | undefined;
  onChange: ChangeByPurposeType[PurposeType];
  purpose: PurposeType;
}): VNode {
  const { config } = useBankCoreApiContext()

  const initial = initializeFromTemplate(username, template, config.default_debit_threshold);
  const [form, setForm] = useState(initial);
  const [errors, setErrors] = useState<
    ErrorMessageMappingFor<typeof initial> | undefined
  >(undefined);
  const { i18n } = useTranslationContext();

  function updateForm(newForm: typeof initial): void {
    const parsed = !newForm.cashout_payto_uri
      ? undefined
      : buildPayto("iban", newForm.cashout_payto_uri, undefined);;

    const trimmedAmountStr = newForm.debit_threshold?.trim();
    const parsedAmount = Amounts.parse(`${config.currency}:${trimmedAmountStr}`);

    const errors = undefinedIfEmpty<ErrorMessageMappingFor<typeof initial>>({
      cashout_payto_uri: (!newForm.cashout_payto_uri
        ? undefined
        : !parsed
          ? i18n.str`does not follow the pattern`
          : !parsed.isKnown || parsed.targetType !== "iban"
            ? i18n.str`only "IBAN" target are supported`
            : !IBAN_REGEX.test(parsed.iban)
              ? i18n.str`IBAN should have just uppercased letters and numbers`
              : validateIBAN(parsed.iban, i18n)),
      contact_data: undefinedIfEmpty({
        email: !newForm.contact_data?.email
          ? undefined
          : !EMAIL_REGEX.test(newForm.contact_data.email)
            ? i18n.str`it should be an email`
            : undefined,
        phone: !newForm.contact_data?.phone
          ? undefined
          : !newForm.contact_data.phone.startsWith("+")
            ? i18n.str`should start with +`
            : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
              ? i18n.str`phone number can't have other than numbers`
              : undefined,
      }),
      debit_threshold: !trimmedAmountStr
        ? (purpose === "create" ? i18n.str`required` : undefined)
        : !parsedAmount
          ? i18n.str`not valid`
            : undefined,
      name: !newForm.name ? i18n.str`required` : undefined,
      username: !newForm.username ? i18n.str`required` : undefined,
    });
    setErrors(errors);
    setForm(newForm);
    if (!onChange) return;

    if (errors) {
      onChange(undefined)
    } else {
      const cashout = !newForm.cashout_payto_uri ? undefined : buildPayto("iban", newForm.cashout_payto_uri, undefined)
      const cashoutURI = !cashout ? undefined : stringifyPaytoUri(cashout)

      const internal = !newForm.payto_uri ? undefined : buildPayto("iban", newForm.payto_uri, undefined);
      const internalURI = !internal ? undefined : stringifyPaytoUri(internal)

      switch (purpose) {
        case "create": {
          //typescript doesn't correctly narrow a generic type
          const callback = onChange as ChangeByPurposeType["create"]
          const result: TalerCorebankApi.RegisterAccountRequest = {
            cashout_payto_uri: cashoutURI,
            name: newForm.name!,
            password: getRandomPassword(),
            username: newForm.username!,
            challenge_contact_data: undefinedIfEmpty({
              email: newForm.contact_data?.email,
              phone: newForm.contact_data?.phone,
            }),
            debit_threshold: `${config.currency}:${newForm.debit_threshold}` as AmountString,
            internal_payto_uri: internalURI,
            is_public: newForm.isPublic,
            is_taler_exchange: newForm.isExchange,
          }
          callback(result)
          return;
        }
        case "update": {
          //typescript doesn't correctly narrow a generic type
          const callback = onChange as ChangeByPurposeType["update"]
          
          const result: TalerCorebankApi.AccountReconfiguration = {
            cashout_payto_uri: cashoutURI,
            challenge_contact_data: undefinedIfEmpty({
              email: newForm.contact_data?.email,
              phone: newForm.contact_data?.phone,
            }),
            debit_threshold: newForm.debit_threshold as AmountString,
            is_taler_exchange: newForm.isExchange,
            name: newForm.name
            // is_public: newForm.isPublic
          }
          callback(result)
          return;
        }
        case "show": {
          return;
        }
        default: {
          assertUnreachable(purpose)
        }
      }
    }
  }

  return (
    <form
      class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
      autoCapitalize="none"
      autoCorrect="off"
      onSubmit={e => {
        e.preventDefault()
      }}
    >
      <div class="px-4 py-6 sm:p-8">
        <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="username"
            >
              {i18n.str`Username`}
              {purpose === "create" && <b style={{ color: "red" }}> *</b>}
            </label>
            <div class="mt-2">
              <input
                ref={focus && purpose === "create" ? doAutoFocus : undefined}
                type="text"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="username"
                id="username"
                data-error={!!errors?.username && form.username !== undefined}
                disabled={purpose !== "create"}
                value={form.username ?? ""}
                onChange={(e) => {
                  form.username = e.currentTarget.value;
                  updateForm(structuredClone(form));
                }}
                // placeholder=""
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.username}
                isDirty={form.username !== undefined}
              />
            </div>
            <p class="mt-2 text-sm text-gray-500" >
              <i18n.Translate>account identification in the bank</i18n.Translate>
            </p>
          </div>

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="name"
            >
              {i18n.str`Name`}
              {purpose === "create" && <b style={{ color: "red" }}> *</b>}
            </label>
            <div class="mt-2">
              <input
                type="text"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="name"
                data-error={!!errors?.name && form.name !== undefined}
                id="name"
                disabled={purpose !== "create"}
                value={form.name ?? ""}
                onChange={(e) => {
                  form.name = e.currentTarget.value;
                  updateForm(structuredClone(form));
                }}
                // placeholder=""
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.name}
                isDirty={form.name !== undefined}
              />
            </div>
            <p class="mt-2 text-sm text-gray-500" >
              <i18n.Translate>name of the person owner the account</i18n.Translate>
            </p>
          </div>


          {purpose !== "create" && (<RenderPaytoDisabledField paytoURI={form.payto_uri as PaytoString} />)}

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="email"
            >
              {i18n.str`Email`}
            </label>
            <div class="mt-2">
              <input
                type="email"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="email"
                id="email"
                data-error={!!errors?.contact_data?.email && form.contact_data?.email !== undefined}
                disabled={purpose === "show"}
                value={form.contact_data?.email ?? ""}
                onChange={(e) => {
                  if (form.contact_data) {
                    form.contact_data.email = e.currentTarget.value;
                    if (!form.contact_data.email) {
                      form.contact_data.email = undefined
                    }
                    updateForm(structuredClone(form));
                  }
                }}
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.contact_data?.email}
                isDirty={form.contact_data?.email !== undefined}
              />
            </div>
          </div>

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="phone"
            >
              {i18n.str`Phone`}
            </label>
            <div class="mt-2">
              <input
                type="text"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="phone"
                id="phone"
                disabled={purpose === "show"}
                value={form.contact_data?.phone ?? ""}
                data-error={!!errors?.contact_data?.phone && form.contact_data?.phone !== undefined}
                onChange={(e) => {
                  if (form.contact_data) {
                    form.contact_data.phone = e.currentTarget.value;
                    if (!form.contact_data.email) {
                      form.contact_data.email = undefined
                    }
                    updateForm(structuredClone(form));
                  }
                }}
                // placeholder=""
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.contact_data?.phone}
                isDirty={form.contact_data?.phone !== undefined}
              />
            </div>
          </div>

          {!noCashout &&
            <div class="sm:col-span-5">
              <label
                class="block text-sm font-medium leading-6 text-gray-900"
                for="cashout"
              >
                {i18n.str`Cashout IBAN`}
              </label>
              <div class="mt-2">
                <input
                  type="text"
                  ref={focus && purpose === "update" ? doAutoFocus : undefined}
                  data-error={!!errors?.cashout_payto_uri && form.cashout_payto_uri !== undefined}
                  class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  name="cashout"
                  id="cashout"
                  disabled={purpose === "show"}
                  value={form.cashout_payto_uri ?? ""}
                  onChange={(e) => {
                    form.cashout_payto_uri = e.currentTarget.value as PaytoString;
                    if (!form.cashout_payto_uri) {
                      form.cashout_payto_uri = undefined
                    }
                    updateForm(structuredClone(form));
                  }}
                  autocomplete="off"
                />
                <ShowInputErrorLabel
                  message={errors?.cashout_payto_uri}
                  isDirty={form.cashout_payto_uri !== undefined}
                />
              </div>
              <p class="mt-2 text-sm text-gray-500" >
                <i18n.Translate>account number where the money is going to be sent when doing cashouts</i18n.Translate>
              </p>
            </div>
          }

          {admin ? <Fragment>
            <div class="sm:col-span-5">
              <label for="debit" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Max debt`}</label>
              <InputAmount
                name="debit"
                left
                currency={config.currency}
                value={form.debit_threshold ?? ""}
                onChange={(e) => {
                  form.debit_threshold = e as AmountString
                  updateForm(structuredClone(form))
                }}
              />
              <ShowInputErrorLabel
                message={errors?.debit_threshold ? String(errors?.debit_threshold) : undefined}
                isDirty={form.debit_threshold !== undefined}
              />
              <p class="mt-2 text-sm text-gray-500" >how much is user able to transfer </p>
            </div>

            <div class="sm:col-span-5">
              <div class="flex items-center justify-between">
                <span class="flex flex-grow flex-col">
                  <span class="text-sm text-black font-medium leading-6 " id="availability-label">
                    <i18n.Translate>Is an exchange</i18n.Translate>
                  </span>
                </span>
                <button type="button" data-enabled={!!form.isExchange} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"

                  onClick={() => {
                    form.isExchange = !form.isExchange
                    updateForm(structuredClone(form))
                  }}>
                  <span aria-hidden="true" data-enabled={!!form.isExchange} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
                </button>
              </div>
            </div>
          </Fragment> :
            undefined
          }

          {purpose === "create" ?
            <div class="sm:col-span-5">
              <div class="flex items-center justify-between">
                <span class="flex flex-grow flex-col">
                  <span class="text-sm text-black font-medium leading-6 " id="availability-label">
                    <i18n.Translate>Is public</i18n.Translate>
                  </span>
                </span>
                <button type="button" data-enabled={!!form.isPublic} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"

                  onClick={() => {
                    form.isPublic = !form.isPublic
                    updateForm(structuredClone(form))
                  }}>
                  <span aria-hidden="true" data-enabled={!!form.isPublic} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
                </button>
              </div>
              <p class="mt-2 text-sm text-gray-500" >
                <i18n.Translate>public accounts have their balance publicly accesible</i18n.Translate>
              </p>
            </div>
            : undefined
          }

        </div>
      </div>
      {children}
    </form>
  );
}
function initializeFromTemplate(
  username: string | undefined,
  account: TalerCorebankApi.AccountData | undefined,
  default_debit_threshold: AmountString,
): WithIntermediate<AccountFormData> {
  const emptyAccount = {
    cashout_payto_uri: undefined,
    contact_data: undefined,
    payto_uri: undefined,
    balance: undefined,
    debit_threshold: Amounts.stringifyValue(default_debit_threshold) as AmountString,
    name: undefined,
  };
  const emptyContact = {
    email: undefined,
    phone: undefined,
  };

  const initial: PartialButDefined<TalerCorebankApi.AccountData> =
    structuredClone(account) ?? emptyAccount;
  if (typeof initial.contact_data === "undefined") {
    initial.contact_data = emptyContact;
  }
  if (initial.cashout_payto_uri) {
    const ac = parsePaytoUri(initial.cashout_payto_uri)
    if (ac?.isKnown && ac.targetType === "iban") {
      // we are using the cashout field for the iban number
      initial.cashout_payto_uri = ac.targetPath as any
    }
  }
  const result: WithIntermediate<AccountFormData> = initial as any // FIXME: check types
  result.username = username

  return initial as any;
}


function RenderPaytoDisabledField({ paytoURI }: { paytoURI: PaytoString | undefined }): VNode {
  const { i18n } = useTranslationContext()
  const payto = parsePaytoUri(paytoURI ?? "");
  if (payto?.isKnown) {
    if (payto.targetType === "iban") {
      const value = payto.iban;
      return <div class="sm:col-span-5">
        <label
          class="block text-sm font-medium leading-6 text-gray-900"
          for="internal-iban"
        >
          {i18n.str`Internal IBAN`}
        </label>
        <div class="mt-2">
          <div class="flex justify-between">
            <input
              type="text"
              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              name="internal-iban"
              id="internal-iban"
              disabled={true}
              value={value ?? ""}
            />
            <CopyButton
              class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
              getContent={() => value ?? ""}
            />
          </div>
        </div>
        <p class="mt-2 text-sm text-gray-500" >
          <i18n.Translate>international bank account number</i18n.Translate>
        </p>
      </div>
    }
    if (payto.targetType === "x-taler-bank") {
      const value = payto.account;
      return <div class="sm:col-span-5">
        <label
          class="block text-sm font-medium leading-6 text-gray-900"
          for="account-id"
        >
          {i18n.str`Account ID`}
        </label>
        <div class="mt-2">
          <div class="flex justify-between">
            <input
              type="text"
              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              name="account-id"
              id="account-id"
              disabled={true}
              value={value ?? ""}
            />
            <CopyButton
              class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
              getContent={() => value ?? ""}
            />
          </div>
        </div>
        <p class="mt-2 text-sm text-gray-500" >
          <i18n.Translate>internal account id</i18n.Translate>
        </p>
      </div>
    }
    if (payto.targetType === "bitcoin") {
      const value = payto.targetPath;
      return <div class="sm:col-span-5">
        <label
          class="block text-sm font-medium leading-6 text-gray-900"
          for="account-id"
        >
          {i18n.str`Bitcoin address`}
        </label>
        <div class="mt-2">
          <div class="flex justify-between">
            <input
              type="text"
              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              name="account-id"
              id="account-id"
              disabled={true}
              value={value ?? ""}
            />
            <CopyButton
              class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
              getContent={() => value ?? ""}
            />
          </div>
        </div>
        <p class="mt-2 text-sm text-gray-500" >
          <i18n.Translate>bitcoin address</i18n.Translate>
        </p>
      </div>
    }
    assertUnreachable(payto)
  }

  const value = paytoURI ?? ""
  return <div class="sm:col-span-5">
    <label
      class="block text-sm font-medium leading-6 text-gray-900"
      for="internal-payto"
    >
      {i18n.str`Internal account`}
    </label>
    <div class="mt-2">
      <div class="flex justify-between">
        <input
          type="text"
          class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
          name="internal-payto"
          id="internal-payto"
          disabled={true}
          value={value ?? ""}
        />
        <CopyButton
          class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
          getContent={() => value ?? ""}
        />
      </div>
    </div>
    <p class="mt-2 text-sm text-gray-500" >
      <i18n.Translate>generic payto URI</i18n.Translate>
    </p>
  </div>
}
