import db from '../config/database';
import { logger } from '../utils/logger';
import { UserValueAnalysisService, UserValueScore } from './UserValueAnalysisService';
import { UserAssetService } from './UserAssetService';
import { MemberLevel } from '../models/UserAsset';

export interface RecommendationInput {
  tenantId: string;
  shopId: string;
  userId: string;
  traceId: string;
  taskId?: string;
  businessType: 'TOC' | 'TOB';
  context?: RecommendationContext;
}

export interface RecommendationContext {
  pageType?: 'HOME' | 'PRODUCT_DETAIL' | 'CART' | 'CATEGORY' | 'SEARCH';
  currentProductId?: string;
  currentCategoryId?: string;
  searchQuery?: string;
  cartProductIds?: string[];
  recentlyViewedIds?: string[];
}

export interface RecommendationResult {
  userId: string;
  tenantId: string;
  shopId: string;
  recommendations: RecommendationItem[];
  strategy: RecommendationStrategy;
  traceId: string;
  generatedAt: Date;
}

export interface RecommendationItem {
  productId: string;
  productName: string;
  productImage?: string;
  price: number;
  originalPrice?: number;
  score: number;
  reason: string;
  tags: string[];
  position: number;
}

export type RecommendationStrategy = 
  | 'VALUE_BASED'
  | 'COLLABORATIVE_FILTERING'
  | 'CONTENT_BASED'
  | 'POPULARITY'
  | 'HYBRID';

export interface UserPreference {
  userId: string;
  tenantId: string;
  shopId: string;
  preferredCategories: string[];
  preferredBrands: string[];
  priceRange: {
    min: number;
    max: number;
  };
  preferredAttributes: Record<string, string[]>;
  lastUpdated: Date;
}

export interface ProductFeature {
  productId: string;
  tenantId: string;
  shopId: string;
  categoryId: string;
  brandId?: string;
  attributes: Record<string, string>;
  price: number;
  salesCount: number;
  rating: number;
  tags: string[];
}

export interface RecommendationConfig {
  maxRecommendations: number;
  minScore: number;
  diversityWeight: number;
  noveltyWeight: number;
  recencyWeight: number;
}

export class PersonalizedRecommendService {
  private static readonly TABLE_NAME = 'cf_user_recommendation';
  private static readonly PREFERENCE_TABLE = 'cf_user_preference';
  private static readonly HISTORY_TABLE = 'cf_recommendation_history';

  private static readonly DEFAULT_CONFIG: RecommendationConfig = {
    maxRecommendations: 20,
    minScore: 0.3,
    diversityWeight: 0.2,
    noveltyWeight: 0.15,
    recencyWeight: 0.1,
  };

  private static readonly MEMBER_LEVEL_MULTIPLIERS: Record<MemberLevel, number> = {
    BRONZE: 1.0,
    SILVER: 1.1,
    GOLD: 1.2,
    PLATINUM: 1.3,
    DIAMOND: 1.5,
  };

