/**
 * Copyright (c) 2020-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 {
  type PlainObject,
  AbstractServerClient,
  isNonEmptyString,
} from '@finos/legend-shared';
import type {
  LightProvider,
  TerminalServicesResponse,
  FetchProductsParams,
} from './models/Provider.js';
import type { DataProductSearchResponse } from './models/DataProductSearchResult.js';
import type {
  SubscriptionRequest,
  SubscriptionResponse,
} from './models/Subscription.js';
import type {
  CartItem,
  CartItemRequest,
  CartItemResponse,
  CartSummary,
  DeleteCartItemResponse,
  VendorAddonsSearchParams,
  VendorAddonsSearchResponse,
} from './models/Cart.js';
import type { OrderDetails } from './models/Order.js';
import type { FeedbackRequest, FeedbackResponse } from './models/Feedback.js';
import type { V1_EntitlementsLakehouseEnvironmentType } from '@finos/legend-graph';
import {
  type TerminalProductOrderResponse,
  OrderStatusCategory,
} from './models/TerminalProductOrder.js';
import type { AutosuggestResponse } from './models/AutosuggestResult.js';
import type { TaxonomyTreeResponse } from './models/Taxonomy.js';
import {
  FieldSearchType,
  type FieldSearchRequest,
  type GroupedFieldSearchResponse,
} from './models/FieldSearch.js';
import type {
  DatasetSearchResponse,
  FieldSearchResponse,
} from './models/DatasetSearchResult.js';
import type { EntitySearchResponse } from './models/EntitySearchResult.js';

export interface TrendingDataProductEntry {
  dataProductId?: string;
  deploymentId?: string;
  dataProductType: string;
  productName: string;
  productDescription?: string;
  dataProductName?: string;
  groupId?: string;
  artifactId?: string;
  versionId?: string;
  dataProductPath?: string;
  originType?: string;
  producerEnvironmentName?: string;
  producerEnvironmentType?: string;
  dataProductSource?: string;
  licenseTo?: string;
}

export interface MarketplaceServerClientConfig {
  serverUrl: string;
  subscriptionUrl: string;
}

export class MarketplaceServerClient extends AbstractServerClient {
  subscriptionUrl: string;
  constructor(config: MarketplaceServerClientConfig) {
    super({
      baseUrl: config.serverUrl,
      networkClientOptions: {
        // NOTE: with the way we setup this server, we allow any (*) origin for CORS
        // so here we have to explicit omit credentials
        // See https://fetch.spec.whatwg.org/#concept-request-credentials-mode
        credentials: 'omit',
      },
    });

    this.subscriptionUrl = config.subscriptionUrl;
  }

  // ------------------------------------------- Vendors -------------------------------------------

  private _vendors = (): string => `${this.baseUrl}/v1/vendors`;

  getVendors = (): Promise<PlainObject<LightProvider>[]> =>
    this.get(this._vendors());

  private _products = (): string => `${this.baseUrl}/v1/workflow/products`;

  fetchProducts = async (
    params: FetchProductsParams,
  ): Promise<PlainObject<TerminalServicesResponse>> => {
    const queryParams: Record<string, string | number | boolean> = {
      kerberos: params.kerberos,
      product_type: params.product_type,
      preferred_products: params.preferred_products,
    };

    if (params.page_number !== undefined) {
      queryParams.page_number = params.page_number;
    }
    if (params.page_size !== undefined) {
      queryParams.page_size = params.page_size;
    }
    if (params.search !== undefined && params.search !== '') {
      queryParams.search = params.search;
    }

    return this.get<PlainObject<TerminalServicesResponse>>(
      this._products(),
      undefined,
      undefined,
      queryParams,
    );
  };

  // ------------------------------------------- Search -------------------------------------------

  private _search = (): string => `${this.baseUrl}/v1/search`;

  private _autosuggest = (): string => `${this.baseUrl}/v1/autosuggest`;

  dataProductSearch = async (
    query: string,
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
    searchType: string = 'hybrid',
    searchFilters: string[] = [],
    pageSize: number = 12,
    pageNumber: number = 1,
    showAll: boolean = false,
  ): Promise<PlainObject<DataProductSearchResponse>> => {
    const filters = searchFilters.join('&search_filters=');
    const searchFilterParam = filters ? `&search_filters=${filters}` : '';
    return this.get<PlainObject<DataProductSearchResponse>>(
      `${this._search()}/dataProducts/${lakehouseEnv}?query=${query}&search_type=${searchType}${searchFilterParam}&page_size=${pageSize}&page_number=${pageNumber}&include_filter_metadata=true&show_all=${showAll}`,
    );
  };

  fieldSearch = async (
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
    params: FieldSearchRequest,
    signal?: AbortSignal,
  ): Promise<PlainObject<GroupedFieldSearchResponse>> =>
    this.get<PlainObject<GroupedFieldSearchResponse>>(
      `${this._search()}/fields/${lakehouseEnv}/grouped`,
      signal ? { signal } : {},
      undefined,
      {
        query: params.query,
        search_type: params.searchType ?? FieldSearchType.HYBRID,
        page_size: params.pageSize ?? 12,
        page_number: params.pageNumber ?? 1,
        ...(params.dataProductTypes?.length
          ? { data_product_types: params.dataProductTypes }
          : {}),
      },
    );

  getAutosuggestions = async (
    query: string,
    environment: string,
    limit: number = 5,
    signal?: AbortSignal,
  ): Promise<AutosuggestResponse> =>
    this.get<AutosuggestResponse>(
      `${this._autosuggest()}/dataProducts/${environment}`,
      signal ? { signal } : {},
      undefined,
      { query, limit },
    );

  datasetSearch = async (
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
    groupId: string,
    artifactId: string,
    versionId: string,
    path: string,
    query: string,
    options?: { searchType?: string; pageSize?: number; pageNumber?: number },
  ): Promise<PlainObject<DatasetSearchResponse>> =>
    this.get<PlainObject<DatasetSearchResponse>>(
      `${this._search()}/datasets/${lakehouseEnv}/legacy`,
      undefined,
      undefined,
      {
        group_id: groupId,
        artifact_id: artifactId,
        version_id: versionId,
        path,
        query,
        search_type: options?.searchType ?? 'hybrid',
        page_size: options?.pageSize ?? 20,
        page_number: options?.pageNumber ?? 1,
      },
    );

  legacyFieldSearch = async (
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
    groupId: string,
    artifactId: string,
    versionId: string,
    path: string,
    options?: { searchType?: string; pageSize?: number; pageNumber?: number },
  ): Promise<PlainObject<FieldSearchResponse>> =>
    this.get<PlainObject<FieldSearchResponse>>(
      `${this._search()}/fields/${lakehouseEnv}/legacy`,
      undefined,
      undefined,
      {
        group_id: groupId,
        artifact_id: artifactId,
        version_id: versionId,
        path,
        search_type: options?.searchType ?? 'hybrid',
        page_size: options?.pageSize ?? 20,
        page_number: options?.pageNumber ?? 1,
      },
    );

  entitySearch = async (
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
    query: string,
    options?: {
      groupId?: string;
      artifactId?: string;
      versionId?: string;
      path?: string;
      dataProductId?: string;
      deploymentId?: string;
      searchType?: string;
      pageSize?: number;
      pageNumber?: number;
      includePrimitiveFields?: boolean;
    },
  ): Promise<PlainObject<EntitySearchResponse>> =>
    this.get<PlainObject<EntitySearchResponse>>(
      `${this._search()}/entities/${lakehouseEnv}`,
      undefined,
      undefined,
      {
        query,
        group_id: options?.groupId,
        artifact_id: options?.artifactId,
        version_id: options?.versionId,
        path: options?.path,
        data_product_id: options?.dataProductId,
        deployment_id: options?.deploymentId,
        search_type: options?.searchType ?? 'hybrid',
        page_size: options?.pageSize ?? 10,
        page_number: options?.pageNumber ?? 1,
        include_primitive_fields: options?.includePrimitiveFields ?? true,
      },
    );

  // ------------------------------------------- Trending -------------------------------------------

  private readonly _analytics = (): string => `${this.baseUrl}/v1/analytics`;
  getTrendingDataProducts = async (
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
  ): Promise<TrendingDataProductEntry[]> =>
    this.get<TrendingDataProductEntry[]>(
      `${this._analytics()}/top-products/${lakehouseEnv}`,
      undefined,
      undefined,
    );

  // ------------------------------------------- Taxonomy -------------------------------------------

  private _taxonomyTree = (): string => `${this.baseUrl}/v1/taxonomy/tree`;

  getTaxonomyTree = async (
    lakehouseEnv: V1_EntitlementsLakehouseEnvironmentType,
    searchQuery?: string | undefined,
    refresh: boolean = false,
  ): Promise<TaxonomyTreeResponse> => {
    const queryParams: Record<string, string | boolean> = {
      refresh,
    };
    if (searchQuery) {
      queryParams.search_query = searchQuery;
    }
    return this.get<TaxonomyTreeResponse>(
      `${this._taxonomyTree()}/${lakehouseEnv}`,
      undefined,
      undefined,
      queryParams,
    );
  };

  // ------------------------------------------- Subscriptions -----------------------------------------

  private _subscriptions = (user: string): string =>
    `${this.baseUrl}/v1/service/subscription/${user}`;

  getSubscriptions = async (
    user: string,
  ): Promise<PlainObject<SubscriptionResponse>> =>
    this.get<PlainObject<SubscriptionResponse>>(this._subscriptions(user));

  cancelSubscriptions = async (
    cancellationRequest: SubscriptionRequest,
  ): Promise<PlainObject<{ message: string }>> =>
    this.post(
      `${this.baseUrl}/v1/workflow/cancel/subscription`,
      cancellationRequest,
    );

  // ------------------------------------------- Cart -------------------------------------------

  private _cart = (user: string): string => `${this.baseUrl}/v1/cart/${user}`;

  getCart = async (
    user: string,
  ): Promise<PlainObject<Record<number, CartItem[]>>> =>
    this.get<PlainObject<Record<number, CartItem[]>>>(this._cart(user));

  getCartSummary = async (user: string): Promise<PlainObject<CartSummary>> =>
    this.get<PlainObject<CartSummary>>(`${this._cart(user)}/summary`);

  clearCart = async (user: string): Promise<void> =>
    this.delete(this._cart(user));

  deleteCartItem = async (
    user: string,
    cartId: number,
    confirmDelete?: boolean,
  ): Promise<PlainObject<DeleteCartItemResponse>> =>
    this.delete(
      `${this._cart(user)}/item/${cartId}`,
      {},
      {},
      undefined,
      confirmDelete ? { confirm_delete: true } : undefined,
    );

  addToCart = async (
    user: string,
    cartItemData: CartItemRequest,
  ): Promise<PlainObject<CartItemResponse>> =>
    this.post(this._cart(user), cartItemData);

  searchVendorAddons = async (
    user: string,
    providerName: string,
    params?: VendorAddonsSearchParams,
    signal?: AbortSignal,
  ): Promise<PlainObject<VendorAddonsSearchResponse>> => {
    const queryParams: Record<string, string | number | boolean> = {};

    if (params?.page !== undefined) {
      queryParams.page = params.page;
    }
    if (params?.page_size !== undefined) {
      queryParams.page_size = params.page_size;
    }
    if (isNonEmptyString(params?.search)) {
      queryParams.search = params.search;
    }
    if (params?.sort_by_price !== undefined) {
      queryParams.sort_by_price = params.sort_by_price;
    }

    return this.get<PlainObject<VendorAddonsSearchResponse>>(
      `${this._cart(user)}/vendor-addons/${encodeURIComponent(providerName)}`,
      signal ? { signal } : {},
      undefined,
      queryParams,
    );
  };

  submitOrder = async (
    user: string,
    orderData: OrderDetails,
  ): Promise<PlainObject<unknown>> =>
    this.post(`${this.baseUrl}/v1/workflow/create/order`, orderData);

  // ------------------------------------------- Orders -------------------------------------------
  private _orders = (): string => `${this.baseUrl}/v1/workflow/fetch/orders`;

  fetchOrders = async (
    user: string,
    category: OrderStatusCategory = OrderStatusCategory.OPEN,
  ): Promise<PlainObject<TerminalProductOrderResponse>> =>
    this.get<PlainObject<TerminalProductOrderResponse>>(
      this._orders(),
      undefined,
      undefined,
      {
        kerberos: user,
        category,
      },
    );

  cancelOrder = async (cancelData: {
    order_id: string;
    kerberos: string;
    comments: string;
    process_instance_id: string;
  }): Promise<PlainObject<{ status_code: number; message: string }>> =>
    this.post(`${this.baseUrl}/v1/workflow/cancel/order`, cancelData);

  // ------------------------------------------- Feedback -------------------------------------------

  private _feedback = (): string => `${this.baseUrl}/v1/feedback`;

  submitFeedback = async (
    feedbackData: FeedbackRequest,
  ): Promise<PlainObject<FeedbackResponse>> =>
    this.post(this._feedback(), feedbackData);
}
