import { logger } from '../../utils/logger';
import db from '../../config/database';

interface ProductScore {
  productId: string;
  tenantId: string;
  overallScore: number;
  dimensions: {
    marketDemand: number;
    competitionLevel: number;
    profitMargin: number;
    supplyStability: number;
    seasonality: number;
    trendScore: number;
  };
  grade: 'A+' | 'A' | 'B' | 'C' | 'D' | 'F';
  recommendation: 'STRONG_BUY' | 'BUY' | 'HOLD' | 'AVOID' | 'STRONG_AVOID';
  confidence: number;
  scoredAt: Date;
}

interface ScoringFactors {
  salesVelocity?: number;
  reviewCount?: number;
  reviewRating?: number;
  pricePoint?: number;
  categoryTrend?: number;
  competitorCount?: number;
  supplierRating?: number;
  leadTime?: number;
  seasonalityIndex?: number;
  historicalPerformance?: number;
}

interface ScoreHistory {
  historyId: string;
  productId: string;
  score: number;
  dimensions: ProductScore['dimensions'];
  scoredAt: Date;
}

export class ProductScoringService {
  private static SCORE_WEIGHTS = {
    marketDemand: 0.25,
    competitionLevel: 0.20,
    profitMargin: 0.25,
    supplyStability: 0.15,
    seasonality: 0.10,
    trendScore: 0.05
  };

  private static GRADE_THRESHOLDS = {
    'A+': 95,
    'A': 85,
    'B': 70,
    'C': 55,
    'D': 40,
    'F': 0
  };

