import type { DoneActorEvent, ErrorActorEvent } from "xstate";

import type {
  CreatePaymentOrderResponse,
  PaymentOrderStatusResponse,
} from "@/data/dto/payment";
import { behaviorAnalytics } from "@/lib/analytics";

import {
  getCurrentOrderPlan,
  getSelectedPlan,
  hasOrderPollingTimedOut,
  PAYMENT_TIMEOUT_ERROR_MESSAGE,
  toPaymentErrorMessage,
} from "../helper/order";
import {
  catalogMachineSetup,
  idleState,
  loadingCachedPlansState,
  loadingPlansState,
  readyState,
  refreshingPlansState,
} from "./catalog-flow";
import { createPaymentActorActionSetup } from "./setup";

const POLL_DELAY_MS = 4000;

const trackCreateOrderStartAction = catalogMachineSetup.createAction(
  ({ context }) => {
    const plan = getSelectedPlan(context);
    if (plan) behaviorAnalytics.createOrderStart(plan, context.payChannel);
  },
);

const trackReturnedPollStartAction = catalogMachineSetup.createAction(
  ({ event }) => {
    if (event.type === "PaymentReturned") {
      behaviorAnalytics.statusPollStart(event.orderId);
    }
  },
);

const applyReturnedOrderAction = catalogMachineSetup.assign(({ event }) => {
  if (event.type !== "PaymentReturned") return {};
  return {
    currentOrderId: event.orderId,
    currentOrderPlanId: null,
    payParams: null,
    orderStatus: "pending",
    orderPollingStartedAt: event.createdAt ?? Date.now(),
    errorMessage: null,
  };
});

const applyLaunchFailureAction = catalogMachineSetup.assign(({ event }) =>
  event.type === "PaymentLaunchFailed"
    ? { errorMessage: event.errorMessage }
    : {},
);

export const paymentMachineSetup = catalogMachineSetup.extend({
  actions: {
    trackCreateOrderStart: trackCreateOrderStartAction,
    trackReturnedPollStart: trackReturnedPollStartAction,
    applyReturnedOrder: applyReturnedOrderAction,
    applyLaunchFailure: applyLaunchFailureAction,
  },
});

const createOrderDoneSetup =
  createPaymentActorActionSetup<
    DoneActorEvent<CreatePaymentOrderResponse>
  >();
const pollOrderDoneSetup =
  createPaymentActorActionSetup<
    DoneActorEvent<PaymentOrderStatusResponse>
  >();
const orderErrorSetup = createPaymentActorActionSetup<ErrorActorEvent>();

const trackCreateOrderSuccessAction = createOrderDoneSetup.createAction(
  ({ context, event }) => {
    const plan = getSelectedPlan(context);
    if (plan) {
      behaviorAnalytics.createOrderSuccess(
        plan,
        event.output.orderId,
        context.payChannel,
      );
    }
    behaviorAnalytics.statusPollStart(event.output.orderId, plan);
  },
);

const applyCreateOrderSuccessAction = createOrderDoneSetup.assign(
  ({ context, event }) => ({
    currentOrderId: event.output.orderId,
    currentOrderPlanId: context.selectedPlanId,
    payParams: event.output.payParams,
    orderStatus: "pending",
    orderPollingStartedAt: Date.now(),
    errorMessage: null,
    launchNonce: context.launchNonce + 1,
  }),
);

const trackCreateOrderFailureAction = orderErrorSetup.createAction(
  ({ context }) => {
    const plan = getSelectedPlan(context);
    if (plan) behaviorAnalytics.createOrderFailed(plan, context.payChannel);
  },
);

const applyOrderErrorAction = orderErrorSetup.assign(({ event }) => ({
  errorMessage: toPaymentErrorMessage(event.error),
}));

const trackPollUpdateAction = pollOrderDoneSetup.createAction(
  ({ context, event }) =>
    behaviorAnalytics.statusPollUpdate(
      context.currentOrderId ?? "",
      event.output.status,
      getCurrentOrderPlan(context),
    ),
);

const trackPollTimeoutAction = pollOrderDoneSetup.createAction(
  ({ context, event }) => {
    const orderId = context.currentOrderId ?? "";
    const plan = getCurrentOrderPlan(context);
    behaviorAnalytics.statusPollUpdate(orderId, event.output.status, plan);
    behaviorAnalytics.statusPollTimeout(orderId, plan);
  },
);

const applyPaidOrderAction = pollOrderDoneSetup.assign(({ event }) => ({
  orderStatus: event.output.status,
  orderPollingStartedAt: null,
  errorMessage: null,
}));

