import { areAddressesTheSame } from "@insite/client-framework/Common/CustomerSelectors";
import {
    createHandlerChainRunnerOptionalParameter,
    Handler,
    HasOnSuccess,
    makeHandlerChainAwaitable,
} from "@insite/client-framework/HandlerCreator";
import {
    Cart,
    CartResult,
    updateCart as updateCartApi,
    UpdateCartApiParameter,
} from "@insite/client-framework/Services/CartService";
import { createShipTo, updateBillTo, updateShipTo } from "@insite/client-framework/Services/CustomersService";
import { FulfillmentMethod, Session, updateSession } from "@insite/client-framework/Services/SessionService";
import { getAddressFieldsDataView } from "@insite/client-framework/Store/Data/AddressFields/AddressFieldsSelector";
import { getBillToState } from "@insite/client-framework/Store/Data/BillTos/BillTosSelectors";
import { getCartState, getCurrentCartState } from "@insite/client-framework/Store/Data/Carts/CartsSelector";
import loadCart from "@insite/client-framework/Store/Data/Carts/Handlers/LoadCart";
import loadCurrentCart from "@insite/client-framework/Store/Data/Carts/Handlers/LoadCurrentCart";
import loadShipTo from "@insite/client-framework/Store/Data/ShipTos/Handlers/LoadShipTo";
import { getShipToState } from "@insite/client-framework/Store/Data/ShipTos/ShipTosSelectors";
import validateBillingAddressForm from "@insite/client-framework/Store/Pages/OnePageCheckout/Handlers/ValidateBillingAddressForm";
import translate from "@insite/client-framework/Translate";
import { BillToModel, ShipToModel } from "@insite/client-framework/Types/ApiModels";

type HandlerType = Handler<
    HasOnSuccess,
    {
        shipTo?: ShipToModel;
        updatedBillTo: BillToModel;
        updatedShipTo?: ShipToModel;
        cart: Cart;
        apiParameter: UpdateCartApiParameter;
        apiResult: CartResult;
    }
>;

export const DispatchBeginSaveShipping: HandlerType = props => {
    props.dispatch({
        type: "Pages/OnePageCheckout/BeginSaveShipping",
    });
};

export const PopulateCart: HandlerType = props => {
    const state = props.getState();
    const { cartId } = state.pages.onePageCheckout;
    const cart = cartId ? getCartState(state, cartId).value : getCurrentCartState(state).value;
    if (!cart) {
        throw new Error("There was no current cart available while trying to update the current cart.");
    }

    props.cart = { ...cart };
};

export const PopulateApiParameter: HandlerType = props => {
    props.apiParameter = {
        cart: props.cart,
    };
};

export const UpdateBillTo: HandlerType = async props => {
    const {
        getState,
        apiParameter: { cart },
    } = props;
    const {
        context: {
            session: { fulfillmentMethod },
        },
        pages: {
            onePageCheckout: { shippingAddressFormState, billingAddressFormState, isBillingAddressTheSameAsShipping },
        },
    } = getState();
    if (!shippingAddressFormState || !billingAddressFormState) {
        return;
    }

    const { address: localModifiedShipTo } = shippingAddressFormState;
    const localModifiedBillTo = { ...billingAddressFormState.address };
    if (isBillingAddressTheSameAsShipping && fulfillmentMethod === FulfillmentMethod.Ship) {
        updateBillToWithShipToFields(localModifiedBillTo, localModifiedShipTo);
    }

    const billTo = getBillToState(getState(), cart.billToId).value;
    if (!billTo) {
        throw new Error("There was no billTo loaded for the current cart");
    }

    if (areAddressesTheSame(billTo, localModifiedBillTo)) {
        props.updatedBillTo = billTo;
    } else {
        const updatedBillTo = await updateBillTo({ billTo: localModifiedBillTo, isEdit: false });
        props.updatedBillTo = { ...updatedBillTo, validation: billTo.validation };
        props.dispatch({
            type: "Pages/OnePageCheckout/SetBillingAddress",
            address: props.updatedBillTo,
        });
    }

    props.apiParameter.cart.billToId = props.updatedBillTo.id;
};

export const ValidateBillingAddress: HandlerType = async props => {
    const addressFieldsDataView = getAddressFieldsDataView(props.getState());
    const awaitableValidate = makeHandlerChainAwaitable<Parameters<typeof validateBillingAddressForm>[0], boolean>(
        validateBillingAddressForm,
    );
    const isBillingAddressValid = await awaitableValidate({
        address: props.updatedBillTo,
        validation: props.updatedBillTo.validation!,
        fieldDisplay: addressFieldsDataView.value!.shipToAddressFields,
    })(props.dispatch, props.getState);

    const {
        context: {
            session: { fulfillmentMethod },
        },
        pages: {
            onePageCheckout: { billingAddressFormState, shippingAddressFormState, isBillingAddressTheSameAsShipping },
        },
    } = props.getState();
    if (!isBillingAddressValid && isBillingAddressTheSameAsShipping && fulfillmentMethod === FulfillmentMethod.Ship) {
        const billingFormErrors = billingAddressFormState?.formErrors;
        const shippingFormErrors = { ...shippingAddressFormState?.formErrors };
        billingFormErrors &&
            shippingFormErrors &&
            Object.keys(billingFormErrors).forEach(key => {
                shippingFormErrors[key] = `${billingFormErrors[key]} ${translate("for billing")}`;
            });
        props.dispatch({
            type: "Pages/OnePageCheckout/SetShippingAddressFormErrors",
            formErrors: shippingFormErrors,
        });
        props.dispatch({
            type: "Pages/OnePageCheckout/CompleteSaveShipping",
        });

        return false;
    }

    props.dispatch({
        type: "Pages/OnePageCheckout/SetIsBillingAddressUpdateRequired",
        isBillingAddressUpdateRequired: !isBillingAddressValid,
    });
};

