import db from '../config/database';
import { logger } from '../utils/logger';
import { AuditService } from './AuditService';
import {
  AdPlan,
  AdCampaign,
  CreateAdPlanParams,
  ExecuteCampaignOptions,
  CampaignResult,
  DateRange,
  ROIData,
  AdPerformanceMetrics,
  AdPlanStatus,
  CampaignStatus,
} from '../models/AdPlan';

const BUDGET_THRESHOLD_FOR_REVIEW = 10000;

export class AdPlanService {
  private static readonly TABLE_AD_PLAN = 'cf_ad_plan';
  private static readonly TABLE_AD_CAMPAIGN = 'cf_ad_campaign';

  static async createAdPlan(params: CreateAdPlanParams): Promise<AdPlan> {
    const {
      tenantId,
      shopId,
      name,
      platform,
      budget,
      dailyBudget,
      startDate,
      endDate,
      targetAudience,
      keywords,
      taskId,
      traceId,
      businessType,
    } = params;

    logger.info('[AdPlanService] Creating ad plan', {
      tenantId,
      shopId,
      name,
      platform,
      budget,
      traceId,
      businessType,
    });

    try {
      const adPlanId = `ADP-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;

      let status: AdPlanStatus = 'DRAFT';
      if (budget >= BUDGET_THRESHOLD_FOR_REVIEW) {
        status = 'PENDING_REVIEW';
        logger.warn('[AdPlanService] Budget exceeds threshold, requires review', {
          adPlanId,
          budget,
          threshold: BUDGET_THRESHOLD_FOR_REVIEW,
          traceId,
        });
      }

      const adPlan: AdPlan = {
        id: adPlanId,
        tenant_id: tenantId,
        shop_id: shopId,
        name,
        platform,
        budget,
        daily_budget: dailyBudget,
        start_date: startDate,
        end_date: endDate,
        status,
        target_audience: targetAudience,
        keywords: keywords || [],
        trace_id: traceId,
        task_id: taskId,
        business_type: businessType,
        created_at: new Date(),
        updated_at: new Date(),
      };

      await db(this.TABLE_AD_PLAN).insert({
        id: adPlanId,
        tenant_id: tenantId,
        shop_id: shopId,
        name,
        platform,
        budget,
        daily_budget: dailyBudget || null,
        start_date: startDate || null,
        end_date: endDate || null,
        status,
        target_audience: targetAudience || null,
        keywords: keywords ? JSON.stringify(keywords) : null,
        trace_id: traceId,
        task_id: taskId || null,
        business_type: businessType,
        created_at: new Date(),
        updated_at: new Date(),
      });

      await AuditService.log({
        tenantId,
        shopId,
        taskId: taskId || '',
        traceId,
        businessType,
        module: 'AD_PLAN',
        action: 'AD_PLAN_CREATED',
        resourceType: 'ad_plan',
        resourceId: adPlanId,
        afterSnapshot: {
          name,
          platform,
          budget,
          status,
        },
        result: 'success',
        source: 'node',
      });

      logger.info('[AdPlanService] Ad plan created', {
        adPlanId,
        tenantId,
        shopId,
        status,
        traceId,
      });

      return adPlan;
    } catch (error: any) {
      logger.error('[AdPlanService] Failed to create ad plan', {
        tenantId,
        shopId,
        name,
        error: error.message,
        traceId,
      });
      throw error;
    }
  }

  static async executeAdCampaign(options: ExecuteCampaignOptions): Promise<CampaignResult> {
    const {
      tenantId,
      shopId,
      adPlanId,
      productId,
      skuId,
      bidAmount,
      taskId,
      traceId,
      businessType,
    } = options;

    logger.info('[AdPlanService] Executing ad campaign', {
      tenantId,
      shopId,
      adPlanId,
      productId,
      bidAmount,
      traceId,
      businessType,
    });

    try {
      const adPlan = await db(this.TABLE_AD_PLAN)
        .where({ id: adPlanId, tenant_id: tenantId })
        .first();

      if (!adPlan) {
        throw new Error(`Ad plan not found: ${adPlanId}`);
      }

      if (adPlan.status === 'PENDING_REVIEW') {
        return {
          campaignId: '',
          status: 'PENDING',
          message: 'Ad plan is pending review, cannot execute campaign',
        };
      }

      if (adPlan.status !== 'ACTIVE' && adPlan.status !== 'DRAFT') {
        throw new Error(`Ad plan status is not valid for execution: ${adPlan.status}`);
      }

      const campaignId = `CAMP-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;

      await db(this.TABLE_AD_CAMPAIGN).insert({
        id: campaignId,
        ad_plan_id: adPlanId,
        tenant_id: tenantId,
        product_id: productId || null,
        sku_id: skuId || null,
        bid_amount: bidAmount,
        clicks: 0,
        impressions: 0,
        spend: 0,
        sales: 0,
        conversions: 0,
        status: 'RUNNING',
        trace_id: traceId,
        created_at: new Date(),
        updated_at: new Date(),
      });

      if (adPlan.status === 'DRAFT') {
        await db(this.TABLE_AD_PLAN)
          .where({ id: adPlanId, tenant_id: tenantId })
          .update({ status: 'ACTIVE', updated_at: new Date() });
      }

      await AuditService.log({
        tenantId,
        shopId,
        taskId: taskId || '',
        traceId,
        businessType,
        module: 'AD_PLAN',
        action: 'AD_CAMPAIGN_EXECUTED',
        resourceType: 'ad_campaign',
        resourceId: campaignId,
        afterSnapshot: {
          adPlanId,
          productId,
          bidAmount,
          status: 'RUNNING',
        },
        result: 'success',
        source: 'node',
      });

      logger.info('[AdPlanService] Ad campaign executed', {
        campaignId,
        adPlanId,
        tenantId,
        shopId,
        traceId,
      });

      return {
        campaignId,
        status: 'RUNNING',
        message: 'Campaign started successfully',
      };
    } catch (error: any) {
      logger.error('[AdPlanService] Failed to execute ad campaign', {
        tenantId,
        shopId,
        adPlanId,
        error: error.message,
        traceId,
      });
      throw error;
    }
  }