  static async initTable(): Promise<void> {
    const hasTable = await db.schema.hasTable(this.TABLE_NAME);
    if (!hasTable) {
      logger.info(`[PersonalizedRecommendService] Creating ${this.TABLE_NAME} table...`);
      await db.schema.createTable(this.TABLE_NAME, (table) => {
        table.string('id', 36).primary();
        table.string('tenant_id', 64).notNullable().index();
        table.string('shop_id', 64).notNullable().index();
        table.string('user_id', 64).notNullable().index();
        table.string('product_id', 64).notNullable();
        table.string('product_name', 255).notNullable();
        table.string('product_image', 512);
        table.decimal('price', 12, 2).notNullable();
        table.decimal('original_price', 12, 2);
        table.decimal('score', 5, 4).notNullable();
        table.string('reason', 255).notNullable();
        table.json('tags').notNullable();
        table.integer('position').notNullable();
        table.string('strategy', 32).notNullable();
        table.string('trace_id', 64).notNullable();
        table.string('task_id', 36);
        table.timestamp('generated_at').defaultTo(db.fn.now());
        table.timestamp('created_at').defaultTo(db.fn.now());

        table.index(['user_id', 'generated_at'], 'idx_recommend_user_time');
        table.index(['product_id'], 'idx_recommend_product');
      });
      logger.info(`[PersonalizedRecommendService] Table ${this.TABLE_NAME} created`);
    }

    const hasPreferenceTable = await db.schema.hasTable(this.PREFERENCE_TABLE);
    if (!hasPreferenceTable) {
      logger.info(`[PersonalizedRecommendService] Creating ${this.PREFERENCE_TABLE} table...`);
      await db.schema.createTable(this.PREFERENCE_TABLE, (table) => {
        table.string('id', 36).primary();
        table.string('tenant_id', 64).notNullable().index();
        table.string('shop_id', 64).notNullable().index();
        table.string('user_id', 64).notNullable().index();
        table.json('preferred_categories').notNullable();
        table.json('preferred_brands').notNullable();
        table.json('price_range').notNullable();
        table.json('preferred_attributes').notNullable();
        table.timestamp('last_updated').defaultTo(db.fn.now());
        table.timestamp('created_at').defaultTo(db.fn.now());

        table.unique(['tenant_id', 'shop_id', 'user_id'], 'uk_preference_user');
      });
      logger.info(`[PersonalizedRecommendService] Table ${this.PREFERENCE_TABLE} created`);
    }

    const hasHistoryTable = await db.schema.hasTable(this.HISTORY_TABLE);
    if (!hasHistoryTable) {
      logger.info(`[PersonalizedRecommendService] Creating ${this.HISTORY_TABLE} table...`);
      await db.schema.createTable(this.HISTORY_TABLE, (table) => {
        table.string('id', 36).primary();
        table.string('tenant_id', 64).notNullable().index();
        table.string('shop_id', 64).notNullable().index();
        table.string('user_id', 64).notNullable().index();
        table.string('product_id', 64).notNullable();
        table.string('action', 32).notNullable();
        table.string('trace_id', 64).notNullable();
        table.timestamp('created_at').defaultTo(db.fn.now());

        table.index(['user_id', 'product_id'], 'idx_history_user_product');
        table.index(['created_at'], 'idx_history_time');
      });
      logger.info(`[PersonalizedRecommendService] Table ${this.HISTORY_TABLE} created`);
    }
  }

  static async generateRecommendations(input: RecommendationInput): Promise<RecommendationResult> {
    const { tenantId, shopId, userId, traceId, taskId, businessType, context } = input;

    logger.info(`[PersonalizedRecommendService] Generating recommendations: userId=${userId}, tenantId=${tenantId}, traceId=${traceId}`);

    const userScore = await UserValueAnalysisService.getUserScore(tenantId, shopId, userId);
    const userAsset = await UserAssetService.getByUserId(tenantId, shopId, userId);
    const userPreference = await this.getUserPreference(tenantId, shopId, userId, traceId);

    const strategy = this.determineStrategy(userScore, context);
    const candidateProducts = await this.getCandidateProducts(
      tenantId,
      shopId,
      userPreference,
      context,
      traceId
    );

    const scoredProducts = await this.scoreProducts(
      candidateProducts,
      userScore,
      userAsset,
      userPreference,
      context,
      traceId
    );

    const diversifiedProducts = this.applyDiversity(scoredProducts, this.DEFAULT_CONFIG);

    const recommendations = diversifiedProducts
      .slice(0, this.DEFAULT_CONFIG.maxRecommendations)
      .map((p, index) => ({
        productId: p.productId,
        productName: p.productName,
        productImage: p.productImage,
        price: p.price,
        originalPrice: p.originalPrice,
        score: p.score,
        reason: p.reason,
        tags: p.tags,
        position: index + 1,
      }));

    const result: RecommendationResult = {
      userId,
      tenantId,
      shopId,
      recommendations,
      strategy,
      traceId,
      generatedAt: new Date(),
    };

    await this.saveRecommendations(result, taskId, businessType);

    logger.info(`[PersonalizedRecommendService] Generated ${recommendations.length} recommendations: userId=${userId}, strategy=${strategy}, traceId=${traceId}`);

    return result;
  }

