import { v4 as uuidv4 } from 'uuid';
import db from '../config/database';
import { logger } from '../utils/logger';
import { PricingService, PricingParams, ProfitSnapshot } from './PricingService';
import PriceComparisonService, { PriceComparison, ComparisonResult } from './PriceComparisonService';
import ExchangeRateService from './ExchangeRateService';
import { ArbitrageOpportunityStatus, ArbitrageExecutionStatus, RiskLevel, BusinessType } from '../types/enums';

export interface ArbitrageOpportunity {
  id: string;
  tenant_id: string;
  product_id: string;
  product_name: string;
  source_platform: string;
  source_url: string;
  source_price: number;
  source_currency: string;
  target_platform: string;
  target_price: number;
  target_currency: string;
  profit_snapshot: ProfitSnapshot;
  risk_level: RiskLevel;
  opportunity_score: number;
  status: ArbitrageOpportunityStatus;
  execution_mode: 'AUTO' | 'MANUAL';
  created_at: Date;
  updated_at: Date;
  executed_at?: Date;
  error_message?: string;
}

export interface ArbitrageExecution {
  id: string;
  opportunity_id: string;
  tenant_id: string;
  status: ArbitrageExecutionStatus;
  purchase_order_id?: string;
  listing_id?: string;
  sale_order_id?: string;
  actual_purchase_price?: number;
  actual_sale_price?: number;
  actual_profit?: number;
  notes?: string;
  created_at: Date;
  updated_at: Date;
}

export interface ArbitrageStats {
  totalOpportunities: number;
  pendingOpportunities: number;
  executedOpportunities: number;
  successRate: number;
  totalProfit: number;
  avgProfitMargin: number;
  avgROI: number;
  byPlatform: Record<string, { count: number; profit: number }>;
  byStatus: Record<string, number>;
}

export class ArbitrageService {
  private static instance: ArbitrageService;
  private static readonly TABLE_OPPORTUNITIES = 'cf_arbitrage_opportunities';
  private static readonly TABLE_EXECUTIONS = 'cf_arbitrage_executions';

  private constructor() {}

  static getInstance(): ArbitrageService {
    if (!ArbitrageService.instance) {
      ArbitrageService.instance = new ArbitrageService();
    }
    return ArbitrageService.instance;
  }

  async createOpportunity(
    tenantId: string,
    productId: string,
    productName: string,
    sourcePlatform: string,
    sourceUrl: string,
    sourcePrice: number,
    sourceCurrency: string,
    targetPlatform: string,
    targetPrice: number,
    targetCurrency: string,
    options?: {
      logisticsCost?: number;
      adBudget?: number;
      isB2B?: boolean;
      executionMode?: 'AUTO' | 'MANUAL';
    }
  ): Promise<ArbitrageOpportunity> {
    const exchangeRate = await ExchangeRateService.getRate(sourceCurrency, targetCurrency);
    const sourcePriceConverted = sourcePrice * exchangeRate;

    const pricingParams: PricingParams = {
      sellingPrice: targetPrice,
      purchasePrice: sourcePriceConverted,
      platform: targetPlatform,
      countryCode: 'US',
      logisticsCost: options?.logisticsCost || 5.0,
      adBudget: options?.adBudget || 2.0,
      isB2B: options?.isB2B || false
    };

    const profitSnapshot = await PricingService.calculateNetProfit(pricingParams);

    const riskCheck = PricingService.checkRisk(profitSnapshot, options?.isB2B || false);
    let riskLevel: ArbitrageOpportunity['risk_level'] = RiskLevel.LOW;
    switch (riskCheck.level) {
      case 'BLOCK':
        riskLevel = RiskLevel.HIGH;
        break;
      case 'WARN':
        riskLevel = RiskLevel.MEDIUM;
        break;
      case 'PASS':
        riskLevel = profitSnapshot.profitRate >= 0.25 ? RiskLevel.LOW : RiskLevel.MEDIUM;
        break;
    }

    const opportunityScore = PricingService.calculateOpportunityScore(profitSnapshot, {
      netProfit: profitSnapshot.netProfit
    });

    const opportunity: ArbitrageOpportunity = {
      id: uuidv4(),
      tenant_id: tenantId,
      product_id: productId,
      product_name: productName,
      source_platform: sourcePlatform,
      source_url: sourceUrl,
      source_price: sourcePrice,
      source_currency: sourceCurrency,
      target_platform: targetPlatform,
      target_price: targetPrice,
      target_currency: targetCurrency,
      profit_snapshot: profitSnapshot,
      risk_level: riskLevel,
      opportunity_score: opportunityScore,
      status: ArbitrageOpportunityStatus.PENDING,
      execution_mode: options?.executionMode || 'MANUAL',
      created_at: new Date(),
      updated_at: new Date()
    };

    await db(ArbitrageService.TABLE_OPPORTUNITIES).insert({
      id: opportunity.id,
      tenant_id: opportunity.tenant_id,
      product_id: opportunity.product_id,
      product_name: opportunity.product_name,
      source_platform: opportunity.source_platform,
      source_url: opportunity.source_url,
      source_price: opportunity.source_price,
      source_currency: opportunity.source_currency,
      target_platform: opportunity.target_platform,
      target_price: opportunity.target_price,
      target_currency: opportunity.target_currency,
      profit_snapshot: JSON.stringify(opportunity.profit_snapshot),
      risk_level: opportunity.risk_level,
      opportunity_score: opportunity.opportunity_score,
      status: opportunity.status,
      execution_mode: opportunity.execution_mode,
      created_at: opportunity.created_at,
      updated_at: opportunity.updated_at
    });

    logger.info(`[ArbitrageService] Created opportunity ${opportunity.id} for product ${productId}`);

    return opportunity;
  }

