import { MagentoApiError, type MagentoClient } from './client.js'
import { and, type SearchCriteria } from './search-criteria.js'
import type {
  AttributeSet, Category, Invoice, Order, Product, ProductAttribute, SearchResult, Shipment, SourceItem, StoreConfig, StoreView, Website,
} from './types.js'

const PRODUCT_ENTITY_TYPE_ID = 4

/** Typed, read-only endpoints used by sync and the MCP server. */
export class MagentoApi {
  constructor(readonly client: MagentoClient) {}

  // ── Stores ──────────────────────────────────────────────────────────────
  storeViews() { return this.client.get<StoreView[]>('/V1/store/storeViews') }
  storeConfigs() { return this.client.get<StoreConfig[]>('/V1/store/storeConfigs') }
  websites() { return this.client.get<Website[]>('/V1/store/websites') }

  // ── Catalog reference data ─────────────────────────────────────────────
  productAttributes() {
    return this.client.paginate<ProductAttribute>('/V1/products/attributes', { pageSize: 200 })
  }

  attributeSets() {
    return this.client.paginate<AttributeSet>('/V1/eav/attribute-sets/list', {
      filterGroups: and({ field: 'entity_type_id', value: PRODUCT_ENTITY_TYPE_ID }),
      pageSize: 200,
    })
  }

  attributeSetMembers(attributeSetId: number) {
    return this.client.get<Array<{ attribute_code: string; is_required: boolean }>>(`/V1/products/attribute-sets/${attributeSetId}/attributes`)
  }

  categories(store: string) {
    return this.client.paginate<Category>('/V1/categories/list', { pageSize: 500 }, { store })
  }

  // ── Products ───────────────────────────────────────────────────────────
  products(store: string, criteria: SearchCriteria) {
    return this.client.paginate<Product>('/V1/products', criteria, { store })
  }

  productsUpdatedSince(store: string, since: string | null, pageSize = 200) {
    return this.products(store, {
      filterGroups: since ? and({ field: 'updated_at', value: since, condition: 'gteq' }) : [],
      sortOrders: [{ field: 'updated_at', direction: 'ASC' }, { field: 'entity_id', direction: 'ASC' }],
      pageSize,
    })
  }

  /** Every product id + sku (cheap; used for delete reconciliation). */
  productIds(pageSize = 1000) {
    return this.client.paginate<{ id: number; sku: string }>('/V1/products', { pageSize }, { fields: 'items[id,sku],total_count' })
  }

  async product(sku: string, store = 'all'): Promise<Product | null> {
    try {
      return await this.client.get<Product>(`/V1/products/${encodeURIComponent(sku)}`, { store })
    } catch (err) {
      if (err instanceof MagentoApiError && err.isNotFound) return null
      throw err
    }
  }

  // ── Inventory ──────────────────────────────────────────────────────────
  /** MSI source items. Returns null when MSI is disabled/absent (404). */
  async *sourceItems(pageSize = 500): AsyncGenerator<SourceItem[]> {
    try {
      yield* this.client.paginate<SourceItem>('/V1/inventory/source-items', { pageSize })
    } catch (err) {
      if (err instanceof MagentoApiError && err.isNotFound) return
      throw err
    }
  }

  async salableQty(sku: string, stockId: number): Promise<number | null> {
    try {
      return await this.client.get<number>(`/V1/inventory/get-product-salable-quantity/${encodeURIComponent(sku)}/${stockId}`)
    } catch (err) {
      // Non-stock products (configurable, bundle) and non-MSI stores answer 400/404.
      if (err instanceof MagentoApiError && (err.status === 400 || err.status === 404)) return null
      throw err
    }
  }

  async isSalable(sku: string, stockId: number): Promise<boolean | null> {
    try {
      return await this.client.get<boolean>(`/V1/inventory/is-product-salable/${encodeURIComponent(sku)}/${stockId}`)
    } catch (err) {
      if (err instanceof MagentoApiError && (err.status === 400 || err.status === 404)) return null
      throw err
    }
  }

  /** Shopper-facing price incl. catalog price rules, via storefront GraphQL. */
  async storefrontPrice(sku: string, store: string) {
    const data = await this.client.graphql<{
      products: { items: Array<{ sku: string; price_range: { minimum_price: StorefrontPrice; maximum_price: StorefrontPrice } }> }
    }>(
      `query($sku: String!) { products(filter: { sku: { eq: $sku } }) { items { sku price_range {
        minimum_price { regular_price { value currency } final_price { value currency } discount { amount_off percent_off } }
        maximum_price { regular_price { value currency } final_price { value currency } discount { amount_off percent_off } }
      } } } }`,
      { sku },
      store,
    )
    return data.products.items.find(i => i.sku === sku) ?? null
  }

  // ── Staff login (for the MCP authorization server) ────────────────────
  /**
   * Check Magento admin credentials. Uses the Google Authenticator 2FA endpoint when an OTP is
   * given (Magento 2.4 enforces admin 2FA by default), otherwise the plain admin-token endpoint.
   * Returns true/false; the admin token Magento issues is discarded (it expires on its own).
   */
  async verifyAdminLogin(username: string, password: string, otp?: string): Promise<{ ok: boolean; reason?: string }> {
    try {
      const token = otp
        ? await this.client.postAnonymous<string>('/V1/tfa/provider/google/authenticate', { username, password, otp })
        : await this.client.postAnonymous<string>('/V1/integration/admin/token', { username, password })
      return { ok: typeof token === 'string' && token.length > 0 }
    } catch (err) {
      if (err instanceof MagentoApiError && err.status < 500) return { ok: false, reason: err.message }
      throw err
    }
  }

  // ── Sales (live) ───────────────────────────────────────────────────────
  searchOrders(criteria: SearchCriteria) {
    return this.client.search<Order>('/V1/orders', criteria)
  }

  async orderByIncrementId(incrementId: string): Promise<Order | null> {
    const res = await this.searchOrders({ filterGroups: and({ field: 'increment_id', value: incrementId }), pageSize: 1 })
    return res.items?.[0] ?? null
  }

  invoicesForOrder(orderId: number) {
    return this.client.search<Invoice>('/V1/invoices', { filterGroups: and({ field: 'order_id', value: orderId }), pageSize: 50 })
  }

  shipmentsForOrder(orderId: number) {
    return this.client.search<Shipment>('/V1/shipments', { filterGroups: and({ field: 'order_id', value: orderId }), pageSize: 50 })
  }
}

export interface StorefrontPrice {
  regular_price: { value: number; currency: string }
  final_price: { value: number; currency: string }
  discount: { amount_off: number; percent_off: number }
}

export type { SearchResult }
