import { v4 as uuidv4 } from 'uuid';
import db from '../config/database';
import { logger } from '../utils/logger';

export interface MerchantMetrics {
  id: string;
  tenant_id: string;
  shop_id: string;
  total_revenue: number;
  total_cost: number;
  total_profit: number;
  roi: number;
  growth_rate: number;
  order_count: number;
  product_count: number;
  avg_order_value: number;
  return_rate: number;
  top_strategy: string | null;
  top_platform: string | null;
  tier: 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM' | 'DIAMOND';
  is_verified: boolean;
  metrics_date: Date;
  created_at: Date;
  updated_at: Date;
}

export interface MetricsCalculationInput {
  tenantId: string;
  shopId: string;
  period: 'DAILY' | 'WEEKLY' | 'MONTHLY';
}

export interface LeaderboardEntry {
  rank: number;
  tenant_id: string;
  tenant_name: string;
  shop_id: string;
  shop_name: string;
  value: number;
  tier: string;
  is_verified: boolean;
  growth_rate?: number;
}

export class MerchantMetricsService {
  private static instance: MerchantMetricsService;

  private constructor() {}

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

  async calculateAndStoreMetrics(input: MetricsCalculationInput): Promise<MerchantMetrics> {
    const { tenantId, shopId, period } = input;
    const metricsDate = this.getMetricsDate(period);

    const existingMetrics = await this.getMetricsByTenantAndDate(tenantId, metricsDate);
    if (existingMetrics) {
      logger.info(`[MerchantMetricsService] Metrics already exist for tenant ${tenantId} on ${metricsDate}`);
      return existingMetrics;
    }

    const metricsData = await this.aggregateMetrics(tenantId, shopId, period);

    const metrics: MerchantMetrics = {
      id: uuidv4(),
      tenant_id: tenantId,
      shop_id: shopId,
      total_revenue: metricsData.totalRevenue,
      total_cost: metricsData.totalCost,
      total_profit: metricsData.totalProfit,
      roi: metricsData.roi,
      growth_rate: metricsData.growthRate,
      order_count: metricsData.orderCount,
      product_count: metricsData.productCount,
      avg_order_value: metricsData.avgOrderValue,
      return_rate: metricsData.returnRate,
      top_strategy: metricsData.topStrategy,
      top_platform: metricsData.topPlatform,
      tier: this.calculateTier(metricsData.totalProfit),
      is_verified: false,
      metrics_date: metricsDate,
      created_at: new Date(),
      updated_at: new Date()
    };

    await db('cf_merchant_metrics').insert(metrics);
    logger.info(`[MerchantMetricsService] Created metrics for tenant ${tenantId}`);

    return metrics;
  }

  async getMetricsByTenantAndDate(tenantId: string, date: Date): Promise<MerchantMetrics | null> {
    const metrics = await db('cf_merchant_metrics')
      .where('tenant_id', tenantId)
      .where('metrics_date', date)
      .first();

    return metrics || null;
  }

  async getLatestMetrics(tenantId: string): Promise<MerchantMetrics | null> {
    const metrics = await db('cf_merchant_metrics')
      .where('tenant_id', tenantId)
      .orderBy('metrics_date', 'desc')
      .first();

    return metrics || null;
  }

  async getMetricsHistory(
    tenantId: string,
    startDate: Date,
    endDate: Date
  ): Promise<MerchantMetrics[]> {
    return db('cf_merchant_metrics')
      .where('tenant_id', tenantId)
      .whereBetween('metrics_date', [startDate, endDate])
      .orderBy('metrics_date', 'asc');
  }