  async approveOpportunity(opportunityId: string): Promise<ArbitrageOpportunity> {
    const opportunity = await this.getOpportunityById(opportunityId);
    if (!opportunity) {
      throw new Error('Opportunity not found');
    }

    if (opportunity.status !== 'PENDING') {
      throw new Error(`Cannot approve opportunity with status ${opportunity.status}`);
    }

    await db(ArbitrageService.TABLE_OPPORTUNITIES)
      .where('id', opportunityId)
      .update({
        status: 'APPROVED',
        updated_at: new Date()
      });

    logger.info(`[ArbitrageService] Approved opportunity ${opportunityId}`);

    return this.getOpportunityById(opportunityId) as Promise<ArbitrageOpportunity>;
  }

  async rejectOpportunity(opportunityId: string, reason?: string): Promise<void> {
    await db(ArbitrageService.TABLE_OPPORTUNITIES)
      .where('id', opportunityId)
      .update({
        status: 'REJECTED',
        error_message: reason,
        updated_at: new Date()
      });

    logger.info(`[ArbitrageService] Rejected opportunity ${opportunityId}: ${reason}`);
  }

  async executeOpportunity(opportunityId: string): Promise<ArbitrageExecution> {
    const opportunity = await this.getOpportunityById(opportunityId);
    if (!opportunity) {
      throw new Error('Opportunity not found');
    }

    if (![ArbitrageOpportunityStatus.PENDING, ArbitrageOpportunityStatus.APPROVED].includes(opportunity.status)) {
      throw new Error(`Cannot execute opportunity with status ${opportunity.status}`);
    }

    await db(ArbitrageService.TABLE_OPPORTUNITIES)
      .where('id', opportunityId)
      .update({
        status: ArbitrageOpportunityStatus.EXECUTING,
        updated_at: new Date()
      });

    const execution: ArbitrageExecution = {
      id: uuidv4(),
      opportunity_id: opportunityId,
      tenant_id: opportunity.tenant_id,
      status: ArbitrageExecutionStatus.STARTED,
      created_at: new Date(),
      updated_at: new Date()
    };

    await db(ArbitrageService.TABLE_EXECUTIONS).insert({
      id: execution.id,
      opportunity_id: execution.opportunity_id,
      tenant_id: execution.tenant_id,
      status: execution.status,
      created_at: execution.created_at,
      updated_at: execution.updated_at
    });

    logger.info(`[ArbitrageService] Started execution ${execution.id} for opportunity ${opportunityId}`);

    return execution;
  }