const applyFailedOrderAction = pollOrderDoneSetup.assign(({ event }) => ({
  orderStatus: event.output.status,
  orderPollingStartedAt: null,
  errorMessage: "Payment failed or was cancelled.",
}));

const applyTimedOutOrderAction = pollOrderDoneSetup.assign({
  orderStatus: "failed",
  orderPollingStartedAt: null,
  errorMessage: PAYMENT_TIMEOUT_ERROR_MESSAGE,
});

const applyPendingOrderAction = pollOrderDoneSetup.assign(({ event }) => ({
  orderStatus: event.output.status,
  errorMessage: null,
}));

const creatingOrderState = paymentMachineSetup.createStateConfig({
  entry: "trackCreateOrderStart",
  invoke: {
    src: "createOrder",
    input: ({ context }) => ({
      planId: context.selectedPlanId,
      payChannel: context.payChannel,
      autoRenew: context.autoRenew,
    }),
    onDone: {
      target: "pollingOrder",
      actions: [
        trackCreateOrderSuccessAction,
        applyCreateOrderSuccessAction,
      ],
    },
    onError: {
      target: "failed",
      actions: [trackCreateOrderFailureAction, applyOrderErrorAction],
    },
  },
});

const pollingOrderState = paymentMachineSetup.createStateConfig({
  invoke: {
    src: "pollOrderStatus",
    input: ({ context }) => ({ orderId: context.currentOrderId ?? "" }),
    onDone: [
      {
        guard: ({ event }) => event.output.status === "paid",
        target: "paid",
        actions: [trackPollUpdateAction, applyPaidOrderAction],
      },
      {
        guard: ({ event }) => event.output.status === "failed",
        target: "failed",
        actions: [trackPollUpdateAction, applyFailedOrderAction],
      },
      {
        guard: ({ context }) =>
          hasOrderPollingTimedOut(context.orderPollingStartedAt),
        target: "failed",
        actions: [trackPollTimeoutAction, applyTimedOutOrderAction],
      },
      {
        target: "waitingForPayment",
        actions: [trackPollUpdateAction, applyPendingOrderAction],
      },
    ],
    onError: {
      target: "failed",
      actions: applyOrderErrorAction,
    },
  },
  on: {
    PaymentReset: {
      target: "ready",
      actions: "resetOrder",
    },
  },
});

const waitingForPaymentState = paymentMachineSetup.createStateConfig({
  after: {
    [POLL_DELAY_MS]: "pollingOrder",
  },
  on: {
    PaymentReset: {
      target: "ready",
      actions: "resetOrder",
    },
  },
});

const paidState = paymentMachineSetup.createStateConfig({
  on: {
    PaymentPlanSelected: {
      target: "ready",
      actions: "selectPlan",
    },
    PaymentPayChannelChanged: {
      target: "ready",
      actions: "changePayChannel",
    },
    PaymentCreateOrderSubmitted: [
      {
        guard: "canCreateOrder",
        target: "creatingOrder",
        actions: "resetOrder",
      },
      {
        target: "ready",
        actions: "showCreateOrderValidationError",
      },
    ],
    PaymentReset: {
      target: "ready",
      actions: "resetOrder",
    },
  },
});

const failedState = paymentMachineSetup.createStateConfig({
  on: {
    PaymentCreateOrderSubmitted: [
      {
        guard: "canCreateOrder",
        target: "creatingOrder",
        actions: "resetOrder",
      },
    ],
    PaymentErrorCleared: {
      target: "ready",
      actions: "clearPaymentError",
    },
    PaymentReset: {
      target: "ready",
      actions: "resetOrder",
    },
  },
});

export const paymentRootStateConfig = paymentMachineSetup.createStateConfig({
  initial: "idle",
  states: {
    idle: idleState,
    loadingCachedPlans: loadingCachedPlansState,
    loadingPlans: loadingPlansState,
    refreshingPlans: refreshingPlansState,
    ready: readyState,
    creatingOrder: creatingOrderState,
    pollingOrder: pollingOrderState,
    waitingForPayment: waitingForPaymentState,
    paid: paidState,
    failed: failedState,
  },
  on: {
    PaymentFirstRechargeConsumed: { actions: "consumeFirstRecharge" },
    PaymentReturned: {
      target: ".pollingOrder",
      actions: ["trackReturnedPollStart", "applyReturnedOrder"],
    },
    PaymentLaunchFailed: {
      target: ".failed",
      actions: "applyLaunchFailure",
    },
  },
});
