import { CatalogProductRecord } from '@/types';
import { PRODUCTS_QUERY, SHOP_INFO_QUERY, COLLECTIONS_QUERY, PRODUCT_TAGS_QUERY } from './graphql';
import { logger } from '@/lib/logger';
import { getShopifyAccessToken, clearCachedShopifyToken } from './token-manager';
import { SHOPIFY_CONFIG } from '@/config/shopify';

interface ShopifyGraphQLResponse<T> {
  data?: T;
  errors?: Array<{ message: string; locations?: unknown }>;
  extensions?: {
    cost?: {
      requestedQueryCost: number;
      actualQueryCost: number;
      throttleStatus: {
        maximumAvailable: number;
        currentlyAvailable: number;
        restoreRate: number;
      };
    };
  };
}

export interface ProductQueryResult {
  products: {
    pageInfo: { hasNextPage: boolean; endCursor: string | null };
    edges: Array<{
      node: {
        id: string;
        title: string;
        descriptionHtml?: string;
        description: string;
        vendor: string;
        productType?: string;
        tags?: string[];
        collections?: {
          edges: Array<{
            node: {
              id: string;
              title: string;
              handle: string;
            };
          }>;
        };
        handle: string;
        status?: string;
        tracksInventory?: boolean;
        totalInventory?: number;
        media?: {
          edges: Array<{
            node: {
              mediaContentType: string;
              alt?: string;
              image?: { id?: string; url: string; altText?: string; width?: number; height?: number };
              sources?: Array<{ url: string; format: string; mimeType: string }>;
              originalSource?: { url: string };
              originUrl?: string;
              embeddedUrl?: string;
            };
          }>;
        };
        images: { edges: Array<{ node: { id?: string; url: string; altText?: string } }> };
        variants: {
          edges: Array<{
            node: {
              id: string;
              title: string;
              sku: string;
              barcode: string;
              price: string;
              compareAtPrice: string | null;
              inventoryQuantity: number;
              inventoryPolicy?: string;
              availableForSale: boolean;
              selectedOptions?: Array<{ name: string; value: string }>;
              image?: { id?: string; url: string; altText?: string };
              inventoryItem?: {
                measurement?: {
                  weight?: {
                    unit: string;
                    value: number;
                  };
                };
              };
            };
          }>;
        };
      };
    }>;
  };
}

/**
 * Intelligent categorization resolving Meta and Google standard taxonomy.
 */
