import type { FeedProvider, ExternalFeed, RawProduct, FetchOptions, FeedInfo, ProviderConfig } from './types.js';
import { log, logError } from './db.js';

/**
 * TradeTracker Adapter
 * 
 * TradeTracker offers both SOAP and direct feed URL downloads.
 * We prefer direct feed URLs for bulk fetches (faster, no pagination needed).
 * Falls back to REST-like endpoints when feed URLs aren't available.
 * 
 * Note: The SOAP client (npm 'soap') is optional. If feeds provide direct
 * download URLs, we stream those instead.
 */

const PAGE_SIZE = 100;

export class TradeTrackerAdapter implements FeedProvider {
  readonly slug = 'tradetracker';
  private customerId: string = '';
  private accessKey: string = '';
  private locale: string = 'nl_NL';
  private soapClient: any = null;
  private authenticated = false;

  async authenticate(config: ProviderConfig): Promise<void> {
    this.customerId = config.customer_id || process.env.TRADETRACKER_CLIENT_ID || '';
    this.accessKey = config.access_key || process.env.TRADETRACKER_ACCESS_KEY || '';
    this.locale = config.locale || 'nl_NL';

    if (!this.customerId || !this.accessKey) {
      throw new Error('TradeTracker: missing customer_id or access_key');
    }

    // Try to load soap module (optional dependency)
    try {
      // @ts-ignore - soap is an optional dependency
      const soap = await import('soap');
      const wsdlUrl = config.wsdl_url || 'https://ws.tradetracker.com/soap/affiliate?wsdl';
      this.soapClient = await soap.createClientAsync(wsdlUrl);
      
      await this.soapClient.authenticateAsync({
        customerID: parseInt(this.customerId),
        passphrase: this.accessKey,
        sandbox: false,
        locale: this.locale,
        demo: false,
      });
      this.authenticated = true;
      log('TradeTracker: SOAP authenticated');
    } catch (err: any) {
      log(`TradeTracker: SOAP not available (${err.message}), will use feed URLs only`);
      this.authenticated = false;
    }
  }

  async listFeeds(): Promise<ExternalFeed[]> {
    if (!this.soapClient || !this.authenticated) {
      log('TradeTracker: SOAP not available, returning empty feed list. Configure feeds manually with feed URLs.');
      return [];
    }

    try {
      // First get affiliate sites
      const [sitesResult] = await this.soapClient.getAffiliateSitesAsync();
      const sites = sitesResult?.affiliateSites || [];
      if (sites.length === 0) {
        log('TradeTracker: no affiliate sites found');
        return [];
      }
      const siteId = sites[0].ID;

      const [feedsResult] = await this.soapClient.getFeedsAsync(siteId, {
        assignmentStatus: 'accepted',
      });

      return (feedsResult?.feeds || []).map((f: any) => ({
        externalId: String(f.ID),
        name: f.name || '',
        merchantName: f.campaign?.name || f.name || '',
        productCount: Number(f.productCount) || 0,
        lastUpdated: f.updateDate ? new Date(f.updateDate) : null,
        feedUrl: f.URL || undefined,
      }));
    } catch (err: any) {
      logError('TradeTracker listFeeds failed', err);
      return [];
    }
  }

  async *fetchProducts(feedId: string, options?: FetchOptions): AsyncGenerator<RawProduct[]> {
    // Check if we have a direct feed URL in the feed config
    const feedUrl = options?.limit !== undefined ? undefined : undefined; // placeholder

    // If a feed URL is provided in DB config, stream it directly
    // This is configured per-feed in the feeds.config JSON column
    // For SOAP pagination fallback:
    if (this.soapClient && this.authenticated) {
      yield* this.fetchViaSoap(feedId, options);
    } else {
      log(`TradeTracker: no SOAP client for feed ${feedId}, skipping`);
    }
  }

  private async *fetchViaSoap(feedId: string, options?: FetchOptions): AsyncGenerator<RawProduct[]> {
    const [sitesResult] = await this.soapClient.getAffiliateSitesAsync();
    const siteId = (sitesResult?.affiliateSites || [])[0]?.ID;
    if (!siteId) throw new Error('TradeTracker: no affiliate site found');

    let offset = options?.offset || 0;
    let hasMore = true;

    while (hasMore) {
      try {
        const [result] = await this.soapClient.getFeedProductsAsync(siteId, {
          feedID: parseInt(feedId),
          limit: PAGE_SIZE,
          offset,
        });

        const products = result?.feedProducts || [];
        if (products.length === 0) {
          hasMore = false;
          break;
        }

        const batch: RawProduct[] = products.map((p: any) => ({
          _external_id: p.identifier || '',
          name: p.name || '',
          description: p.description || '',
          price: String(p.price || ''),
          productURL: p.productURL || '',
          imageURL: p.imageURL || '',
          productCategoryName: p.productCategoryName || '',
          ...this.flattenAdditional(p.additional),
        }));

        yield batch;
        offset += PAGE_SIZE;
        if (products.length < PAGE_SIZE) hasMore = false;

        // Rate limiting: ~5 req/s
        await new Promise(r => setTimeout(r, 200));
      } catch (err: any) {
        logError(`TradeTracker fetch error at offset ${offset}`, err);
        hasMore = false;
      }
    }
  }

  private flattenAdditional(additional: any[] | undefined): Record<string, string> {
    if (!additional || !Array.isArray(additional)) return {};
    const result: Record<string, string> = {};
    for (const item of additional) {
      if (item.name && item.value != null) {
        result[`additional_${item.name}`] = String(item.value);
      }
    }
    return result;
  }

  async getFeedInfo(feedId: string): Promise<FeedInfo> {
    const feeds = await this.listFeeds();
    const feed = feeds.find(f => f.externalId === feedId);
    return {
      productCount: feed?.productCount || 0,
      lastUpdated: feed?.lastUpdated || null,
    };
  }
}