  private static determineStrategy(
    userScore: UserValueScore | null,
    context?: RecommendationContext
  ): RecommendationStrategy {
    if (userScore && userScore.scoreLevel === 'VIP') {
      return 'VALUE_BASED';
    }

    if (context?.currentProductId) {
      return 'CONTENT_BASED';
    }

    if (userScore && userScore.dimensions.engagement.score > 50) {
      return 'COLLABORATIVE_FILTERING';
    }

    return 'HYBRID';
  }

  private static async getUserPreference(
    tenantId: string,
    shopId: string,
    userId: string,
    traceId: string
  ): Promise<UserPreference> {
    const record = await db(this.PREFERENCE_TABLE)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .first();

    if (record) {
      return {
        userId,
        tenantId,
        shopId,
        preferredCategories: JSON.parse(record.preferred_categories),
        preferredBrands: JSON.parse(record.preferred_brands),
        priceRange: JSON.parse(record.price_range),
        preferredAttributes: JSON.parse(record.preferred_attributes),
        lastUpdated: record.last_updated,
      };
    }

    return {
      userId,
      tenantId,
      shopId,
      preferredCategories: [],
      preferredBrands: [],
      priceRange: { min: 0, max: 10000 },
      preferredAttributes: {},
      lastUpdated: new Date(),
    };
  }

  private static async getCandidateProducts(
    tenantId: string,
    shopId: string,
    preference: UserPreference,
    context?: RecommendationContext,
    traceId?: string
  ): Promise<ProductFeature[]> {
    const candidates: ProductFeature[] = [];

    try {
      const productTable = 'cf_product';
      const hasTable = await db.schema.hasTable(productTable);
      
      if (!hasTable) {
        return this.getMockProducts(tenantId, shopId);
      }

      let query = db(productTable)
        .where({ tenant_id: tenantId })
        .where('status', 'ACTIVE')
        .limit(100);

      if (preference.preferredCategories.length > 0) {
        query = query.whereIn('category_id', preference.preferredCategories);
      }

      const products = await query;

      for (const p of products) {
        candidates.push({
          productId: p.id,
          tenantId: p.tenant_id,
          shopId: p.shop_id || shopId,
          categoryId: p.category_id,
          brandId: p.brand_id,
          attributes: JSON.parse(p.attributes || '{}'),
          price: Number(p.price),
          salesCount: Number(p.sales_count || 0),
          rating: Number(p.rating || 0),
          tags: JSON.parse(p.tags || '[]'),
        });
      }
    } catch (error) {
      logger.error(`[PersonalizedRecommendService] Failed to get candidate products: traceId=${traceId}`);
      return this.getMockProducts(tenantId, shopId);
    }

    return candidates;
  }

  private static getMockProducts(tenantId: string, shopId: string): ProductFeature[] {
    const mockProducts: ProductFeature[] = [];
    const categories = ['electronics', 'clothing', 'home', 'beauty', 'sports'];
    const tags = ['bestseller', 'new', 'trending', 'recommended', 'hot'];

    for (let i = 1; i <= 50; i++) {
      mockProducts.push({
        productId: `prod_${i}`,
        tenantId,
        shopId,
        categoryId: categories[i % categories.length],
        price: Math.floor(Math.random() * 500) + 50,
        salesCount: Math.floor(Math.random() * 1000),
        rating: Math.round((Math.random() * 2 + 3) * 10) / 10,
        tags: [tags[i % tags.length]],
        attributes: {},
      });
    }

    return mockProducts;
  }