  private static generateId(prefix: string): string {
    return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  static async calculateScore(params: {
    productId: string;
    tenantId: string;
    factors: ScoringFactors;
  }): Promise<ProductScore> {
    const { productId, tenantId, factors } = params;

    logger.info('[ProductScoringService] Calculating product score', {
      productId,
      tenantId
    });

    const dimensions = {
      marketDemand: this.calculateMarketDemandScore(factors),
      competitionLevel: this.calculateCompetitionScore(factors),
      profitMargin: this.calculateProfitMarginScore(factors),
      supplyStability: this.calculateSupplyStabilityScore(factors),
      seasonality: this.calculateSeasonalityScore(factors),
      trendScore: this.calculateTrendScore(factors)
    };

    const overallScore = this.calculateOverallScore(dimensions);
    const grade = this.determineGrade(overallScore);
    const recommendation = this.determineRecommendation(overallScore, dimensions);
    const confidence = this.calculateConfidence(factors);

    const score: ProductScore = {
      productId,
      tenantId,
      overallScore,
      dimensions,
      grade,
      recommendation,
      confidence,
      scoredAt: new Date()
    };

    await this.saveScore(score);

    return score;
  }

  static async batchScore(params: {
    tenantId: string;
    productIds: string[];
  }): Promise<ProductScore[]> {
    const { tenantId, productIds } = params;

    logger.info('[ProductScoringService] Batch scoring products', {
      tenantId,
      count: productIds.length
    });

    const results: ProductScore[] = [];

    for (const productId of productIds) {
      const factors = await this.gatherScoringFactors(productId, tenantId);
      const score = await this.calculateScore({ productId, tenantId, factors });
      results.push(score);
    }

    return results;
  }

  static async getScoreHistory(params: {
    productId: string;
    tenantId: string;
    limit?: number;
  }): Promise<ScoreHistory[]> {
    const { productId, tenantId, limit = 30 } = params;

    const history = await db('cf_product_score_history')
      .where({
        product_id: productId,
        tenant_id: tenantId
      })
      .orderBy('scored_at', 'desc')
      .limit(limit);

    return history.map(h => ({
      historyId: h.id,
      productId: h.product_id,
      score: h.overall_score,
      dimensions: JSON.parse(h.dimensions || '{}'),
      scoredAt: h.scored_at
    }));
  }

  static async getTopScoredProducts(params: {
    tenantId: string;
    limit?: number;
    minScore?: number;
    category?: string;
  }): Promise<ProductScore[]> {
    const { tenantId, limit = 20, minScore = 70, category } = params;

    let query = db('cf_product_score')
      .where({
        tenant_id: tenantId
      })
      .where('overall_score', '>=', minScore)
      .orderBy('overall_score', 'desc')
      .limit(limit);

    if (category) {
      query = query.whereRaw(`
        product_id IN (
          SELECT id FROM cf_product WHERE tenant_id = ? AND category = ?
        )
      `, [tenantId, category]);
    }

    const scores = await query;

    return scores.map(s => ({
      productId: s.product_id,
      tenantId: s.tenant_id,
      overallScore: s.overall_score,
      dimensions: JSON.parse(s.dimensions || '{}'),
      grade: s.grade,
      recommendation: s.recommendation,
      confidence: s.confidence,
      scoredAt: s.scored_at
    }));
  }

  static async compareScores(params: {
    tenantId: string;
    productIds: string[];
  }): Promise<{
    products: ProductScore[];
    comparison: {
      bestOverall: string;
      bestProfitMargin: string;
      bestDemand: string;
      lowestCompetition: string;
    };
  }> {
    const { tenantId, productIds } = params;

    const products = await Promise.all(
      productIds.map(productId => 
        this.getLatestScore({ productId, tenantId })
      )
    );

    const validProducts = products.filter((p): p is ProductScore => p !== null);

    const comparison = {
      bestOverall: this.findBest(validProducts, 'overallScore'),
      bestProfitMargin: this.findBest(validProducts, 'profitMargin', 'dimensions'),
      bestDemand: this.findBest(validProducts, 'marketDemand', 'dimensions'),
      lowestCompetition: this.findBest(validProducts, 'competitionLevel', 'dimensions', true)
    };

    return { products: validProducts, comparison };
  }

  private static calculateMarketDemandScore(factors: ScoringFactors): number {
    let score = 50;

    if (factors.salesVelocity !== undefined) {
      if (factors.salesVelocity > 100) score += 30;
      else if (factors.salesVelocity > 50) score += 20;
      else if (factors.salesVelocity > 20) score += 10;
      else if (factors.salesVelocity < 5) score -= 20;
    }

    if (factors.reviewCount !== undefined) {
      if (factors.reviewCount > 500) score += 15;
      else if (factors.reviewCount > 100) score += 10;
      else if (factors.reviewCount > 20) score += 5;
    }

    if (factors.categoryTrend !== undefined) {
      score += factors.categoryTrend * 10;
    }

    return Math.max(0, Math.min(100, score));
  }

  private static calculateCompetitionScore(factors: ScoringFactors): number {
    let score = 50;

    if (factors.competitorCount !== undefined) {
      if (factors.competitorCount < 10) score += 40;
      else if (factors.competitorCount < 30) score += 20;
      else if (factors.competitorCount < 100) score += 0;
      else if (factors.competitorCount > 500) score -= 30;
      else if (factors.competitorCount > 200) score -= 15;
    }

    if (factors.reviewRating !== undefined) {
      if (factors.reviewRating > 4.5) score += 10;
      else if (factors.reviewRating > 4.0) score += 5;
      else if (factors.reviewRating < 3.5) score -= 10;
    }

    return Math.max(0, Math.min(100, score));
  }

  private static calculateProfitMarginScore(factors: ScoringFactors): number {
    let score = 50;

    if (factors.pricePoint !== undefined) {
      if (factors.pricePoint > 50) score += 20;
      else if (factors.pricePoint > 20) score += 10;
      else if (factors.pricePoint < 5) score -= 20;
    }

    if (factors.historicalPerformance !== undefined) {
      score += factors.historicalPerformance * 0.3;
    }

    return Math.max(0, Math.min(100, score));
  }

  private static calculateSupplyStabilityScore(factors: ScoringFactors): number {
    let score = 50;

    if (factors.supplierRating !== undefined) {
      if (factors.supplierRating > 4.5) score += 30;
      else if (factors.supplierRating > 4.0) score += 20;
      else if (factors.supplierRating > 3.5) score += 10;
      else if (factors.supplierRating < 3.0) score -= 20;
    }

    if (factors.leadTime !== undefined) {
      if (factors.leadTime <= 3) score += 20;
      else if (factors.leadTime <= 7) score += 10;
      else if (factors.leadTime > 21) score -= 20;
      else if (factors.leadTime > 14) score -= 10;
    }

    return Math.max(0, Math.min(100, score));
  }

  private static calculateSeasonalityScore(factors: ScoringFactors): number {
    let score = 50;

    if (factors.seasonalityIndex !== undefined) {
      if (factors.seasonalityIndex < 0.2) score += 40;
      else if (factors.seasonalityIndex < 0.4) score += 20;
      else if (factors.seasonalityIndex > 0.8) score -= 30;
      else if (factors.seasonalityIndex > 0.6) score -= 15;
    }

    return Math.max(0, Math.min(100, score));
  }

  private static calculateTrendScore(factors: ScoringFactors): number {
    let score = 50;

    if (factors.categoryTrend !== undefined) {
      score += factors.categoryTrend * 20;
    }

    if (factors.historicalPerformance !== undefined) {
      if (factors.historicalPerformance > 0) score += 15;
      else if (factors.historicalPerformance < 0) score -= 15;
    }

    return Math.max(0, Math.min(100, score));
  }

  private static calculateOverallScore(dimensions: ProductScore['dimensions']): number {
    const weights = this.SCORE_WEIGHTS;
    
    return Math.round(
      dimensions.marketDemand * weights.marketDemand +
      dimensions.competitionLevel * weights.competitionLevel +
      dimensions.profitMargin * weights.profitMargin +
      dimensions.supplyStability * weights.supplyStability +
      dimensions.seasonality * weights.seasonality +
      dimensions.trendScore * weights.trendScore
    );
  }

  private static determineGrade(score: number): ProductScore['grade'] {
    if (score >= this.GRADE_THRESHOLDS['A+']) return 'A+';
    if (score >= this.GRADE_THRESHOLDS['A']) return 'A';
    if (score >= this.GRADE_THRESHOLDS['B']) return 'B';
    if (score >= this.GRADE_THRESHOLDS['C']) return 'C';
    if (score >= this.GRADE_THRESHOLDS['D']) return 'D';
    return 'F';
  }

  private static determineRecommendation(
    score: number,
    dimensions: ProductScore['dimensions']
  ): ProductScore['recommendation'] {
    if (score >= 85 && dimensions.profitMargin >= 70) return 'STRONG_BUY';
    if (score >= 70) return 'BUY';
    if (score >= 50) return 'HOLD';
    if (score >= 30) return 'AVOID';
    return 'STRONG_AVOID';
  }

  private static calculateConfidence(factors: ScoringFactors): number {
    const factorCount = Object.values(factors).filter(v => v !== undefined).length;
    const maxFactors = 10;
    
    return Math.round((factorCount / maxFactors) * 100);
  }

  private static async gatherScoringFactors(
    productId: string,
    tenantId: string
  ): Promise<ScoringFactors> {
    const product = await db('cf_product')
      .where({ id: productId, tenant_id: tenantId })
      .first();

    if (!product) {
      return {};
    }

    const listings = await db('cf_listing')
      .where({ product_id: productId, tenant_id: tenantId });

    const totalSales = listings.reduce((sum, l) => sum + (l.sales_count || 0), 0);
    const avgRating = listings.length > 0
      ? listings.reduce((sum, l) => sum + (l.rating || 0), 0) / listings.length
      : 0;

    return {
      salesVelocity: totalSales,
      reviewCount: product.review_count,
      reviewRating: avgRating,
      pricePoint: product.price,
      competitorCount: listings.length,
      supplierRating: product.supplier_rating,
      leadTime: product.lead_time
    };
  }

  private static async saveScore(score: ProductScore): Promise<void> {
    try {
      await db('cf_product_score').insert({
        id: this.generateId('score'),
        product_id: score.productId,
        tenant_id: score.tenantId,
        overall_score: score.overallScore,
        dimensions: JSON.stringify(score.dimensions),
        grade: score.grade,
        recommendation: score.recommendation,
        confidence: score.confidence,
        scored_at: score.scoredAt,
        created_at: new Date()
      });

      await db('cf_product_score_history').insert({
        id: this.generateId('score_hist'),
        product_id: score.productId,
        tenant_id: score.tenantId,
        overall_score: score.overallScore,
        dimensions: JSON.stringify(score.dimensions),
        scored_at: score.scoredAt,
        created_at: new Date()
      });
    } catch (error: any) {
      logger.error('[ProductScoringService] Failed to save score', {
        productId: score.productId,
        error: error.message
      });
    }
  }

  private static async getLatestScore(params: {
    productId: string;
    tenantId: string;
  }): Promise<ProductScore | null> {
    const { productId, tenantId } = params;

    const score = await db('cf_product_score')
      .where({
        product_id: productId,
        tenant_id: tenantId
      })
      .orderBy('scored_at', 'desc')
      .first();

    if (!score) return null;

    return {
      productId: score.product_id,
      tenantId: score.tenant_id,
      overallScore: score.overall_score,
      dimensions: JSON.parse(score.dimensions || '{}'),
      grade: score.grade,
      recommendation: score.recommendation,
      confidence: score.confidence,
      scoredAt: score.scored_at
    };
  }

  private static findBest(
    products: ProductScore[],
    field: keyof ProductScore | keyof ProductScore['dimensions'],
    nested?: 'dimensions',
    lowest = false
  ): string {
    if (products.length === 0) return '';

    const getValue = (p: ProductScore): number => {
      if (nested && field in p.dimensions) {
        return p.dimensions[field as keyof ProductScore['dimensions']];
      }
      return p[field as keyof ProductScore] as number;
    };

    const sorted = [...products].sort((a, b) => {
      const aVal = getValue(a);
      const bVal = getValue(b);
      return lowest ? aVal - bVal : bVal - aVal;
    });

    return sorted[0].productId;
  }
}

export default ProductScoringService;