  async updateExecutionStatus(
    executionId: string,
    status: ArbitrageExecution['status'],
    data?: {
      purchaseOrderId?: string;
      listingId?: string;
      saleOrderId?: string;
      actualPurchasePrice?: number;
      actualSalePrice?: number;
      actualProfit?: number;
      notes?: string;
    }
  ): Promise<ArbitrageExecution> {
    const updateData: any = {
      status,
      updated_at: new Date(),
      ...data
    };

    await db(ArbitrageService.TABLE_EXECUTIONS)
      .where('id', executionId)
      .update(updateData);

    if (status === 'COMPLETED') {
      const execution = await this.getExecutionById(executionId);
      if (execution) {
        await db(ArbitrageService.TABLE_OPPORTUNITIES)
          .where('id', execution.opportunity_id)
          .update({
            status: 'EXECUTED',
            executed_at: new Date(),
            updated_at: new Date()
          });
      }
    } else if (status === 'FAILED') {
      const execution = await this.getExecutionById(executionId);
      if (execution) {
        await db(ArbitrageService.TABLE_OPPORTUNITIES)
          .where('id', execution.opportunity_id)
          .update({
            status: 'FAILED',
            error_message: data?.notes,
            updated_at: new Date()
          });
      }
    }

    logger.info(`[ArbitrageService] Updated execution ${executionId} to status ${status}`);

    return this.getExecutionById(executionId) as Promise<ArbitrageExecution>;
  }

  async getOpportunityById(opportunityId: string): Promise<ArbitrageOpportunity | null> {
    const row = await db(ArbitrageService.TABLE_OPPORTUNITIES)
      .where('id', opportunityId)
      .first();

    if (!row) return null;

    return this.mapOpportunityFromDb(row);
  }

  async getOpportunitiesByTenant(
    tenantId: string,
    options?: {
      status?: string;
      platform?: string;
      minScore?: number;
      limit?: number;
      offset?: number;
    }
  ): Promise<{ opportunities: ArbitrageOpportunity[]; total: number }> {
    let query = db(ArbitrageService.TABLE_OPPORTUNITIES)
      .where('tenant_id', tenantId);

    if (options?.status) {
      query = query.where('status', options.status);
    }
    if (options?.platform) {
      query = query.where('target_platform', options.platform);
    }
    if (options?.minScore) {
      query = query.where('opportunity_score', '>=', options.minScore);
    }

    const total = await query.clone().count('* as count').first();

    if (options?.limit) {
      query = query.limit(options.limit);
    }
    if (options?.offset) {
      query = query.offset(options.offset);
    }

    const rows = await query.orderBy('created_at', 'desc');

    return {
      opportunities: rows.map(this.mapOpportunityFromDb),
      total: Number(total?.count) || 0
    };
  }

  async getExecutionById(executionId: string): Promise<ArbitrageExecution | null> {
    const row = await db(ArbitrageService.TABLE_EXECUTIONS)
      .where('id', executionId)
      .first();

    if (!row) return null;

    return this.mapExecutionFromDb(row);
  }

  async getExecutionsByOpportunity(opportunityId: string): Promise<ArbitrageExecution[]> {
    const rows = await db(ArbitrageService.TABLE_EXECUTIONS)
      .where('opportunity_id', opportunityId)
      .orderBy('created_at', 'desc');

    return rows.map(this.mapExecutionFromDb);
  }

  async getArbitrageStats(tenantId: string): Promise<ArbitrageStats> {
    const opportunities = await db(ArbitrageService.TABLE_OPPORTUNITIES)
      .where('tenant_id', tenantId);

    const executions = await db(ArbitrageService.TABLE_EXECUTIONS)
      .where('tenant_id', tenantId)
      .where('status', 'COMPLETED');

    const byPlatform: Record<string, { count: number; profit: number }> = {};
    const byStatus: Record<string, number> = {};

    for (const opp of opportunities) {
      const platform = opp.target_platform;
      if (!byPlatform[platform]) {
        byPlatform[platform] = { count: 0, profit: 0 };
      }
      byPlatform[platform].count++;

      const status = opp.status;
      byStatus[status] = (byStatus[status] || 0) + 1;
    }

    for (const exec of executions) {
      const opp = opportunities.find(o => o.id === exec.opportunity_id);
      if (opp && exec.actual_profit) {
        byPlatform[opp.target_platform].profit += parseFloat(exec.actual_profit);
      }
    }

    const totalProfit = executions.reduce((sum, e) => sum + (parseFloat(e.actual_profit) || 0), 0);
    const executedCount = opportunities.filter(o => o.status === 'EXECUTED').length;
    const successCount = executions.filter(e => e.status === 'COMPLETED').length;

    const profitSnapshots = opportunities
      .filter(o => o.profit_snapshot)
      .map(o => typeof o.profit_snapshot === 'string' ? JSON.parse(o.profit_snapshot) : o.profit_snapshot);

    return {
      totalOpportunities: opportunities.length,
      pendingOpportunities: opportunities.filter(o => o.status === 'PENDING').length,
      executedOpportunities: executedCount,
      successRate: executedCount > 0 ? successCount / executedCount : 0,
      totalProfit,
      avgProfitMargin: profitSnapshots.length > 0
        ? profitSnapshots.reduce((sum, s) => sum + s.profitRate, 0) / profitSnapshots.length
        : 0,
      avgROI: profitSnapshots.length > 0
        ? profitSnapshots.reduce((sum, s) => sum + s.roi, 0) / profitSnapshots.length
        : 0,
      byPlatform,
      byStatus
    };
  }