  private static async scoreProducts(
    products: ProductFeature[],
    userScore: UserValueScore | null,
    userAsset: any,
    preference: UserPreference,
    context?: RecommendationContext,
    traceId?: string
  ): Promise<(ProductFeature & { score: number; reason: string })[]> {
    const memberLevel = userAsset?.memberLevel || 'BRONZE';
    const levelMultiplier = this.MEMBER_LEVEL_MULTIPLIERS[memberLevel];

    const scoredProducts = products.map((product) => {
      let score = 0;
      const reasons: string[] = [];

      const categoryMatch = preference.preferredCategories.includes(product.categoryId);
      if (categoryMatch) {
        score += 0.3;
        reasons.push('符合您的偏好分类');
      }

      const priceInRange =
        product.price >= preference.priceRange.min &&
        product.price <= preference.priceRange.max;
      if (priceInRange) {
        score += 0.2;
        reasons.push('价格适中');
      }

      if (product.rating >= 4.5) {
        score += 0.15;
        reasons.push('高评分商品');
      }

      if (product.salesCount > 500) {
        score += 0.1;
        reasons.push('热销商品');
      }

      if (product.tags.includes('new')) {
        score += 0.1;
        reasons.push('新品上架');
      }

      if (userScore) {
        if (userScore.scoreLevel === 'VIP' && product.price > 500) {
          score += 0.15;
          reasons.push('VIP专属推荐');
        }

        if (userScore.dimensions.spending.score > 70 && product.price > 300) {
          score += 0.1;
          reasons.push('品质好物');
        }
      }

      score *= levelMultiplier;

      score = Math.min(score, 1);

      return {
        ...product,
        score,
        reason: reasons.length > 0 ? reasons[0] : '为您推荐',
      };
    });

    return scoredProducts.sort((a, b) => b.score - a.score);
  }

  private static applyDiversity(
    products: (ProductFeature & { score: number; reason: string })[],
    config: RecommendationConfig
  ): (ProductFeature & { score: number; reason: string })[] {
    const result: (ProductFeature & { score: number; reason: string })[] = [];
    const categoryCount: Record<string, number> = {};

    for (const product of products) {
      const category = product.categoryId;
      const count = categoryCount[category] || 0;

      if (count < 3) {
        result.push(product);
        categoryCount[category] = count + 1;
      }

      if (result.length >= config.maxRecommendations * 1.5) {
        break;
      }
    }

    return result;
  }

  private static async saveRecommendations(
    result: RecommendationResult,
    taskId?: string,
    businessType?: 'TOC' | 'TOB'
  ): Promise<void> {
    for (const item of result.recommendations) {
      const id = this.generateId();

      await db(this.TABLE_NAME).insert({
        id,
        tenant_id: result.tenantId,
        shop_id: result.shopId,
        user_id: result.userId,
        product_id: item.productId,
        product_name: item.productName,
        product_image: item.productImage || null,
        price: item.price,
        original_price: item.originalPrice || null,
        score: item.score,
        reason: item.reason,
        tags: JSON.stringify(item.tags),
        position: item.position,
        strategy: result.strategy,
        trace_id: result.traceId,
        task_id: taskId || null,
        generated_at: result.generatedAt,
      });
    }
  }

  static async updateUserPreference(
    tenantId: string,
    shopId: string,
    userId: string,
    action: 'VIEW' | 'CART' | 'PURCHASE' | 'WISHLIST',
    productId: string,
    traceId: string
  ): Promise<void> {
    await db(this.HISTORY_TABLE).insert({
      id: this.generateId(),
      tenant_id: tenantId,
      shop_id: shopId,
      user_id: userId,
      product_id: productId,
      action,
      trace_id: traceId,
    });

    await this.recalculatePreference(tenantId, shopId, userId, traceId);
  }

  private static async recalculatePreference(
    tenantId: string,
    shopId: string,
    userId: string,
    traceId: string
  ): Promise<void> {
    const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

    const history = await db(this.HISTORY_TABLE)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .where('created_at', '>=', thirtyDaysAgo)
      .select('product_id', 'action');

    if (history.length === 0) {
      return;
    }

    const categoryWeights: Record<string, number> = {};
    const brandWeights: Record<string, number> = {};
    const prices: number[] = [];

    const actionWeights: Record<string, number> = {
      VIEW: 1,
      WISHLIST: 2,
      CART: 3,
      PURCHASE: 5,
    };

    for (const h of history) {
      const weight = actionWeights[h.action] || 1;
      categoryWeights[`cat_${h.product_id}`] = (categoryWeights[`cat_${h.product_id}`] || 0) + weight;
    }

    const preferredCategories = Object.entries(categoryWeights)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5)
      .map(([cat]) => cat);

    const priceRange = prices.length > 0
      ? {
          min: Math.min(...prices) * 0.8,
          max: Math.max(...prices) * 1.2,
        }
      : { min: 0, max: 10000 };

    const existing = await db(this.PREFERENCE_TABLE)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .first();