  static async calculateROI(
    adPlanId: string,
    dateRange: DateRange,
    options: {
      tenantId: string;
      shopId: string;
      taskId?: string;
      traceId: string;
      businessType: 'TOC' | 'TOB';
    }
  ): Promise<ROIData> {
    const { tenantId, shopId, taskId, traceId, businessType } = options;

    logger.info('[AdPlanService] Calculating ROI', {
      adPlanId,
      tenantId,
      shopId,
      dateRange,
      traceId,
      businessType,
    });

    try {
      const adPlan = await db(this.TABLE_AD_PLAN)
        .where({ id: adPlanId, tenant_id: tenantId })
        .first();

      if (!adPlan) {
        throw new Error(`Ad plan not found: ${adPlanId}`);
      }

      const campaigns = await db(this.TABLE_AD_CAMPAIGN)
        .where({ ad_plan_id: adPlanId, tenant_id: tenantId })
        .where('created_at', '>=', dateRange.startDate)
        .where('created_at', '<=', dateRange.endDate);

      const metrics: AdPerformanceMetrics = {
        impressions: 0,
        clicks: 0,
        spend: 0,
        sales: 0,
        conversions: 0,
      };

      for (const campaign of campaigns) {
        metrics.impressions += campaign.impressions || 0;
        metrics.clicks += campaign.clicks || 0;
        metrics.spend += Number(campaign.spend) || 0;
        metrics.sales += Number(campaign.sales) || 0;
        metrics.conversions += campaign.conversions || 0;
      }

      const totalSpend = Number(metrics.spend.toFixed(2));
      const totalSales = Number(metrics.sales.toFixed(2));
      const totalConversions = metrics.conversions;

      let roi = 0;
      if (totalSpend > 0) {
        roi = Number((((totalSales - totalSpend) / totalSpend) * 100).toFixed(2));
      }

      let roas = 0;
      if (totalSpend > 0) {
        roas = Number((totalSales / totalSpend).toFixed(2));
      }

      let ctr = 0;
      if (metrics.impressions > 0) {
        ctr = Number(((metrics.clicks / metrics.impressions) * 100).toFixed(2));
      }

      let cpc = 0;
      if (metrics.clicks > 0) {
        cpc = Number((totalSpend / metrics.clicks).toFixed(2));
      }

      let acos = 0;
      if (totalSales > 0) {
        acos = Number(((totalSpend / totalSales) * 100).toFixed(2));
      }

      const roiData: ROIData = {
        adPlanId,
        totalSpend,
        totalSales,
        totalConversions,
        roi,
        roas,
        ctr,
        cpc,
        acos,
        dateRange,
      };

      await AuditService.log({
        tenantId,
        shopId,
        taskId: taskId || '',
        traceId,
        businessType,
        module: 'AD_PLAN',
        action: 'ROI_CALCULATED',
        resourceType: 'ad_plan',
        resourceId: adPlanId,
        afterSnapshot: {
          totalSpend,
          totalSales,
          roi,
          roas,
          ctr,
          acos,
        },
        result: 'success',
        source: 'node',
      });

      logger.info('[AdPlanService] ROI calculated', {
        adPlanId,
        tenantId,
        shopId,
        totalSpend,
        totalSales,
        roi,
        traceId,
      });

      return roiData;
    } catch (error: any) {
      logger.error('[AdPlanService] Failed to calculate ROI', {
        adPlanId,
        tenantId,
        shopId,
        error: error.message,
        traceId,
      });
      throw error;
    }
  }