  async getTopMerchants(
    type: 'REVENUE' | 'ROI' | 'GROWTH',
    limit: number = 10,
    period: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME' = 'MONTHLY'
  ): Promise<LeaderboardEntry[]> {
    const metricsDate = this.getMetricsDate(period === 'ALL_TIME' ? 'MONTHLY' : period);
    
    let query = db('cf_merchant_metrics as mm')
      .join('cf_tenant as t', 'mm.tenant_id', 't.id')
      .join('cf_shop as s', 'mm.shop_id', 's.id')
      .select(
        'mm.tenant_id',
        't.name as tenant_name',
        'mm.shop_id',
        's.name as shop_name',
        'mm.tier',
        'mm.is_verified',
        'mm.growth_rate'
      );

    switch (type) {
      case 'REVENUE':
        query = query.select('mm.total_profit as value').orderBy('mm.total_profit', 'desc');
        break;
      case 'ROI':
        query = query.select('mm.roi as value').orderBy('mm.roi', 'desc');
        break;
      case 'GROWTH':
        query = query.select('mm.growth_rate as value').orderBy('mm.growth_rate', 'desc');
        break;
    }

    if (period !== 'ALL_TIME') {
      query = query.where('mm.metrics_date', metricsDate);
    }

    const results = await query.limit(limit);

    return results.map((row, index) => ({
      rank: index + 1,
      tenant_id: row.tenant_id,
      tenant_name: row.tenant_name,
      shop_id: row.shop_id,
      shop_name: row.shop_name,
      value: parseFloat(row.value) || 0,
      tier: row.tier,
      is_verified: row.is_verified,
      growth_rate: parseFloat(row.growth_rate) || 0
    }));
  }

  async verifyMetrics(metricsId: string): Promise<void> {
    await db('cf_merchant_metrics')
      .where('id', metricsId)
      .update({ is_verified: true, updated_at: new Date() });

    logger.info(`[MerchantMetricsService] Verified metrics ${metricsId}`);
  }

  async flagSuspiciousMetrics(): Promise<MerchantMetrics[]> {
    const suspicious = await db('cf_merchant_metrics')
      .where('roi', '>', 10)
      .orWhere('growth_rate', '>', 5)
      .orWhere('return_rate', '<', 0.01);

    logger.warn(`[MerchantMetricsService] Found ${suspicious.length} potentially suspicious metrics`);
    return suspicious;
  }

  private async aggregateMetrics(
    tenantId: string,
    shopId: string,
    period: 'DAILY' | 'WEEKLY' | 'MONTHLY'
  ): Promise<{
    totalRevenue: number;
    totalCost: number;
    totalProfit: number;
    roi: number;
    growthRate: number;
    orderCount: number;
    productCount: number;
    avgOrderValue: number;
    returnRate: number;
    topStrategy: string | null;
    topPlatform: string | null;
  }> {
    const { startDate, endDate } = this.getPeriodRange(period);

    const orders = await db('cf_order')
      .where('tenant_id', tenantId)
      .where('shop_id', shopId)
      .whereBetween('created_at', [startDate, endDate]);

    const products = await db('cf_product')
      .where('tenant_id', tenantId)
      .where('shop_id', shopId)
      .whereBetween('created_at', [startDate, endDate]);

    const totalRevenue = orders.reduce((sum, o) => sum + (parseFloat(o.total_amount) || 0), 0);
    const totalCost = orders.reduce((sum, o) => sum + (parseFloat(o.cost_amount) || 0), 0);
    const totalProfit = totalRevenue - totalCost;
    const roi = totalCost > 0 ? totalProfit / totalCost : 0;

    const previousPeriodMetrics = await this.getPreviousPeriodMetrics(tenantId, period);
    const growthRate = previousPeriodMetrics.totalProfit > 0
      ? (totalProfit - previousPeriodMetrics.totalProfit) / previousPeriodMetrics.totalProfit
      : 0;

    const orderCount = orders.length;
    const productCount = products.length;
    const avgOrderValue = orderCount > 0 ? totalRevenue / orderCount : 0;

    const returnOrders = orders.filter(o => o.status === 'RETURNED' || o.status === 'REFUNDED');
    const returnRate = orderCount > 0 ? returnOrders.length / orderCount : 0;

    const topStrategy = await this.getTopStrategy(tenantId, startDate, endDate);
    const topPlatform = await this.getTopPlatform(tenantId, startDate, endDate);

    return {
      totalRevenue,
      totalCost,
      totalProfit,
      roi,
      growthRate,
      orderCount,
      productCount,
      avgOrderValue,
      returnRate,
      topStrategy,
      topPlatform
    };
  }