    if (existing) {
      await db(this.PREFERENCE_TABLE)
        .where({ id: existing.id })
        .update({
          preferred_categories: JSON.stringify(preferredCategories),
          preferred_brands: JSON.stringify(Object.keys(brandWeights)),
          price_range: JSON.stringify(priceRange),
          preferred_attributes: JSON.stringify({}),
          last_updated: new Date(),
        });
    } else {
      await db(this.PREFERENCE_TABLE).insert({
        id: this.generateId(),
        tenant_id: tenantId,
        shop_id: shopId,
        user_id: userId,
        preferred_categories: JSON.stringify(preferredCategories),
        preferred_brands: JSON.stringify(Object.keys(brandWeights)),
        price_range: JSON.stringify(priceRange),
        preferred_attributes: JSON.stringify({}),
      });
    }

    logger.info(`[PersonalizedRecommendService] Updated user preference: userId=${userId}, traceId=${traceId}`);
  }

  static async getRecommendationHistory(
    tenantId: string,
    shopId: string,
    userId: string,
    limit: number = 10
  ): Promise<RecommendationResult[]> {
    const records = await db(this.TABLE_NAME)
      .where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
      .orderBy('generated_at', 'desc')
      .limit(limit * 20);

    const groupedByTrace: Record<string, RecommendationItem[]> = {};

    for (const record of records) {
      const trace = record.trace_id;
      if (!groupedByTrace[trace]) {
        groupedByTrace[trace] = [];
      }

      groupedByTrace[trace].push({
        productId: record.product_id,
        productName: record.product_name,
        productImage: record.product_image,
        price: Number(record.price),
        originalPrice: record.original_price ? Number(record.original_price) : undefined,
        score: Number(record.score),
        reason: record.reason,
        tags: JSON.parse(record.tags),
        position: record.position,
      });
    }

    const results: RecommendationResult[] = [];
    for (const [trace, items] of Object.entries(groupedByTrace)) {
      const firstRecord = records.find((r) => r.trace_id === trace);
      if (firstRecord) {
        results.push({
          userId,
          tenantId,
          shopId,
          recommendations: items.sort((a, b) => a.position - b.position),
          strategy: firstRecord.strategy as RecommendationStrategy,
          traceId: trace,
          generatedAt: firstRecord.generated_at,
        });
      }

      if (results.length >= limit) break;
    }

    return results;
  }

  static async getSimilarUsers(
    tenantId: string,
    shopId: string,
    userId: string,
    traceId: string
  ): Promise<string[]> {
    const userScore = await UserValueAnalysisService.getUserScore(tenantId, shopId, userId);

    if (!userScore) {
      return [];
    }

    const similarUsers = await db('cf_user_value_score')
      .where({ tenant_id: tenantId, shop_id: shopId })
      .where('user_id', '!=', userId)
      .whereBetween('overall_score', [
        userScore.overallScore - 10,
        userScore.overallScore + 10,
      ])
      .select('user_id')
      .limit(20);

    return similarUsers.map((u: any) => u.user_id);
  }

  static async getCrossSellRecommendations(
    tenantId: string,
    shopId: string,
    userId: string,
    cartProductIds: string[],
    traceId: string,
    businessType: 'TOC' | 'TOB'
  ): Promise<RecommendationItem[]> {
    const context: RecommendationContext = {
      pageType: 'CART',
      cartProductIds,
    };

    const result = await this.generateRecommendations({
      tenantId,
      shopId,
      userId,
      traceId,
      businessType,
      context,
    });

    return result.recommendations.filter(
      (r) => !cartProductIds.includes(r.productId)
    );
  }

  static async trackRecommendationClick(
    tenantId: string,
    shopId: string,
    userId: string,
    productId: string,
    position: number,
    traceId: string
  ): Promise<void> {
    await db(this.HISTORY_TABLE).insert({
      id: this.generateId(),
      tenant_id: tenantId,
      shop_id: shopId,
      user_id: userId,
      product_id: productId,
      action: 'RECOMMENDATION_CLICK',
      trace_id: traceId,
    });

    logger.info(`[PersonalizedRecommendService] Tracked recommendation click: userId=${userId}, productId=${productId}, position=${position}, traceId=${traceId}`);
  }

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