  static async getAdPlanById(adPlanId: string, tenantId: string): Promise<AdPlan | null> {
    try {
      const adPlan = await db(this.TABLE_AD_PLAN)
        .where({ id: adPlanId, tenant_id: tenantId })
        .first();

      if (!adPlan) {
        return null;
      }

      return {
        ...adPlan,
        keywords: adPlan.keywords ? JSON.parse(adPlan.keywords) : [],
      };
    } catch (error: any) {
      logger.error('[AdPlanService] Failed to get ad plan', {
        adPlanId,
        tenantId,
        error: error.message,
      });
      throw error;
    }
  }

  static async updateAdPlanStatus(
    adPlanId: string,
    status: AdPlanStatus,
    options: {
      tenantId: string;
      shopId: string;
      traceId: string;
    }
  ): Promise<void> {
    const { tenantId, shopId, traceId } = options;

    logger.info('[AdPlanService] Updating ad plan status', {
      adPlanId,
      status,
      tenantId,
      shopId,
      traceId,
    });

    try {
      await db(this.TABLE_AD_PLAN)
        .where({ id: adPlanId, tenant_id: tenantId })
        .update({ status, updated_at: new Date() });

      await AuditService.log({
        tenantId,
        shopId,
        taskId: '',
        traceId,
        businessType: 'TOC',
        module: 'AD_PLAN',
        action: 'AD_PLAN_STATUS_UPDATED',
        resourceType: 'ad_plan',
        resourceId: adPlanId,
        afterSnapshot: { status },
        result: 'success',
        source: 'node',
      });

      logger.info('[AdPlanService] Ad plan status updated', {
        adPlanId,
        status,
        traceId,
      });
    } catch (error: any) {
      logger.error('[AdPlanService] Failed to update ad plan status', {
        adPlanId,
        status,
        error: error.message,
        traceId,
      });
      throw error;
    }
  }

  static async updateCampaignMetrics(
    campaignId: string,
    metrics: Partial<AdPerformanceMetrics>,
    options: {
      tenantId: string;
      traceId: string;
    }
  ): Promise<void> {
    const { tenantId, traceId } = options;

    logger.info('[AdPlanService] Updating campaign metrics', {
      campaignId,
      tenantId,
      metrics,
      traceId,
    });

    try {
      const updateData: Record<string, any> = { updated_at: new Date() };

      if (metrics.impressions !== undefined) updateData.impressions = metrics.impressions;
      if (metrics.clicks !== undefined) updateData.clicks = metrics.clicks;
      if (metrics.spend !== undefined) updateData.spend = metrics.spend;
      if (metrics.sales !== undefined) updateData.sales = metrics.sales;
      if (metrics.conversions !== undefined) updateData.conversions = metrics.conversions;

      await db(this.TABLE_AD_CAMPAIGN)
        .where({ id: campaignId, tenant_id: tenantId })
        .update(updateData);

      logger.info('[AdPlanService] Campaign metrics updated', {
        campaignId,
        traceId,
      });
    } catch (error: any) {
      logger.error('[AdPlanService] Failed to update campaign metrics', {
        campaignId,
        error: error.message,
        traceId,
      });
      throw error;
    }
  }