  private async getPreviousPeriodMetrics(
    tenantId: string,
    period: 'DAILY' | 'WEEKLY' | 'MONTHLY'
  ): Promise<{ totalProfit: number }> {
    const previousDate = this.getPreviousPeriodDate(period);
    
    const metrics = await db('cf_merchant_metrics')
      .where('tenant_id', tenantId)
      .where('metrics_date', previousDate)
      .first();

    return {
      totalProfit: metrics ? parseFloat(metrics.total_profit) || 0 : 0
    };
  }

  private async getTopStrategy(tenantId: string, startDate: Date, endDate: Date): Promise<string | null> {
    const strategies = await db('cf_order')
      .where('tenant_id', tenantId)
      .whereBetween('created_at', [startDate, endDate])
      .whereNotNull('strategy')
      .groupBy('strategy')
      .select('strategy')
      .count('* as count')
      .orderBy('count', 'desc')
      .first();

    return strategies?.strategy ? String(strategies.strategy) : null;
  }

  private async getTopPlatform(tenantId: string, startDate: Date, endDate: Date): Promise<string | null> {
    const platforms = await db('cf_order')
      .where('tenant_id', tenantId)
      .whereBetween('created_at', [startDate, endDate])
      .whereNotNull('platform')
      .groupBy('platform')
      .select('platform')
      .count('* as count')
      .orderBy('count', 'desc')
      .first();

    return platforms?.platform ? String(platforms.platform) : null;
  }

  private calculateTier(totalProfit: number): 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM' | 'DIAMOND' {
    if (totalProfit >= 100000) return 'DIAMOND';
    if (totalProfit >= 50000) return 'PLATINUM';
    if (totalProfit >= 20000) return 'GOLD';
    if (totalProfit >= 5000) return 'SILVER';
    return 'BRONZE';
  }

  private getMetricsDate(period: 'DAILY' | 'WEEKLY' | 'MONTHLY'): Date {
    const now = new Date();
    switch (period) {
      case 'DAILY':
        return new Date(now.getFullYear(), now.getMonth(), now.getDate());
      case 'WEEKLY':
        const weekStart = new Date(now);
        weekStart.setDate(now.getDate() - now.getDay());
        return new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate());
      case 'MONTHLY':
        return new Date(now.getFullYear(), now.getMonth(), 1);
      default:
        return new Date(now.getFullYear(), now.getMonth(), 1);
    }
  }

  private getPeriodRange(period: 'DAILY' | 'WEEKLY' | 'MONTHLY'): { startDate: Date; endDate: Date } {
    const now = new Date();
    let startDate: Date;
    const endDate = new Date(now);

    switch (period) {
      case 'DAILY':
        startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        break;
      case 'WEEKLY':
        startDate = new Date(now);
        startDate.setDate(now.getDate() - 7);
        break;
      case 'MONTHLY':
        startDate = new Date(now.getFullYear(), now.getMonth(), 1);
        break;
      default:
        startDate = new Date(now.getFullYear(), now.getMonth(), 1);
    }

    return { startDate, endDate };
  }

  private getPreviousPeriodDate(period: 'DAILY' | 'WEEKLY' | 'MONTHLY'): Date {
    const now = new Date();
    switch (period) {
      case 'DAILY':
        const yesterday = new Date(now);
        yesterday.setDate(now.getDate() - 1);
        return new Date(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate());
      case 'WEEKLY':
        const lastWeek = new Date(now);
        lastWeek.setDate(now.getDate() - 14);
        const weekStart = new Date(lastWeek);
        weekStart.setDate(lastWeek.getDate() - lastWeek.getDay());
        return new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate());
      case 'MONTHLY':
        return new Date(now.getFullYear(), now.getMonth() - 1, 1);
      default:
        return new Date(now.getFullYear(), now.getMonth() - 1, 1);
    }
  }
}

export default MerchantMetricsService.getInstance();