export function resolveProductCategory(title: string, productType?: string, tags?: string[]): {
  google_product_category: string;
  fb_product_category: string;
} {
  const text = `${title} ${productType || ''} ${(tags || []).join(' ')}`.toLowerCase();

  if (text.includes('gift card') || text.includes('voucher') || text.includes('certificate')) {
    return {
      google_product_category: 'Arts & Entertainment > Party & Celebration > Gift Giving > Gift Cards & Certificates',
      fb_product_category: 'Gift Cards & Certificates',
    };
  }
  if (text.includes('snowboard') || text.includes('snow board')) {
    return {
      google_product_category: 'Sporting Goods > Outdoor Recreation > Winter Sports > Skiing & Snowboarding > Snowboards',
      fb_product_category: 'Sporting Goods > Snowboards',
    };
  }
  if (text.includes('ski wax') || text.includes('wax') || text.includes('board tuning')) {
    return {
      google_product_category: 'Sporting Goods > Outdoor Recreation > Winter Sports > Skiing & Snowboarding > Ski & Snowboard Maintenance',
      fb_product_category: 'Sporting Goods > Winter Sports Accessories',
    };
  }
  if (text.includes('cbd') || text.includes('hemp') || text.includes('sleep spray') || text.includes('dropper') || text.includes('oil') || text.includes('mist')) {
    return {
      google_product_category: 'Health & Beauty > Health Care > Holistic & Alternative Medicine',
      fb_product_category: 'Health & Beauty > Personal Care',
    };
  }
  if (text.includes('jacket') || text.includes('coat') || text.includes('parka') || text.includes('outerwear') || text.includes('thermal')) {
    return {
      google_product_category: 'Apparel & Accessories > Clothing > Outerwear > Coats & Jackets',
      fb_product_category: 'Clothing > Coats & Jackets',
    };
  }
  if (text.includes('beanie') || text.includes('cap') || text.includes('hat') || text.includes('headwear')) {
    return {
      google_product_category: 'Apparel & Accessories > Clothing Accessories > Hats > Beanies',
      fb_product_category: 'Clothing Accessories > Hats',
    };
  }
  if (text.includes('dress') || text.includes('gown') || text.includes('maxi') || text.includes('cocktail') || text.includes('slip dress')) {
    return {
      google_product_category: 'Apparel & Accessories > Clothing > Dresses',
      fb_product_category: 'Clothing > Dresses',
    };
  }
  if (text.includes('coffee') || text.includes('bean') || text.includes('roast') || text.includes('tea') || text.includes('beverage')) {
    return {
      google_product_category: 'Food, Beverages & Tobacco > Beverages > Coffee',
      fb_product_category: 'Food & Beverages > Coffee',
    };
  }
  if (text.includes('scarf') || text.includes('shawl') || text.includes('wrap')) {
    return {
      google_product_category: 'Apparel & Accessories > Clothing Accessories > Scarves & Shawls',
      fb_product_category: 'Clothing Accessories > Scarves',
    };
  }
  if (text.includes('shirt') || text.includes('t-shirt') || text.includes('tee') || text.includes('hoodie') || text.includes('sweater')) {
    return {
      google_product_category: 'Apparel & Accessories > Clothing > Shirts & Tops',
      fb_product_category: 'Clothing > Shirts & Tops',
    };
  }

  if (productType && productType.trim()) {
    return {
      google_product_category: productType.trim(),
      fb_product_category: productType.trim(),
    };
  }

  return {
    google_product_category: 'Apparel & Accessories',
    fb_product_category: 'General Merchandise',
  };
}

