import { queueProducer } from './producer';
import { db } from '@/db/client';
import { decrypt } from '@/lib/crypto/encryption';
import { ShopifyClient } from '@/lib/shopify/client';
import { getShopifyAccessToken } from '@/lib/shopify/token-manager';
import { MetaGraphClient } from '@/lib/meta/client';
import { CatalogSyncJobPayload, WebhookJobPayload } from '@/types';
import { logger } from '@/lib/logger';

class QueueWorker {
  private activeJobs = new Set<string>();

  async processJob(jobId: string): Promise<void> {
    if (this.activeJobs.has(jobId)) return;
    const job = queueProducer.getJob(jobId);
    if (!job || job.status === 'completed' || job.status === 'failed') return;

    this.activeJobs.add(jobId);
    job.status = 'running';
    job.attempts += 1;
    job.updated_at = new Date().toISOString();

    logger.info(`Worker processing job ${job.id} (Attempt ${job.attempts}/${job.max_attempts}) - Type: ${job.type}`);

    try {
      switch (job.type) {
        case 'catalog_sync':
          await this.executeCatalogSync(job.shop_id, job.payload as unknown as CatalogSyncJobPayload);
          break;
        case 'webhook_process':
          await this.executeWebhookProcess(job.shop_id, job.payload as unknown as WebhookJobPayload);
          break;
        default:
          logger.warn(`Unknown job type: ${job.type}`);
      }

      job.status = 'completed';
      job.updated_at = new Date().toISOString();
      logger.info(`Job ${job.id} completed successfully`);
    } catch (err) {
      const errorMsg = err instanceof Error ? err.message : String(err);
      job.error = errorMsg;
      job.updated_at = new Date().toISOString();

      if (job.attempts < job.max_attempts) {
        job.status = 'queued';
        const delay = Math.pow(2, job.attempts) * 1000; // Exponential backoff: 2s, 4s, 8s...
        logger.warn(`Job ${job.id} failed, retrying in ${delay}ms: ${errorMsg}`);
        setTimeout(() => {
          this.processJob(job.id);
        }, delay);
      } else {
        job.status = 'failed';
        logger.error(`Job ${job.id} permanently failed after ${job.attempts} attempts: ${errorMsg}`);
      }
    } finally {
      this.activeJobs.delete(jobId);
    }
  }

  private async executeCatalogSync(shopId: string, payload: CatalogSyncJobPayload): Promise<void> {
    const shop = await db.getShopById(shopId);
    if (!shop) throw new Error(`Shop not found: ${shopId}`);

    const syncId = payload.sync_id;
    await db.updateSync(syncId, { status: 'processing' });

    let shopifyToken = '';
    try {
      shopifyToken = await getShopifyAccessToken(shop.shopify_domain);
    } catch {
      shopifyToken = decrypt(shop.access_token_encrypted);
    }
    const shopifyClient = new ShopifyClient(shop.shopify_domain, shopifyToken);

    // 1. Fetch products & variants from Shopify
    const products = await shopifyClient.fetchAllProductsForCatalog(shopId);

    // 2. Persist products into local catalog database
    for (const prod of products) {
      await db.upsertProduct(prod);
    }

    // 3. If Meta is connected, push batch items to Meta Catalog
    const metaConn = await db.getMetaConnectionByShopId(shopId);
    if (metaConn && metaConn.catalog_id && metaConn.status === 'connected') {
      const metaToken = decrypt(metaConn.access_token_encrypted);
      const metaClient = new MetaGraphClient(metaToken);
      await metaClient.syncCatalogBatch(metaConn.catalog_id, products);
    }

    // 4. Update sync state
    await db.updateSync(syncId, {
      status: 'completed',
      total_products: products.length,
      processed_products: products.length,
      failed_products: 0,
      completed_at: new Date().toISOString(),
    });

    await db.recordAuditLog({
      id: `audit_${Date.now()}`,
      shop_id: shopId,
      action: 'catalog_sync_completed',
      actor: 'worker',
      metadata: { total: products.length },
      created_at: new Date().toISOString(),
    });
  }

