/**
 * Copyright (c) 2025-present, Goldman Sachs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  makeObservable,
  observable,
  action,
  flow,
  flowResult,
  computed,
} from 'mobx';
import {
  LogEvent,
  type GeneratorFn,
  assertErrorThrown,
  ActionState,
} from '@finos/legend-shared';
import {
  TerminalItemType,
  type CartItem,
  type CartItemRequest,
  type CartItemResponse,
  type CartSummary,
  type OrderDetails,
  type TerminalResult,
  type TraderProfile,
  type TraderProfileItem,
  RecommendationSource,
} from '@finos/legend-server-marketplace';
import type { LegendMarketplaceBaseStore } from '../LegendMarketplaceBaseStore.js';
import { APPLICATION_EVENT } from '@finos/legend-application';
import { toastManager } from '../../components/Toast/CartToast.js';

const boolToString = (val: boolean | undefined): 'true' | 'false' =>
  val ? 'true' : 'false';

enum BUSINESS_REASONS {
  NEW_HIRE = 'New Hire',
  NEW_ROLE = 'New Role',
  USER_MOVE = 'User Move',
  TRANSFER = 'Transfer',
  OTHER_REASON = 'Other Reason',
}

export class CartStore {
  readonly baseStore: LegendMarketplaceBaseStore;

  items: Record<number, CartItem[]> = {};
  targetUser: string | undefined = undefined;
  businessReason: string | undefined = undefined;
  readonly initState = ActionState.create();
  readonly loadingState = ActionState.create();
  readonly submitState = ActionState.create();
  open = false;
  cartSummary: CartSummary = {
    total_items: 0,
    total_cost: 0,
    formatted_total_cost: '$0.00',
  };

  constructor(baseStore: LegendMarketplaceBaseStore) {
    makeObservable(this, {
      items: observable,
      targetUser: observable,
      businessReason: observable,
      open: observable,
      cartSummary: observable,
      cartUser: computed,
      cartItemIds: computed,
      setOpen: action,
      setTargetUser: flow,
      setBusinessReason: action,
      initialize: flow,
      submitOrder: flow,
      refresh: flow,
      clearCart: flow,
      deleteCartItem: flow,
      addToCartWithAPI: flow,
      addOrderProfileItemsToCart: flow,
    });
    this.baseStore = baseStore;
  }

  private get currentUser(): string {
    return this.baseStore.applicationStore.identityService.currentUser;
  }

  get cartUser(): string {
    return this.targetUser ?? this.currentUser;
  }

  get cartItemIds(): Set<number> {
    const ids = new Set<number>();
    for (const vendorProfileId in this.items) {
      if (Object.prototype.hasOwnProperty.call(this.items, vendorProfileId)) {
        const cartItems = this.items[Number(vendorProfileId)];
        if (cartItems) {
          for (const item of cartItems) {
            ids.add(item.id);
          }
        }
      }
    }
    return ids;
  }

  setOpen(val: boolean): void {
    this.open = val;
  }

  *setTargetUser(val: string | undefined): GeneratorFn<void> {
    this.loadingState.inProgress();
    this.targetUser = val;
    this.items = {};
    this.cartSummary = {
      total_items: 0,
      total_cost: 0,
      formatted_total_cost: '$0.00',
    };
    this.businessReason = undefined;
    try {
      yield flowResult(this.refresh());
      this.loadingState.complete();
    } catch (error) {
      assertErrorThrown(error);
      this.baseStore.applicationStore.logService.error(
        LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE),
        `Failed to load cart for user: ${error.message}`,
      );
      this.loadingState.fail();
    }
  }

  setBusinessReason(val: string | undefined): void {
    this.businessReason = val;
  }

  isItemInCart(itemId: number): boolean {
    return this.cartItemIds.has(itemId);
  }

  /**
   * Returns the add-on items that depend on the given cart item.
   * When a Terminal is deleted, its associated add-ons (same vendor) must also be removed.
   */
  getDependentAddOns(cartId: number): CartItem[] {
    for (const vendorProfileId in this.items) {
      if (Object.prototype.hasOwnProperty.call(this.items, vendorProfileId)) {
        const cartItems = this.items[Number(vendorProfileId)];
        if (cartItems) {
          const target = cartItems.find((item) => item.cartId === cartId);
          if (target && target.category === TerminalItemType.TERMINAL) {
            return cartItems.filter(
              (item) =>
                item.cartId !== cartId &&
                item.category === TerminalItemType.ADD_ON,
            );
          }
        }
      }
    }
    return [];
  }

  *addToCartWithAPI(
    cartItemData: CartItemRequest,
    suppressSuccessToast = false,
  ): GeneratorFn<{
    success: boolean;
    recommendations?: TerminalResult[];
    message: string;
    totalCount?: number | null;
  }> {
    const user = this.cartUser;

    if (!user) {
      const message = 'User not authenticated';
      toastManager.error(message);
      return { success: false, message };
    }

    this.loadingState.inProgress();
    try {
      const response = (yield this.baseStore.marketplaceServerClient.addToCart(
        user,
        cartItemData,
      )) as CartItemResponse;

      yield flowResult(this.refresh());

      const responseMessage: string = response.message;
      if (!/^2\d\d$/.test(String(response.status_code))) {
        toastManager.warning(responseMessage);
      } else if (!suppressSuccessToast) {
        toastManager.success(responseMessage);
      }

      const recommendations: TerminalResult[] =
        response.marketplace_addons ?? response.marketplace_terminals ?? [];

      const parentVendorId = response.vendor_profile_id;
      if (parentVendorId && recommendations.length > 0) {
        recommendations.forEach((item) => {
          if (!item.vendorProfileId) {
            item.vendorProfileId = parentVendorId;
          }
          if (item.skipWorkflow === undefined) {
            item.skipWorkflow = true;
          }
        });
      }

      this.loadingState.complete();
      return {
        success: true,
        recommendations,
        message: responseMessage,
        totalCount: response.total_count,
      };
    } catch (error) {
      assertErrorThrown(error);
      const message = `Failed to add ${cartItemData.productName} to cart: ${error.message}`;
      toastManager.error(message);
      this.loadingState.fail();
      return { success: false, message };
    }
  }

  /**
   * Adds a list of order-profile items to the cart, skipping already-owned ones.
   * Each item is added sequentially so that vendor-profile items can be added
   * before their associated add-ons.
   */
  *addOrderProfileItemsToCart(
    items: TraderProfileItem[],
    suppressSuccessToast = false,
  ): GeneratorFn<void> {
    for (const item of items) {
      if (item.isOwned) {
        continue;
      }
      yield flowResult(
        this.addToCartWithAPI(
          {
            id: item.id,
            productName: item.productName,
            providerName: item.providerName,
            category: item.category,
            price: item.price,
            description: item.description ?? '',
            isOwned: boolToString(item.isOwned),
            ...(item.model === null || item.model === undefined
              ? {}
              : { model: item.model }),
            skipWorkflow: true,
            ...(item.isMandatory === undefined
              ? {}
              : { isMandatory: item.isMandatory }),
            ...(item.vendorProfileId === undefined
              ? {}
              : { vendorProfileId: item.vendorProfileId }),
            ...(item.permissionId === undefined
              ? {}
              : { permissionId: item.permissionId }),
          },
          suppressSuccessToast,
        ),
      );
    }
  }

  /**
   * Returns true when all non-owned items of the profile are present in the
   * cart.  For multiselect profiles, at least one complete terminal bundle
   * (terminal + its associated add-ons) must be fully in the cart.
   */
  isOrderProfileInCart(profile: TraderProfile): boolean {
    const nonOwnedItems = profile.items.filter((item) => !item.isOwned);
    const nonOwnedTerminals = nonOwnedItems.filter((item) => item.isTerminal);
    if (profile.multiselect) {
      return nonOwnedTerminals.some((terminal) => {
        const selectedModel = terminal.model ?? null;
        const bundleItems = [
          terminal,
          ...profile.items.filter(
            (item) =>
              !item.isTerminal &&
              !item.isOwned &&
              (selectedModel === null || item.model === selectedModel),
          ),
        ];
        return bundleItems.every((item) => this.isItemInCart(item.id));
      });
    }
    return (
      nonOwnedItems.length > 0 &&
      nonOwnedItems.every((item) => this.isItemInCart(item.id))
    );
  }

  providerToCartRequest(provider: TerminalResult): CartItemRequest {
    const isInventory = provider.source === RecommendationSource.INVENTORY;
    return {
      id: isInventory ? (provider.permissionId ?? provider.id) : provider.id,
      productName: provider.productName,
      providerName: provider.providerName,
      category: provider.category,
      price: provider.price,
      description: provider.description,
      isOwned: boolToString(provider.isOwned),
      model: provider.model ?? provider.productName,
      skipWorkflow: provider.skipWorkflow ?? false,
      ...(provider.vendorProfileId !== undefined && {
        vendorProfileId: provider.vendorProfileId,
      }),
      ...(provider.permissionId !== undefined && {
        permissionId: provider.permissionId,
      }),
      ...(provider.source !== undefined && {
        source: provider.source,
      }),
    };
  }

  *initialize(): GeneratorFn<void> {
    if (!this.initState.isInInitialState) {
      return;
    }
    this.initState.inProgress();
    try {
      yield flowResult(this.refresh());
      this.initState.complete();
    } catch (error) {
      assertErrorThrown(error);
      this.baseStore.applicationStore.logService.warn(
        LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE),
        'Cart initialization failed, using empty state',
      );
      this.initState.fail();
    }
  }

  *refresh(): GeneratorFn<void> {
    const user = this.cartUser;
    if (!user) {
      return;
    }

    try {
      this.items = (yield this.baseStore.marketplaceServerClient.getCart(
        user,
      )) as Record<number, CartItem[]>;

      this.cartSummary =
        (yield this.baseStore.marketplaceServerClient.getCartSummary(
          user,
        )) as CartSummary;
    } catch (error) {
      assertErrorThrown(error);
      this.baseStore.applicationStore.logService.error(
        LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE),
        `Failed to refresh cart: ${error.message}`,
      );
    }
  }

  *submitOrder(): GeneratorFn<void> {
    if (!this.businessReason) {
      toastManager.warning(
        'Please select a business reason before submitting order',
      );
      return;
    }
    if (this.cartSummary.total_items === 0) {
      toastManager.warning('Cart is empty - nothing to order');
      return;
    }
    const user = this.currentUser;
    if (!user) {
      toastManager.error('User not authenticated');
      return;
    }

    this.submitState.inProgress();
    try {
      const orderData: OrderDetails = {
        ordered_by: user,
        kerberos: this.cartUser,
        order_items: this.items,
        business_justification: this.businessReason,
      };

      yield this.baseStore.marketplaceServerClient.submitOrder(user, orderData);

      toastManager.notify('Order created successfully!', 'success');

      yield flowResult(this.refresh());
      this.setBusinessReason(undefined);
      this.open = false;
      this.submitState.complete();
    } catch (error) {
      assertErrorThrown(error);
      const message = `Failed to submit order: ${error.message}`;
      toastManager.error(message);
      this.submitState.fail();
    }
  }

  *clearCart(): GeneratorFn<void> {
    const user = this.cartUser;
    if (!user) {
      toastManager.error('User not authenticated');
      return;
    }

    this.loadingState.inProgress();
    try {
      yield this.baseStore.marketplaceServerClient.clearCart(user);
      yield flowResult(this.refresh());
      toastManager.success('Cart cleared successfully');
      this.loadingState.complete();
    } catch (error) {
      assertErrorThrown(error);
      const message = `Failed to clear cart: ${error.message}`;
      toastManager.error(message);
      this.loadingState.fail();
    }
  }

  *deleteCartItem(cartId: number, confirmDelete?: boolean): GeneratorFn<void> {
    const user = this.cartUser;
    if (!user) {
      toastManager.error('User not authenticated');
      return;
    }

    this.loadingState.inProgress();
    try {
      yield this.baseStore.marketplaceServerClient.deleteCartItem(
        user,
        cartId,
        confirmDelete,
      );

      yield flowResult(this.refresh());
      toastManager.success('Item removed successfully');
      this.loadingState.complete();
    } catch (error) {
      assertErrorThrown(error);
      const message = `Failed to remove item: ${error.message}`;
      toastManager.error(message);
      this.loadingState.fail();
    }
  }

  static readonly BUSINESS_REASONS = BUSINESS_REASONS;
}