function cleanDescriptionText(raw?: string): string {
  if (!raw) return '';
  return raw
    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
    .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
    .replace(/<br\s*[\/]?>/gi, '\n')
    .replace(/<\/p>/gi, '\n\n')
    .replace(/<\/li>/gi, '\n')
    .replace(/<\/h[1-6]>/gi, '\n\n')
    .replace(/<[^>]*>/g, ' ')
    .replace(/&nbsp;/gi, ' ')
    .replace(/&amp;/gi, '&')
    .replace(/&quot;/gi, '"')
    .replace(/&#39;/gi, "'")
    .replace(/&apos;/gi, "'")
    .replace(/&lt;/gi, '<')
    .replace(/&gt;/gi, '>')
    .replace(/&#x27;/gi, "'")
    .replace(/&#x2F;/gi, '/')
    .replace(/&rsquo;/gi, "'")
    .replace(/&lsquo;/gi, "'")
    .replace(/&ldquo;/gi, '"')
    .replace(/&rdquo;/gi, '"')
    .replace(/&ndash;/gi, '-')
    .replace(/&mdash;/gi, '--')
    .replace(/&bull;/gi, '•')
    .replace(/&trade;/gi, '™')
    .replace(/&copy;/gi, '©')
    .replace(/&reg;/gi, '®')
    .replace(/[ \t]+/g, ' ')
    .replace(/\n\s*\n\s*\n+/g, '\n\n')
    .trim();
}

function normalizeUrl(url?: string | null): string {
  if (!url) return '';
  let clean = url.trim();
  if (clean.startsWith('//')) {
    clean = 'https:' + clean;
  } else if (clean.startsWith('http://')) {
    clean = clean.replace(/^http:\/\//i, 'https://');
  }
  return clean;
}

export class ShopifyClient {
  private shopDomain: string;
  private accessToken?: string;
  private apiVersion = SHOPIFY_CONFIG.apiVersion;

  constructor(shopDomain: string, accessToken?: string) {
    const clean = shopDomain.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim().toLowerCase();
    this.shopDomain = clean.includes('.') ? clean : `${clean}.myshopify.com`;
    if (accessToken && !accessToken.startsWith('mock_') && !accessToken.startsWith('shpat_mock_')) {
      this.accessToken = accessToken;
    }
  }

  private async getValidToken(forceRefresh = false): Promise<string> {
    if (!forceRefresh) {
      try {
        const token = await getShopifyAccessToken(this.shopDomain, false);
        this.accessToken = token;
        return token;
      } catch (err) {
        if (this.accessToken) return this.accessToken;
        throw err;
      }
    }
    const token = await getShopifyAccessToken(this.shopDomain, true);
    this.accessToken = token;
    return token;
  }

  async query<T>(query: string, variables: Record<string, unknown> = {}, isRetry = false): Promise<T> {
    const url = `https://${this.shopDomain}/admin/api/${this.apiVersion}/graphql.json`;
    const token = await this.getValidToken(isRetry);

    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': token,
      },
      body: JSON.stringify({ query, variables }),
    });

    if (!response.ok) {
      const errText = await response.text().catch(() => '');
      let parsedMsg = '';
      try {
        const json = JSON.parse(errText);
        parsedMsg = json.errors || json.error || '';
      } catch {}
      const reasonHeader = response.headers.get('x-shopify-api-reason') || '';
      const details = [parsedMsg, reasonHeader, errText].filter(Boolean)[0] || response.statusText;

      const isTokenIssue =
        response.status === 401 ||
        response.status === 403 ||
        details.toLowerCase().includes('non-expiring') ||
        details.toLowerCase().includes('token');

      if (!isRetry && isTokenIssue) {
        logger.warn(`[ShopifyClient] GraphQL query received HTTP ${response.status} (${details}). Retrying with fresh token...`);
        clearCachedShopifyToken(this.shopDomain);
        try {
          return await this.query<T>(query, variables, true);
        } catch (retryErr) {
          logger.error(`[ShopifyClient] GraphQL query retry failed:`, retryErr);
        }
      }

      if (response.status === 403) {
        throw new Error(
          `Shopify API error: 403 Forbidden for ${this.shopDomain}. Missing required permissions ('read_products'). Re-authorization required: ${details}`
        );
      }
      throw new Error(`Shopify API error: ${response.status} ${response.statusText} - ${details}`);
    }

    const result: ShopifyGraphQLResponse<T> = await response.json();
    if (result.errors && result.errors.length > 0) {
      const errorMsg = result.errors.map((e) => e.message).join('; ');

      if (!isRetry && (errorMsg.toLowerCase().includes('access denied') || errorMsg.toLowerCase().includes('token') || errorMsg.toLowerCase().includes('non-expiring'))) {
        logger.warn(`[ShopifyClient] GraphQL result error '${errorMsg}'. Retrying with fresh token...`);
        clearCachedShopifyToken(this.shopDomain);
        try {
          return await this.query<T>(query, variables, true);
        } catch {}
      }

      if (errorMsg.toLowerCase().includes('access denied') || errorMsg.toLowerCase().includes('read_products')) {
        throw new Error(
          `Shopify GraphQL error: Access denied for ${this.shopDomain}. Required access: 'read_products' scope. Re-authorization required: ${errorMsg}`
        );
      }
      throw new Error(`Shopify GraphQL error: ${errorMsg}`);
    }

    // Handle rate-limit throttling
    if (result.extensions?.cost?.throttleStatus) {
      const available = result.extensions.cost.throttleStatus.currentlyAvailable;
      if (available < 100) {
        logger.warn(`Shopify API rate limit throttling: ${available} points available`);
        await new Promise((resolve) => setTimeout(resolve, 500));
      }
    }

    return result.data as T;
  }

  /**
   * Fetches all products and variants directly from Shopify via GraphQL or REST Admin API.
   * Automatically handles client credentials grant and token renewal.
   */
  async fetchAllProductsForCatalog(shopId: string): Promise<CatalogProductRecord[]> {
    const products: CatalogProductRecord[] = [];
    const now = new Date().toISOString();
    let token = await this.getValidToken();

    let liveFetchSucceeded = false;
    let lastError = '';

    // 1. Attempt live Shopify GraphQL API
    try {
      let hasNextPage = true;
      let cursor: string | null = null;
      logger.info(`Starting live Shopify GraphQL product fetch for shop: ${this.shopDomain}`);

      while (hasNextPage) {
        const res: ProductQueryResult = await this.query<ProductQueryResult>(PRODUCTS_QUERY, {
          first: 50,
          after: cursor,
        });
        if (!res.products || !res.products.edges || res.products.edges.length === 0) break;

        for (const prodEdge of res.products.edges) {
          const prod = prodEdge.node;

          // Exclude only explicitly draft or archived products
          const statusUpper = (prod.status || '').toUpperCase();
          if (statusUpper === 'DRAFT' || statusUpper === 'ARCHIVED') {
            continue;
          }

          const rawDesc = prod.descriptionHtml || prod.description || '';
          const cleanDesc = cleanDescriptionText(rawDesc) || prod.title;

          // Collect all images from both `images` connection and `media` (MediaImage)
          const collectedImages: string[] = [];
          for (const imgEdge of prod.images?.edges || []) {
            const u = normalizeUrl(imgEdge.node?.url);
            if (u && !collectedImages.includes(u) && !u.includes('placeholder.com')) {
              collectedImages.push(u);
            }
          }

          let videoUrl: string | null = null;
          if (prod.media?.edges) {
            for (const mediaEdge of prod.media.edges) {
              const m = mediaEdge.node;
              if (m.mediaContentType === 'IMAGE' && m.image?.url) {
                const u = normalizeUrl(m.image.url);
                if (u && !collectedImages.includes(u) && !u.includes('placeholder.com')) {
                  collectedImages.push(u);
                }
              } else if (m.mediaContentType === 'VIDEO' && !videoUrl) {
                videoUrl = m.originalSource?.url || m.sources?.[0]?.url || null;
              } else if (m.mediaContentType === 'EXTERNAL_VIDEO' && !videoUrl) {
                videoUrl = m.originUrl || m.embeddedUrl || null;
              }
            }
          }

          const mainImage = collectedImages[0] || '';
          const additionalImages = collectedImages.slice(1);
          const categories = resolveProductCategory(prod.title, prod.productType, prod.tags);

          for (const varEdge of prod.variants.edges) {
            const v = varEdge.node;
            const varImage = normalizeUrl(v.image?.url || '') || mainImage;
            const variantNum = v.id.replace(/^.*\//, '');
            const productNum = prod.id.replace(/^.*\//, '');

            const priceNum = parseFloat(v.price) || 0;
            const comparePriceNum = v.compareAtPrice ? parseFloat(v.compareAtPrice) : null;
            const isAvailable =
              v.availableForSale === true ||
              prod.tracksInventory === false ||
              v.inventoryPolicy === 'CONTINUE' ||
              (v.availableForSale !== false && (v.inventoryQuantity === undefined || v.inventoryQuantity === null || v.inventoryQuantity > 0));

            // Extract variant options if any (e.g. Color / Size)
            const optionsText = (v.selectedOptions || [])
              .filter(o => o && o.name !== 'Title' && o.value !== 'Default Title')
              .map(o => `${o.name}: ${o.value}`)
              .join(', ');

            const weightVal = v.inventoryItem?.measurement?.weight?.value ?? null;
            const rawUnit = v.inventoryItem?.measurement?.weight?.unit;
            let weightUnit: string | null = null;
            if (rawUnit) {
              const u = rawUnit.toLowerCase();
              if (u.includes('pound') || u === 'lb' || u === 'lbs') weightUnit = 'lb';
              else if (u.includes('kilo') || u === 'kg') weightUnit = 'kg';
              else if (u.includes('gram') || u === 'g') weightUnit = 'g';
              else if (u.includes('ounce') || u === 'oz') weightUnit = 'oz';
              else weightUnit = u;
            }

            products.push({
              id: `prod_${productNum}_${variantNum}`,
              shop_id: shopId,
              shopify_product_id: prod.id,
              shopify_variant_id: v.id,
              meta_product_id: `FB_${productNum}_${variantNum}`,
              sku: v.sku || `SKU-${variantNum}`,
              barcode: v.barcode || null,
              title: prod.title,
              variant_title: v.title && v.title !== 'Default Title' ? v.title : null,
              description: cleanDesc,
              body_html: rawDesc || null,
              price: priceNum,
              compare_at_price: comparePriceNum,
              currency: 'USD',
              availability: isAvailable ? 'in stock' : 'out of stock',
              inventory_quantity: v.inventoryQuantity ?? (prod.tracksInventory === false ? 100 : 1),
              image_url: varImage || mainImage || '',
              additional_images: additionalImages,
              video_url: videoUrl,
              product_url: `https://${this.shopDomain}/products/${prod.handle}?variant=${variantNum}`,
              google_product_category: categories.google_product_category,
              fb_product_category: categories.fb_product_category,
              brand: prod.vendor || 'Store Brand',
              condition: 'new',
              product_type: prod.productType || undefined,
              weight: weightVal,
              weight_unit: weightUnit,
              custom_label_0: prod.productType || undefined,
              custom_label_1: (prod.tags || []).slice(0, 3).join(', ') || undefined,
              custom_label_2: optionsText || undefined,
              tags: prod.tags || [],
              collections: (prod.collections?.edges || []).map(e => e.node.title),
              collection_ids: (prod.collections?.edges || []).map(e => String(e.node.id).replace(/^.*\//, '')),
              status: 'synced',
              last_synced_at: now,
            });
          }
        }

        hasNextPage = res.products.pageInfo.hasNextPage;
        cursor = res.products.pageInfo.endCursor;
      }

      if (products.length > 0) {
        liveFetchSucceeded = true;
      }
    } catch (gqlErr: any) {
      lastError = gqlErr.message || String(gqlErr);
      logger.warn(`Shopify GraphQL direct sync encountered: ${lastError}. Attempting REST Admin API...`);
    }

    // 2. Attempt live Shopify REST Admin API if GraphQL was throttled or failed
    if (!liveFetchSucceeded) {
      try {
        let restToken = await this.getValidToken();
        const restUrl = `https://${this.shopDomain}/admin/api/${this.apiVersion}/products.json?limit=250`;
        let restRes = await fetch(restUrl, {
          headers: {
            'Content-Type': 'application/json',
            'X-Shopify-Access-Token': restToken,
          },
        });

        if (!restRes.ok && (restRes.status === 401 || restRes.status === 403)) {
          const reasonHeader = restRes.headers.get('x-shopify-api-reason') || '';
          logger.warn(`[ShopifyClient] REST API product fetch received ${restRes.status} (${reasonHeader}). Retrying with fresh token...`);
          clearCachedShopifyToken(this.shopDomain);
          try {
            restToken = await this.getValidToken(true);
            restRes = await fetch(restUrl, {
              headers: {
                'Content-Type': 'application/json',
                'X-Shopify-Access-Token': restToken,
              },
            });
          } catch (refErr) {
            logger.error(`[ShopifyClient] REST product fetch token refresh failed:`, refErr);
          }
        }

        if (restRes.ok) {
          const restData = await restRes.json();
          const rawProds = restData.products || [];

          for (const prod of rawProds) {
            // Exclude only explicitly draft or archived products
            const statusLower = String(prod.status || '').toLowerCase();
            if (statusLower === 'draft' || statusLower === 'archived') {
              continue;
            }

            const prodNum = String(prod.id);
            const mainImg = normalizeUrl(prod.image?.src || prod.images?.[0]?.src || '');
            const addImgs = (prod.images || []).slice(1).map((i: any) => normalizeUrl(i.src || i)).filter(Boolean);
            const tags = typeof prod.tags === 'string' ? prod.tags.split(',').map((t: string) => t.trim()) : prod.tags || [];
            const categories = resolveProductCategory(prod.title, prod.product_type, tags);
            const cleanDesc = cleanDescriptionText(prod.body_html || '') || prod.title;

            for (const v of prod.variants || []) {
              const varNum = String(v.id);
              const matchedImg = v.image_id
                ? prod.images?.find((img: any) => img.id === v.image_id)?.src
                : null;
              const varImg = normalizeUrl(matchedImg) || mainImg;

              const isAvailable =
                v.available === true ||
                v.inventory_policy === 'continue' ||
                v.inventory_management === null ||
                (v.inventory_quantity !== undefined && v.inventory_quantity > 0);

              const priceNum = parseFloat(v.price) || 0;
              const comparePriceNum = v.compare_at_price ? parseFloat(v.compare_at_price) : null;

              products.push({
                id: `prod_${prodNum}_${varNum}`,
                shop_id: shopId,
                shopify_product_id: `gid://shopify/Product/${prodNum}`,
                shopify_variant_id: `gid://shopify/ProductVariant/${varNum}`,
                meta_product_id: `FB_${prodNum}_${varNum}`,
                sku: v.sku || `SKU-${varNum}`,
                barcode: v.barcode || null,
                title: prod.title,
                variant_title: v.title && v.title !== 'Default Title' ? v.title : null,
                description: cleanDesc,
                price: priceNum,
                compare_at_price: comparePriceNum,
                currency: 'USD',
                availability: isAvailable ? 'in stock' : 'out of stock',
                inventory_quantity: v.inventory_quantity ?? 1,
                image_url: varImg || mainImg || '',
                additional_images: addImgs,
                video_url: null,
                product_url: `https://${this.shopDomain}/products/${prod.handle}?variant=${varNum}`,
                google_product_category: categories.google_product_category,
                fb_product_category: categories.fb_product_category,
                brand: prod.vendor || 'Store Brand',
                condition: 'new',
                custom_label_0: prod.product_type || undefined,
                custom_label_1: tags.slice(0, 3).join(', ') || undefined,
                tags: tags,
                status: 'synced',
                last_synced_at: now,
              });
            }
          }

          if (products.length > 0) {
            liveFetchSucceeded = true;
          }
        } else {
          const restErrText = await restRes.text().catch(() => '');
          let restErrMsg = '';
          try {
            const parsed = JSON.parse(restErrText);
            restErrMsg = parsed.errors || parsed.error || '';
          } catch {
            restErrMsg = restErrText;
          }
          const reasonHeader = restRes.headers.get('x-shopify-api-reason') || '';
          const finalRestMsg = [restErrMsg, reasonHeader].filter(Boolean).join(' - ') || restRes.statusText;

          if (restRes.status === 403) {
            lastError = `Shopify REST API responded with HTTP 403 (Forbidden): Store '${this.shopDomain}' has not granted 'read_products' permission (${finalRestMsg}). Re-authorization required.`;
          } else {
            lastError = `Shopify REST API responded with HTTP ${restRes.status} (${restRes.statusText}): ${finalRestMsg}`;
          }
        }
      } catch (restErr: any) {
        lastError = restErr.message || String(restErr);
      }
    }

    if (!liveFetchSucceeded || products.length === 0) {
      const isPermissionErr =
        lastError.includes('403') ||
        lastError.toLowerCase().includes('forbidden') ||
        lastError.toLowerCase().includes('read_products') ||
        lastError.toLowerCase().includes('access denied') ||
        lastError.toLowerCase().includes('re-authorization required');

      const actionPrompt = isPermissionErr
        ? `Store re-authorization required: Please grant 'read_products' permission by visiting /api/auth/shopify?shop=${this.shopDomain}.`
        : `Please verify your Shopify App credentials in Settings.`;

      throw new Error(
        `Failed to fetch live products from Shopify store ${this.shopDomain}: ${lastError || 'Authentication error'}. ${actionPrompt}`
      );
    }

    return products;
  }

  /**
   * Retrieves Shopify Store metadata including IANA timezone and currency.
   */
  async getShopInfo(): Promise<{
    id: string;
    name: string;
    email: string;
    myshopifyDomain: string;
    currencyCode: string;
    ianaTimezone: string;
    primaryDomain?: { url: string; host: string };
  }> {
    // 1. Try GraphQL first
    try {
      const res = await this.query<{ shop: any }>(SHOP_INFO_QUERY);
      if (res?.shop) {
        return {
          id: res.shop.id || '',
          name: res.shop.name || this.shopDomain,
          email: res.shop.email || '',
          myshopifyDomain: res.shop.myshopifyDomain || this.shopDomain,
          currencyCode: res.shop.currencyCode || 'USD',
          ianaTimezone: res.shop.ianaTimezone || 'UTC',
          primaryDomain: res.shop.primaryDomain,
        };
      }
    } catch (gqlErr: any) {
      logger.warn(`[ShopifyClient] getShopInfo GraphQL note: ${gqlErr.message}. Trying REST...`);
    }

    // 2. Try REST Admin API
    try {
      let token = await this.getValidToken();
      let restRes = await fetch(`https://${this.shopDomain}/admin/api/${this.apiVersion}/shop.json`, {
        headers: {
          'Content-Type': 'application/json',
          'X-Shopify-Access-Token': token,
        },
      });

      if (!restRes.ok && (restRes.status === 401 || restRes.status === 403)) {
        clearCachedShopifyToken(this.shopDomain);
        try {
          token = await this.getValidToken(true);
          restRes = await fetch(`https://${this.shopDomain}/admin/api/${this.apiVersion}/shop.json`, {
            headers: {
              'Content-Type': 'application/json',
              'X-Shopify-Access-Token': token,
            },
          });
        } catch {}
      }

      if (restRes.ok) {
        const json = await restRes.json();
        if (json.shop) {
          return {
            id: String(json.shop.id),
            name: json.shop.name || this.shopDomain,
            email: json.shop.email || '',
            myshopifyDomain: json.shop.myshopify_domain || this.shopDomain,
            currencyCode: json.shop.currency || 'USD',
            ianaTimezone: json.shop.iana_timezone || json.shop.timezone || 'UTC',
            primaryDomain: {
              url: `https://${json.shop.domain || this.shopDomain}`,
              host: json.shop.domain || this.shopDomain,
            },
          };
        }
      }
    } catch (restErr: any) {
      logger.warn(`[ShopifyClient] getShopInfo REST note: ${restErr.message}`);
    }

    return {
      id: '',
      name: this.shopDomain,
      email: '',
      myshopifyDomain: this.shopDomain,
      currencyCode: 'USD',
      ianaTimezone: 'America/New_York',
    };
  }

  /**
   * Fetches all live collections for the shop directly from Shopify via GraphQL or REST.
   */
  async fetchCollections(): Promise<Array<{ id: string; title: string; handle: string; products_count: number; image?: string | null }>> {
    try {
      let hasNextPage = true;
      let cursor: string | null = null;
      const allCols: Array<{ id: string; title: string; handle: string; products_count: number; image?: string | null }> = [];

      while (hasNextPage) {
        const res: any = await this.query(COLLECTIONS_QUERY, { first: 100, after: cursor });
        const edges = res?.collections?.edges || [];
        for (const edge of edges) {
          const node = edge.node;
          const numId = String(node.id).replace(/^.*\//, '');
          allCols.push({
            id: numId,
            title: node.title,
            handle: node.handle,
            products_count: node.productsCount?.count ?? 0,
            image: node.image?.url || null,
          });
        }
        hasNextPage = Boolean(res?.collections?.pageInfo?.hasNextPage);
        cursor = res?.collections?.pageInfo?.endCursor || null;
      }

      if (allCols.length > 0) {
        return allCols;
      }
    } catch (graphErr: any) {
      logger.warn(`[ShopifyClient] GraphQL fetchCollections note for ${this.shopDomain}: ${graphErr.message}`);
    }

    // Fallback: REST custom_collections + smart_collections
    try {
      let token = await this.getValidToken();
      let headers = { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' };
      const [customRes, smartRes] = await Promise.all([
        fetch(`https://${this.shopDomain}/admin/api/${this.apiVersion}/custom_collections.json?limit=250`, { headers }).catch(() => null),
        fetch(`https://${this.shopDomain}/admin/api/${this.apiVersion}/smart_collections.json?limit=250`, { headers }).catch(() => null),
      ]);

      const restCols: Array<{ id: string; title: string; handle: string; products_count: number; image?: string | null }> = [];
      if (customRes && customRes.ok) {
        const cData = await customRes.json();
        for (const c of cData.custom_collections || []) {
          restCols.push({
            id: String(c.id),
            title: c.title,
            handle: c.handle,
            products_count: c.products_count ?? 0,
            image: c.image?.src || null,
          });
        }
      }
      if (smartRes && smartRes.ok) {
        const sData = await smartRes.json();
        for (const s of sData.smart_collections || []) {
          restCols.push({
            id: String(s.id),
            title: s.title,
            handle: s.handle,
            products_count: s.products_count ?? 0,
            image: s.image?.src || null,
          });
        }
      }

      if (restCols.length > 0) {
        return restCols;
      }
    } catch (restErr: any) {
      logger.warn(`[ShopifyClient] REST fetchCollections note for ${this.shopDomain}: ${restErr.message}`);
    }

    return [];
  }

  /**
   * Fetches all live product tags for the shop directly from Shopify via GraphQL.
   */
  async fetchTags(): Promise<string[]> {
    try {
      const tagSet = new Set<string>();
      const res: any = await this.query(PRODUCT_TAGS_QUERY, { first: 250 });
      const edges = res?.shop?.productTags?.edges || [];
      for (const edge of edges) {
        if (edge.node && edge.node.trim()) {
          tagSet.add(edge.node.trim());
        }
      }

      if (tagSet.size > 0) {
        return Array.from(tagSet).sort();
      }
    } catch (graphErr: any) {
      logger.warn(`[ShopifyClient] GraphQL fetchTags note for ${this.shopDomain}: ${graphErr.message}`);
    }

    // Secondary fallback: Query distinct tags from products query
    try {
      const prodTagsQuery = `
        query getProductsTags($after: String) {
          products(first: 100, after: $after) {
            pageInfo {
              hasNextPage
              endCursor
            }
            edges {
              node {
                tags
              }
            }
          }
        }
      `;
      let hasNextPage = true;
      let cursor: string | null = null;
      const tagSet = new Set<string>();

      while (hasNextPage) {
        const res: any = await this.query(prodTagsQuery, { after: cursor });
        const edges = res?.products?.edges || [];
        for (const edge of edges) {
          for (const t of edge.node?.tags || []) {
            if (t && t.trim()) tagSet.add(t.trim());
          }
        }
        hasNextPage = Boolean(res?.products?.pageInfo?.hasNextPage);
        cursor = res?.products?.pageInfo?.endCursor || null;
      }

      if (tagSet.size > 0) {
        return Array.from(tagSet).sort();
      }
    } catch (prodErr: any) {
      logger.warn(`[ShopifyClient] Secondary fetchTags note for ${this.shopDomain}: ${prodErr.message}`);
    }

    return [];
  }
}