  private async executeWebhookProcess(shopId: string, payload: WebhookJobPayload): Promise<void> {
    const { topic, body } = payload;
    const now = new Date().toISOString();
    logger.info(`Processing webhook topic ${topic} for shop ${shopId}`);

    if (topic.startsWith('products/')) {
      const { processProductWebhook } = await import('@/lib/shopify/product-webhook-handler');
      await processProductWebhook(shopId, topic, body);
      logger.info(`Product webhook ${topic} processed and synced for shop ${shopId}`);
    } else if (topic === 'app/uninstalled') {
      const shop = await db.getShopById(shopId);
      if (shop) {
        await db.upsertShop({
          ...shop,
          status: 'uninstalled',
          uninstalled_at: now,
          updated_at: now,
        });

        // Record in app_installations
        await db.recordAppInstallation({
          id: `inst_un_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
          shop_id: shop.id,
          shopify_domain: shop.shopify_domain,
          event_type: 'uninstalled',
          plan_at_time: shop.plan,
          metadata: { reason: 'Merchant uninstalled app from Shopify Admin' },
          occurred_at: now,
          created_at: now,
        });

        // Cancel active subscriptions
        const existingSub = await db.getSubscriptionByShopId(shop.id);
        if (existingSub && existingSub.status !== 'cancelled') {
          await db.cancelSubscription(existingSub.id, now);

          await db.recordBillingEvent({
            id: `bill_un_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
            shop_id: shop.id,
            shopify_domain: shop.shopify_domain,
            subscription_id: existingSub.id,
            event_type: 'charge_cancelled',
            amount: 0,
            currency: existingSub.currency || 'USD',
            status: 'verified',
            billing_date: now,
            details: { reason: 'App uninstalled' },
            created_at: now,
          });
        }

        // Record activity log
        await db.recordStoreActivity({
          id: `act_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
          shop_id: shop.id,
          shopify_domain: shop.shopify_domain,
          action: 'app_uninstalled',
          actor: 'shopify_webhook',
          description: `Merchant uninstalled app from ${shop.shopify_domain}`,
          metadata: { topic, uninstalled_at: now },
          created_at: now,
        });

        logger.info(`App uninstalled processed and recorded for ${shop.shopify_domain}`);
      }
    } else if (topic === 'app_subscriptions/update') {
      const shop = await db.getShopById(shopId);
      if (shop) {
        const appSubscription = (body as any)?.app_subscription || body;
        const subStatus = (appSubscription?.status || 'active').toLowerCase();
        const subName = appSubscription?.name || `${shop.plan.toUpperCase()} Plan`;
        const lineItem = appSubscription?.line_items?.[0] || {};
        const price = parseFloat(lineItem?.plan?.pricing_details?.price?.amount || '0');
        const currency = lineItem?.plan?.pricing_details?.price?.currency_code || 'USD';
        const subId = appSubscription?.admin_graphql_api_id || `sub_${Date.now()}`;

        const nameLower = subName.toLowerCase();
        let planId = shop.plan || 'growth';
        if (subStatus === 'cancelled') {
          planId = 'basic';
        } else if (nameLower.includes('accelerate') || price >= 90) {
          planId = 'accelerate';
        } else if (nameLower.includes('growth') || price >= 10) {
          planId = 'growth';
        } else if (nameLower.includes('basic') || price === 0) {
          planId = 'basic';
        }

        // Upsert subscription
        await db.upsertSubscription({
          id: `sub_${shop.id}`,
          shop_id: shop.id,
          shopify_domain: shop.shopify_domain,
          shopify_subscription_id: subId,
          plan_id: planId,
          plan_name: subName,
          status: subStatus === 'active' ? 'active' : subStatus === 'cancelled' ? 'cancelled' : 'trial',
          billing_cycle: 'monthly',
          price: subStatus === 'cancelled' ? 0 : price,
          currency,
          trial_days: shop.trial_days || 0,
          trial_starts_at: shop.trial_starts_at || null,
          trial_ends_at: shop.trial_ends_at || null,
          current_period_end: appSubscription?.current_period_end || null,
          cancelled_at: subStatus === 'cancelled' ? now : null,
          created_at: now,
          updated_at: now,
        });

        // Record billing event
        await db.recordBillingEvent({
          id: `bill_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
          shop_id: shop.id,
          shopify_domain: shop.shopify_domain,
          subscription_id: `sub_${shop.id}`,
          event_type: subStatus === 'active' ? 'charge_activated' : subStatus === 'cancelled' ? 'charge_cancelled' : 'payment_success',
          amount: subStatus === 'cancelled' ? 0 : price,
          currency,
          status: 'verified',
          shopify_charge_id: subId,
          details: { status: subStatus, name: subName },
          billing_date: now,
          created_at: now,
        });

        // Update shop subscription status and monthly revenue
        await db.upsertShop({
          ...shop,
          plan: planId,
          subscription_status: subStatus === 'active' ? 'active' : subStatus === 'cancelled' ? 'free' : 'trial',
          monthly_revenue: subStatus === 'active' ? price : 0,
          updated_at: now,
        });

        // Record activity log
        await db.recordStoreActivity({
          id: `act_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
          shop_id: shop.id,
          shopify_domain: shop.shopify_domain,
          action: 'subscription_status_changed',
          actor: 'shopify_webhook',
          description: `Subscription status updated to ${subStatus} ($${price}/mo)`,
          metadata: { subStatus, price, subId },
          created_at: now,
        });

        logger.info(`Subscription update webhook processed for ${shop.shopify_domain}: ${subStatus}`);
      }
    }
  }
}

export const queueWorker = new QueueWorker();
