import {
  BillingDetailsFields,
  billingDetailsSchema,
  defaultBillingDetails,
} from "@/components/billing/billing-details-form";
import { StripeInputElementWrapper } from "@/components/billing/payment-checkout/stripe-input-element-wrapper";
import { withStripeElementsHoc } from "@/components/billing/stripe";
import { FormServerErrors } from "@/components/form-common/form-server-errors";
import { FormSubmit } from "@/components/form-common/form-submit";
import {
  fieldItemProps,
  fieldLabelProps,
  fieldMessageProps,
} from "@/components/tanstack-form-utils";
import { ButtonProps } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { useLocaleFunction } from "@/hooks/use-localed";
import { toast } from "@/hooks/use-toast";
import {
  attachUserPaymentMethod,
  type BillingDetails,
  validateUserBillingDetails,
} from "@/lib/api/billing";
import { getErrorMessage, Standard400Error } from "@/lib/errors";
import {
  applyFormError,
  handleFormElementSubmitEvent,
  handleSubmitErrors,
} from "@/lib/form";
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
import type { Stripe, StripeElements } from "@stripe/stripe-js";
import { type FormValidateAsyncFn, useForm } from "@tanstack/react-form";
import { useId } from "react";

export interface PaymentMethodsQuickSetupProps {
  billingDetails: BillingDetails;
  submitVariant?: ButtonProps["variant"];
  onSetupFinished: (paymentMethodId: string) => void;
}

export const PaymentMethodsQuickSetup = withStripeElementsHoc(
  ({
    billingDetails,
    submitVariant,
    onSetupFinished,
  }: PaymentMethodsQuickSetupProps) => {
    const stripe = useStripe();
    const elements = useElements();

    const form = useForm({
      validators: {
        onSubmitAsync: createPaymentQuickSetupAsyncSchema(
          billingDetails,
          stripe!,
          elements!
        ),
      },
      onSubmit: async ({ value, formApi }) => {
        await handleSubmitErrors(formApi, async () => {
          await attachUserPaymentMethod(value.payment_method_id);
          onSetupFinished(value.payment_method_id);
          toast({
            title: t("payment.toasts.succeed-to-add-payment-method"),
          });
        });
      },
      defaultValues: {
        payment_method_id: "",
      },
    });

    const id = useId();
    const t = useLocaleFunction();

    return (
      <form className="space-y-4" onSubmit={handleFormElementSubmitEvent(form)}>
        <form.Field name="payment_method_id">
          {(field) => (
            <div className="space-y-2">
              <Label
                htmlFor="card-element"
                {...fieldLabelProps(field, id)}
                onClick={() => elements?.getElement("card")?.focus()}
              >
                {t("payment.payment-methods.add.title")}
              </Label>
              <StripeInputElementWrapper {...fieldItemProps(field, id)}>
                <CardElement
                  options={{ hidePostalCode: true, disabled: false }}
                  id="card-element"
                  onBlur={() =>
                    field.setErrorMap({
                      onSubmit: undefined,
                      onServer: undefined,
                    })
                  }
                  onChange={() =>
                    field.setErrorMap({
                      onSubmit: undefined,
                      onServer: undefined,
                    })
                  }
                />
              </StripeInputElementWrapper>
              <p {...fieldMessageProps(field, id)}></p>
            </div>
          )}
        </form.Field>
        <FormServerErrors
          formApi={form}
          title={t("payment.alerts.failed-to-add-payment-method.title")}
        />
        <FormSubmit
          formApi={form}
          className="w-full"
          variant={submitVariant ?? "secondary"}
        >
          {t("payment.payment-methods.add")}
        </FormSubmit>
      </form>
    );
  }
);

PaymentMethodsQuickSetup.displayName = "PaymentMethodsQuickSetup";

export interface PaymentMethodFullSetupProps {
  submitVariant?: ButtonProps["variant"];
  onSetupFinished: (paymentMethodId: string) => void;
}