  async scanForOpportunities(
    tenantId: string,
    productIds: string[],
    sourcePlatform: string = '1688'
  ): Promise<ArbitrageOpportunity[]> {
    logger.info(`[ArbitrageService] Scanning for opportunities for ${productIds.length} products`);

    const opportunities: ArbitrageOpportunity[] = [];

    for (const productId of productIds) {
      try {
        const mockData = this.getMockProductData(productId);
        if (!mockData) continue;

        const targetPlatforms = ['Amazon', 'Temu', 'TikTok', 'Shopee'];

        for (const targetPlatform of targetPlatforms) {
          const targetPrice = await this.estimateTargetPrice(mockData.sourcePrice, targetPlatform);

          const opportunity = await this.createOpportunity(
            tenantId,
            productId,
            mockData.productName,
            sourcePlatform,
            mockData.sourceUrl,
            mockData.sourcePrice,
            'CNY',
            targetPlatform,
            targetPrice,
            'USD'
          );

          if (opportunity.risk_level !== 'BLOCK') {
            opportunities.push(opportunity);
          }
        }
      } catch (error) {
        logger.error(`[ArbitrageService] Error scanning product ${productId}:`, error);
      }
    }

    logger.info(`[ArbitrageService] Found ${opportunities.length} opportunities`);

    return opportunities;
  }

  private getMockProductData(productId: string): { productName: string; sourcePrice: number; sourceUrl: string } | null {
    return {
      productName: `Product ${productId}`,
      sourcePrice: 50 + Math.random() * 100,
      sourceUrl: `https://1688.com/item/${productId}`
    };
  }

  private async estimateTargetPrice(sourcePriceCNY: number, platform: string): Promise<number> {
    const sourcePriceUSD = sourcePriceCNY * await ExchangeRateService.getRate('CNY', 'USD');
    const multipliers: Record<string, number> = {
      'Amazon': 3.5,
      'Temu': 2.2,
      'TikTok': 2.5,
      'Shopee': 2.0
    };
    const multiplier = multipliers[platform] || 2.5;
    return Number((sourcePriceUSD * multiplier).toFixed(2));
  }

  private mapOpportunityFromDb(row: any): ArbitrageOpportunity {
    return {
      id: row.id,
      tenant_id: row.tenant_id,
      product_id: row.product_id,
      product_name: row.product_name,
      source_platform: row.source_platform,
      source_url: row.source_url,
      source_price: parseFloat(row.source_price),
      source_currency: row.source_currency,
      target_platform: row.target_platform,
      target_price: parseFloat(row.target_price),
      target_currency: row.target_currency,
      profit_snapshot: row.profit_snapshot ? (typeof row.profit_snapshot === 'string' ? JSON.parse(row.profit_snapshot) : row.profit_snapshot) : null,
      risk_level: row.risk_level,
      opportunity_score: row.opportunity_score,
      status: row.status,
      execution_mode: row.execution_mode,
      created_at: row.created_at,
      updated_at: row.updated_at,
      executed_at: row.executed_at,
      error_message: row.error_message
    };
  }

  private mapExecutionFromDb(row: any): ArbitrageExecution {
    return {
      id: row.id,
      opportunity_id: row.opportunity_id,
      tenant_id: row.tenant_id,
      status: row.status,
      purchase_order_id: row.purchase_order_id,
      listing_id: row.listing_id,
      sale_order_id: row.sale_order_id,
      actual_purchase_price: row.actual_purchase_price ? parseFloat(row.actual_purchase_price) : undefined,
      actual_sale_price: row.actual_sale_price ? parseFloat(row.actual_sale_price) : undefined,
      actual_profit: row.actual_profit ? parseFloat(row.actual_profit) : undefined,
      notes: row.notes,
      created_at: row.created_at,
      updated_at: row.updated_at
    };
  }
}

export default ArbitrageService.getInstance();