  static async pauseAdPlan(
    adPlanId: string,
    options: {
      tenantId: string;
      shopId: string;
      traceId: string;
    }
  ): Promise<void> {
    await this.updateAdPlanStatus(adPlanId, 'PAUSED', options);

    await db(this.TABLE_AD_CAMPAIGN)
      .where({ ad_plan_id: adPlanId, tenant_id: options.tenantId })
      .where('status', 'RUNNING')
      .update({ status: 'PAUSED', updated_at: new Date() });

    logger.info('[AdPlanService] Ad plan and campaigns paused', {
      adPlanId,
      tenantId: options.tenantId,
      traceId: options.traceId,
    });
  }

  static async initTables(): Promise<void> {
    try {
      const hasAdPlanTable = await db.schema.hasTable(this.TABLE_AD_PLAN);
      if (!hasAdPlanTable) {
        logger.info('[AdPlanService] Creating cf_ad_plan table...');
        await db.schema.createTable(this.TABLE_AD_PLAN, (table) => {
          table.string('id', 64).primary();
          table.string('tenant_id', 64).notNullable();
          table.string('shop_id', 64).notNullable();
          table.string('name', 255).notNullable();
          table.enum('platform', ['AMAZON', 'EBAY', 'SHOPEE', 'TIKTOK']).notNullable();
          table.decimal('budget', 10, 2).notNullable();
          table.decimal('daily_budget', 10, 2);
          table.date('start_date');
          table.date('end_date');
          table.enum('status', ['DRAFT', 'ACTIVE', 'PAUSED', 'COMPLETED', 'PENDING_REVIEW']).defaultTo('DRAFT');
          table.text('target_audience');
          table.json('keywords');
          table.string('trace_id', 64);
          table.string('task_id', 64);
          table.string('business_type', 10);
          table.timestamps(true, true);

          table.index(['tenant_id', 'shop_id'], 'idx_ad_plan_tenant_shop');
          table.index(['status'], 'idx_ad_plan_status');
          table.index(['platform'], 'idx_ad_plan_platform');
        });
        logger.info('[AdPlanService] Table cf_ad_plan created');
      }

      const hasAdCampaignTable = await db.schema.hasTable(this.TABLE_AD_CAMPAIGN);
      if (!hasAdCampaignTable) {
        logger.info('[AdPlanService] Creating cf_ad_campaign table...');
        await db.schema.createTable(this.TABLE_AD_CAMPAIGN, (table) => {
          table.string('id', 64).primary();
          table.string('ad_plan_id', 64).notNullable();
          table.string('tenant_id', 64).notNullable();
          table.string('product_id', 64);
          table.string('sku_id', 64);
          table.decimal('bid_amount', 10, 2).notNullable();
          table.integer('clicks').defaultTo(0);
          table.integer('impressions').defaultTo(0);
          table.decimal('spend', 10, 2).defaultTo(0);
          table.decimal('sales', 10, 2).defaultTo(0);
          table.integer('conversions').defaultTo(0);
          table.enum('status', ['PENDING', 'RUNNING', 'PAUSED', 'COMPLETED', 'FAILED']).defaultTo('PENDING');
          table.string('trace_id', 64);
          table.timestamps(true, true);

          table.index(['ad_plan_id'], 'idx_ad_campaign_plan');
          table.index(['tenant_id'], 'idx_ad_campaign_tenant');
          table.index(['status'], 'idx_ad_campaign_status');
        });
        logger.info('[AdPlanService] Table cf_ad_campaign created');
      }

      logger.info('[AdPlanService] Ad plan tables initialized successfully');
    } catch (error: any) {
      logger.error(`[AdPlanService] Failed to initialize ad plan tables - error: ${error.message}`);
      throw error;
    }
  }
}