export const PaymentMethodFullSetup = withStripeElementsHoc(
  ({ onSetupFinished, submitVariant }: PaymentMethodFullSetupProps) => {
    const stripe = useStripe();
    const elements = useElements();

    const form = useForm({
      validators: {
        onSubmitAsync: createPaymentFullSetupAsyncSchema(stripe!, elements!),
      },
      onSubmit: async ({ value, formApi }) => {
        await handleSubmitErrors(formApi, async () => {
          await attachUserPaymentMethod(value.payment_method_id);
          onSetupFinished(value.payment_method_id);
          toast({
            title: t("payment.toasts.succeed-to-add-payment-method"),
          });
        });
      },
      defaultValues: {
        ...defaultBillingDetails,
        payment_method_id: "",
      },
    });

    const id = useId();
    const t = useLocaleFunction();

    return (
      <form className="space-y-4" onSubmit={handleFormElementSubmitEvent(form)}>
        <form.Field name="payment_method_id">
          {(field) => (
            <div className="space-y-2">
              <Label
                htmlFor="card-element"
                {...fieldLabelProps(field, id)}
                onClick={() => elements?.getElement("card")?.focus()}
              >
                {t("payment.payment-methods.add.title")}
              </Label>
              <StripeInputElementWrapper {...fieldItemProps(field, id)}>
                <CardElement
                  options={{ hidePostalCode: true, disabled: false }}
                  onBlur={() =>
                    field.setErrorMap({
                      onSubmit: undefined,
                      onServer: undefined,
                    })
                  }
                  onChange={() =>
                    field.setErrorMap({
                      onSubmit: undefined,
                      onServer: undefined,
                    })
                  }
                  id="card-element"
                />
              </StripeInputElementWrapper>
              <p {...fieldMessageProps(field, id)}></p>
            </div>
          )}
        </form.Field>
        <BillingDetailsFields formApi={form} />
        <FormServerErrors
          formApi={form}
          title={t("payment.alerts.failed-to-add-payment-method.title")}
        />
        <FormSubmit
          formApi={form}
          className="w-full"
          variant={submitVariant ?? "secondary"}
        >
          {t("payment.payment-methods.add")}
        </FormSubmit>
      </form>
    );
  }
);

PaymentMethodFullSetup.displayName = "PaymentMethodFullSetup";

async function createPaymentMethod(
  stripe: Stripe,
  elements: StripeElements,
  billingDetails: BillingDetails
): Promise<object | string> {
  const creatResult = await stripe.createPaymentMethod({
    elements,
    params: {
      billing_details: {
        address: billingDetails,
      },
    },
  });

  if (creatResult.error) {
    return {
      fields: {
        payment_method_id: getErrorMessage(creatResult.error),
      },
    };
  }

  return creatResult.paymentMethod.id;
}

function createPaymentQuickSetupAsyncSchema(
  billingDetails: BillingDetails,
  stripe: Stripe | null,
  elements: StripeElements | null
): FormValidateAsyncFn<{ payment_method_id: string }> {
  return async ({ formApi }) => {
    if (!stripe || !elements) {
      return {
        issues: [
          {
            path: ["payment_method_id"],
            message:
              "Stripe was not initialized properly. Please try again later.",
          },
        ],
      };
    }
    const paymentMethodId = await createPaymentMethod(
      stripe,
      elements,
      billingDetails
    );

    if (typeof paymentMethodId === "string") {
      formApi.setFieldValue("payment_method_id", paymentMethodId);
    } else {
      return paymentMethodId;
    }
  };
}

function createPaymentFullSetupAsyncSchema(
  stripe: Stripe | null,
  elements: StripeElements | null
): FormValidateAsyncFn<{ payment_method_id: string } & BillingDetails> {
  return async ({ formApi, value }) => {
    if (!stripe || !elements) {
      return {
        issues: [
          {
            path: ["payment_method_id"],
            message:
              "Stripe was not initialized properly. Please try again later.",
          },
        ],
      };
    }

    try {
      await validateUserBillingDetails(
        Object.assign({}, value, { payment_method_id: undefined })
      );
    } catch (e) {
      if (Standard400Error.isStandard400Error(e)) {
        const rest = applyFormError(formApi, e.body, "onServer");
        return {
          form: rest,
        };
      }
    }

    const result = billingDetailsSchema.safeParse(value);
    if (!result.success) {
      const { formErrors, fieldErrors } = result.error.formErrors;
      return {
        form: formErrors,
        fields: fieldErrors,
      };
    }
    const billingDetails = result.data;

    const paymentMethodId = await createPaymentMethod(
      stripe,
      elements,
      billingDetails
    );
    if (typeof paymentMethodId === "string") {
      formApi.setFieldValue("payment_method_id", paymentMethodId);
    } else {
      return paymentMethodId;
    }
  };
}