export const AddOrUpdateShipTo: HandlerType = async props => {
    const { dispatch, getState, cart } = props;
    const state = getState();
    const {
        context: {
            session: { fulfillmentMethod },
        },
        pages: {
            onePageCheckout: { shippingAddressFormState, isBillingAddressTheSameAsShipping },
        },
    } = state;
    if (!shippingAddressFormState || fulfillmentMethod !== FulfillmentMethod.Ship) {
        return;
    }

    const shipTo = getShipToState(state, cart.shipToId).value;
    const billTo = getBillToState(state, cart.billToId).value;
    if (!shipTo || !billTo) {
        throw new Error("There was no shipTo or billTo loaded for the current cart");
    }

    const { address: localModifiedShipTo } = shippingAddressFormState;
    if (isBillingAddressTheSameAsShipping && cart.shipToId !== cart.billToId && cart.billToId) {
        props.apiParameter.cart.shipToId = cart.billToId;
        const { shipToId } = props.apiParameter.cart;
        await makeHandlerChainAwaitable(loadShipTo)({ shipToId, billToId: cart.billToId, expand: ["validation"] })(
            dispatch,
            getState,
        );
        const shipToState = getShipToState(getState(), shipToId);
        shipToState.value &&
            props.dispatch({
                type: "Pages/OnePageCheckout/SetShippingAddress",
                address: shipToState.value,
            });
        return;
    }

    let updatedShipTo;
    if (!isBillingAddressTheSameAsShipping && cart.shipToId === cart.billToId) {
        updatedShipTo = await createShipTo({ shipTo: localModifiedShipTo });
    } else if (!areAddressesTheSame(shipTo, localModifiedShipTo)) {
        updatedShipTo = await updateShipTo({ shipTo: localModifiedShipTo, billToId: billTo.id });
    }

    if (updatedShipTo) {
        props.updatedShipTo = { ...updatedShipTo, validation: shipTo.validation };
        dispatch(loadShipTo({ shipToId: props.updatedShipTo.id, billToId: billTo.id }));
        props.dispatch({
            type: "Pages/OnePageCheckout/SetShippingAddress",
            address: props.updatedShipTo,
        });
        props.apiParameter.cart.shipToId = props.updatedShipTo.id;
    }
};

export const UpdateSession: HandlerType = async props => {
    const {
        updatedShipTo,
        updatedBillTo,
        apiParameter: { cart },
    } = props;

    if (updatedShipTo || updatedBillTo) {
        await updateSession({
            session: {
                billToId: updatedBillTo?.id ?? cart.billToId,
                shipToId: updatedShipTo?.id ?? cart.shipToId,
                customerWasUpdated: true,
            } as Session,
        });
    }
};

export const UpdateCart: HandlerType = async props => {
    props.apiResult = await updateCartApi(props.apiParameter);
};

export const LoadCart: HandlerType = props => {
    const { cartId } = props.getState().pages.checkoutShipping;
    if (cartId) {
        props.dispatch(loadCart({ cartId, getPromotions: true }));
    } else {
        props.dispatch(loadCurrentCart({ getPromotions: true }));
    }
};

export const ExecuteOnSuccessCallback: HandlerType = props => {
    props.parameter.onSuccess?.();
};

export const DispatchCompleteSaveShipping: HandlerType = props => {
    props.dispatch({
        type: "Pages/OnePageCheckout/CompleteSaveShipping",
    });
};

const updateBillToWithShipToFields = (billTo: BillToModel, shipTo: ShipToModel) => {
    billTo.firstName = shipTo.firstName;
    billTo.lastName = shipTo.lastName;
    billTo.attention = shipTo.attention;
    billTo.companyName = shipTo.companyName;
    billTo.address1 = shipTo.address1;
    billTo.address2 = shipTo.address2;
    billTo.address3 = shipTo.address3;
    billTo.address4 = shipTo.address4;
    billTo.country = shipTo.country;
    billTo.city = shipTo.city;
    billTo.state = shipTo.state;
    billTo.postalCode = shipTo.postalCode;
    billTo.phone = shipTo.phone;
    billTo.email = shipTo.email;
    billTo.fax = shipTo.fax;
    billTo.contactFullName = shipTo.contactFullName;
    billTo.fullAddress = shipTo.fullAddress;
};

export const chain = [
    DispatchBeginSaveShipping,
    PopulateCart,
    PopulateApiParameter,
    UpdateBillTo,
    ValidateBillingAddress,
    AddOrUpdateShipTo,
    UpdateSession,
    UpdateCart,
    LoadCart,
    ExecuteOnSuccessCallback,
    DispatchCompleteSaveShipping,
];

const saveShipping = createHandlerChainRunnerOptionalParameter(chain, {}, "SaveShipping");
export default saveShipping;
