import type { FeedProvider, FeedRow, NormalizedProduct, SyncStats, ProviderRow, FieldMappingRow } from './types.js';
import { query, log, logError, getClient } from './db.js';
import { getMappings, applyMappings, normalizeProduct } from './field-mapper.js';
import { matchProduct, createProduct, upsertOffer } from './product-matcher.js';
import { WebgainsAdapter } from './webgains-adapter.js';
import { TradeTrackerAdapter } from './tradetracker-adapter.js';
import { AwinAdapter } from './awin-adapter.js';

// =============================================================================
// Provider Registry
// =============================================================================

const ADAPTERS: Record<string, new () => FeedProvider> = {
  webgains: WebgainsAdapter,
  tradetracker: TradeTrackerAdapter,
  awin: AwinAdapter,
};

export function getAdapter(adapterType: string): FeedProvider {
  const AdapterClass = ADAPTERS[adapterType];
  if (!AdapterClass) throw new Error(`Unknown adapter type: ${adapterType}`);
  return new AdapterClass();
}

// =============================================================================
// Sync Engine
// =============================================================================

export async function syncFeed(feedId: number): Promise<SyncStats> {
  const stats: SyncStats = {
    fetched: 0, new: 0, updated: 0, removed: 0, matched: 0, errors: 0, errorDetails: [],
  };

  // Load feed + provider
  const feedResult = await query(
    `SELECT f.*, p.slug as provider_slug, p.config as provider_config, p.adapter_type
     FROM feeds f JOIN providers p ON f.provider_id = p.id
     WHERE f.id = $1`,
    [feedId]
  );

  if (feedResult.rows.length === 0) throw new Error(`Feed ${feedId} not found`);
  const feed = feedResult.rows[0];

  // Create sync log
  const logResult = await query(
    `INSERT INTO sync_logs (feed_id, status) VALUES ($1, 'running') RETURNING id`,
    [feedId]
  );
  const syncLogId = logResult.rows[0].id;
  const syncStart = new Date();

  try {
    log(`Sync started: feed ${feedId} (${feed.name}) via ${feed.provider_slug}`);

    // Initialize adapter
    const adapter = getAdapter(feed.adapter_type);
    const config = { ...feed.provider_config, ...feed.config };
    await adapter.authenticate(config);

    // Load field mappings
    const mappings = await getMappings(feed.provider_id, feedId);
    if (mappings.length === 0) {
      throw new Error(`No field mappings found for feed ${feedId} (provider ${feed.provider_id})`);
    }

    // Fetch and process
    const externalId = feed.external_id || String(feedId);
    for await (const batch of adapter.fetchProducts(externalId)) {
      for (const raw of batch) {
        try {
          stats.fetched++;
          const mapped = applyMappings(raw, mappings);
          const extId = String(raw._external_id || raw.identifier || raw.id || '');
          const normalized = normalizeProduct(mapped, extId);

          if (!normalized) {
            stats.errors++;
            if (stats.errorDetails.length < 100) {
              stats.errorDetails.push({ message: `Failed to normalize product: ${mapped.name || 'unknown'}` });
            }
            continue;
          }

          // Match to existing product
          const match = await matchProduct(normalized);

          let productId: number;
          if (match.productId) {
            productId = match.productId;
            stats.matched++;
          } else {
            productId = await createProduct(normalized);
            stats.new++;
          }

          // Upsert offer
          const offerResult = await upsertOffer(productId, feedId, normalized);
          if (offerResult === 'updated') stats.updated++;
        } catch (err: any) {
          stats.errors++;
          if (stats.errorDetails.length < 100) {
            stats.errorDetails.push({ message: err.message });
          }
        }
      }
    }

    // Mark stale offers (not seen in this sync) as out of stock
    const staleResult = await query(
      `UPDATE product_offers SET in_stock = false, updated_at = NOW()
       WHERE feed_id = $1 AND last_seen_at < $2 AND in_stock = true
       RETURNING id`,
      [feedId, syncStart]
    );
    stats.removed = staleResult.rowCount || 0;

    // Update feed status
    await query(
      `UPDATE feeds SET
        last_sync_at = NOW(),
        last_sync_status = 'success',
        last_error = NULL,
        product_count = $2,
        updated_at = NOW()
       WHERE id = $1`,
      [feedId, stats.fetched]
    );

    // Update sync log
    await query(
      `UPDATE sync_logs SET
        finished_at = NOW(), status = 'success',
        products_fetched = $2, products_new = $3, products_updated = $4,
        products_removed = $5, products_matched = $6, errors_count = $7,
        error_details = $8
       WHERE id = $1`,
      [syncLogId, stats.fetched, stats.new, stats.updated, stats.removed, stats.matched, stats.errors,
       stats.errorDetails.length > 0 ? JSON.stringify(stats.errorDetails) : null]
    );

    log(`Sync completed: feed ${feedId} — ${stats.fetched} fetched, ${stats.new} new, ${stats.updated} updated, ${stats.matched} matched, ${stats.removed} stale, ${stats.errors} errors`);

  } catch (err: any) {
    logError(`Sync failed: feed ${feedId}`, err);

    await query(
      `UPDATE sync_logs SET finished_at = NOW(), status = 'failed', error_details = $2 WHERE id = $1`,
      [syncLogId, JSON.stringify([{ message: err.message }])]
    );
    await query(
      `UPDATE feeds SET last_sync_at = NOW(), last_sync_status = 'failed', last_error = $2, updated_at = NOW() WHERE id = $1`,
      [feedId, err.message]
    );

    throw err;
  }

  return stats;
}

// =============================================================================
// Get overdue feeds for scheduled syncing
// =============================================================================

export async function getOverdueFeeds(): Promise<FeedRow[]> {
  const result = await query(
    `SELECT f.*, p.slug as provider_slug, p.config as provider_config
     FROM feeds f
     JOIN providers p ON f.provider_id = p.id
     WHERE f.is_enabled = true AND p.is_active = true
       AND (
         f.last_sync_at IS NULL
         OR (
           CASE f.sync_frequency
             WHEN '1h'  THEN f.last_sync_at < NOW() - INTERVAL '1 hour'
             WHEN '4h'  THEN f.last_sync_at < NOW() - INTERVAL '4 hours'
             WHEN '12h' THEN f.last_sync_at < NOW() - INTERVAL '12 hours'
             WHEN '24h' THEN f.last_sync_at < NOW() - INTERVAL '24 hours'
             ELSE f.last_sync_at < NOW() - INTERVAL '4 hours'
           END
         )
       )
     ORDER BY f.last_sync_at ASC NULLS FIRST
     LIMIT 5`
  );
  return result.rows;
}
