import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { DailyStatisticsORM } from '../../infrastructure/database/postgres/entities/daily-statistics.orm';

export interface DailyStatisticsDto {
  statDate: string;
  dimension: string;
  dimensionValue: string | null;
  newUsers: number;
  newRegisteredUsers: number;
  activeUsers: number;
  newConversations: number;
  totalMessages: number;
  userMessages: number;
  assistantMessages: number;
  avgConversationTurns: number;
  newOrders: number;
  paidOrders: number;
  totalOrderAmount: number;
  totalPaidAmount: number;
  refundedOrders: number;
  refundAmount: number;
  conversionCount: number;
  conversionRate: number;
  totalInputTokens: number;
  totalOutputTokens: number;
  estimatedApiCost: number;
}

export interface TrendDataPoint {
  date: string;
  value: number;
}

@Injectable()
export class StatisticsAggregationService {
  private readonly logger = new Logger(StatisticsAggregationService.name);

  constructor(
    @InjectRepository(DailyStatisticsORM)
    private readonly statsRepo: Repository<DailyStatisticsORM>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Aggregate daily statistics for a specific date
   */
  async aggregateDailyStats(date: Date): Promise<void> {
    const statDate = date.toISOString().split('T')[0];
    this.logger.log(`Aggregating statistics for date: ${statDate}`);

    try {
      // Aggregate OVERALL statistics
      await this.aggregateOverallStats(statDate);

      // Aggregate by CHANNEL
      await this.aggregateByChannel(statDate);

      // Aggregate by CATEGORY
      await this.aggregateByCategory(statDate);

      this.logger.log(`Statistics aggregation completed for: ${statDate}`);
    } catch (error) {
      this.logger.error(`Statistics aggregation failed for ${statDate}:`, error);
      throw error;
    }
  }

  /**
   * Refresh today's statistics (for real-time dashboard)
   */
  async refreshTodayStats(): Promise<void> {
    const today = new Date();
    await this.aggregateDailyStats(today);
  }

  /**
   * Get daily statistics with filters
   */
  async getDailyStatistics(params: {
    startDate: string;
    endDate: string;
    dimension?: string;
    dimensionValue?: string;
  }): Promise<DailyStatisticsDto[]> {
    const query = this.statsRepo.createQueryBuilder('stats')
      .where('stats.statDate >= :startDate', { startDate: params.startDate })
      .andWhere('stats.statDate <= :endDate', { endDate: params.endDate });

    if (params.dimension) {
      query.andWhere('stats.dimension = :dimension', { dimension: params.dimension });
    }

    if (params.dimensionValue) {
      query.andWhere('stats.dimensionValue = :dimensionValue', { dimensionValue: params.dimensionValue });
    }

    const results = await query.orderBy('stats.statDate', 'DESC').getMany();

    return results.map(this.toDto);
  }

  /**
   * Get today's real-time statistics
   */
  async getTodayStatistics(): Promise<DailyStatisticsDto | null> {
    const today = new Date().toISOString().split('T')[0];
    const stats = await this.statsRepo.findOne({
      where: { statDate: new Date(today), dimension: 'OVERALL' },
    });

    return stats ? this.toDto(stats) : null;
  }

  /**
   * Get trend data for charts
   */
  async getTrendData(params: {
    days: number;
    metrics: string[];
  }): Promise<Record<string, TrendDataPoint[]>> {
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - params.days);

    const stats = await this.statsRepo.find({
      where: { dimension: 'OVERALL' },
      order: { statDate: 'ASC' },
    });

    const filtered = stats.filter(s => s.statDate >= startDate);

    const result: Record<string, TrendDataPoint[]> = {};
    for (const metric of params.metrics) {
      result[metric] = filtered.map(s => ({
        date: s.statDate instanceof Date
          ? s.statDate.toISOString().split('T')[0]
          : String(s.statDate).split('T')[0],
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        value: Number((s as any)[metric] || 0),
      }));
    }

    return result;
  }

  /**
   * Get statistics by channel
   */
  async getStatisticsByChannel(startDate: string, endDate: string): Promise<DailyStatisticsDto[]> {
    return this.getDailyStatistics({
      startDate,
      endDate,
      dimension: 'CHANNEL',
    });
  }

  /**
   * Get statistics by category
   */
  async getStatisticsByCategory(startDate: string, endDate: string): Promise<DailyStatisticsDto[]> {
    return this.getDailyStatistics({
      startDate,
      endDate,
      dimension: 'CATEGORY',
    });
  }

  private async aggregateOverallStats(statDate: string): Promise<void> {
    await this.dataSource.query(
      `
      INSERT INTO daily_statistics (
        stat_date, dimension, dimension_value,
        new_users, new_registered_users, active_users,
        new_conversations, total_messages, user_messages, assistant_messages, avg_conversation_turns,
        new_orders, paid_orders, total_order_amount, total_paid_amount, refunded_orders, refund_amount,
        conversion_count, conversion_rate,
        total_input_tokens, total_output_tokens, estimated_api_cost
      )
      SELECT
        $1::date as stat_date,
        'OVERALL' as dimension,
        NULL as dimension_value,

        -- User statistics
        (SELECT COUNT(*) FROM users WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as new_users,
        (SELECT COUNT(*) FROM users WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day' AND type = 'REGISTERED') as new_registered_users,
        (SELECT COUNT(*) FROM users WHERE last_active_at >= $1::date AND last_active_at < $1::date + interval '1 day') as active_users,

        -- Conversation statistics
        (SELECT COUNT(*) FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as new_conversations,
        (SELECT COUNT(*) FROM messages WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as total_messages,
        (SELECT COUNT(*) FROM messages WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day' AND role = 'user') as user_messages,
        (SELECT COUNT(*) FROM messages WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day' AND role = 'assistant') as assistant_messages,
        (SELECT COALESCE(AVG(message_count / 2.0), 0) FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day' AND message_count > 0) as avg_conversation_turns,

        -- Order statistics
        (SELECT COUNT(*) FROM orders WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as new_orders,
        (SELECT COUNT(*) FROM orders WHERE paid_at >= $1::date AND paid_at < $1::date + interval '1 day') as paid_orders,
        (SELECT COALESCE(SUM(amount), 0) FROM orders WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as total_order_amount,
        (SELECT COALESCE(SUM(paid_amount), 0) FROM orders WHERE paid_at >= $1::date AND paid_at < $1::date + interval '1 day') as total_paid_amount,
        (SELECT COUNT(*) FROM orders WHERE refunded_at >= $1::date AND refunded_at < $1::date + interval '1 day') as refunded_orders,
        (SELECT COALESCE(SUM(paid_amount), 0) FROM orders WHERE refunded_at >= $1::date AND refunded_at < $1::date + interval '1 day') as refund_amount,

        -- Conversion statistics
        (SELECT COUNT(*) FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day' AND has_converted = true) as conversion_count,
        CASE
          WHEN (SELECT COUNT(*) FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') > 0
          THEN (SELECT COUNT(*)::decimal FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day' AND has_converted = true) /
               (SELECT COUNT(*)::decimal FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day')
          ELSE 0
        END as conversion_rate,

        -- Token statistics
        (SELECT COALESCE(SUM(total_input_tokens), 0) FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as total_input_tokens,
        (SELECT COALESCE(SUM(total_output_tokens), 0) FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as total_output_tokens,
        -- Estimated cost: $3/1M input, $15/1M output (Claude Sonnet 4)
        (SELECT COALESCE(SUM(total_input_tokens), 0) * 3.0 / 1000000 + COALESCE(SUM(total_output_tokens), 0) * 15.0 / 1000000
         FROM conversations WHERE created_at >= $1::date AND created_at < $1::date + interval '1 day') as estimated_api_cost

      ON CONFLICT (stat_date, dimension, dimension_value)
      DO UPDATE SET
        new_users = EXCLUDED.new_users,
        new_registered_users = EXCLUDED.new_registered_users,
        active_users = EXCLUDED.active_users,
        new_conversations = EXCLUDED.new_conversations,
        total_messages = EXCLUDED.total_messages,
        user_messages = EXCLUDED.user_messages,
        assistant_messages = EXCLUDED.assistant_messages,
        avg_conversation_turns = EXCLUDED.avg_conversation_turns,
        new_orders = EXCLUDED.new_orders,
        paid_orders = EXCLUDED.paid_orders,
        total_order_amount = EXCLUDED.total_order_amount,
        total_paid_amount = EXCLUDED.total_paid_amount,
        refunded_orders = EXCLUDED.refunded_orders,
        refund_amount = EXCLUDED.refund_amount,
        conversion_count = EXCLUDED.conversion_count,
        conversion_rate = EXCLUDED.conversion_rate,
        total_input_tokens = EXCLUDED.total_input_tokens,
        total_output_tokens = EXCLUDED.total_output_tokens,
        estimated_api_cost = EXCLUDED.estimated_api_cost,
        updated_at = NOW()
      `,
      [statDate]
    );
  }

  private async aggregateByChannel(statDate: string): Promise<void> {
    await this.dataSource.query(
      `
      INSERT INTO daily_statistics (
        stat_date, dimension, dimension_value,
        new_users, new_registered_users, active_users,
        new_conversations, total_messages, user_messages, assistant_messages, avg_conversation_turns,
        new_orders, paid_orders, total_order_amount, total_paid_amount, refunded_orders, refund_amount,
        conversion_count, conversion_rate,
        total_input_tokens, total_output_tokens, estimated_api_cost
      )
      SELECT
        $1::date as stat_date,
        'CHANNEL' as dimension,
        u.source_channel as dimension_value,

        COUNT(DISTINCT CASE WHEN u.created_at >= $1::date AND u.created_at < $1::date + interval '1 day' THEN u.id END) as new_users,
        COUNT(DISTINCT CASE WHEN u.created_at >= $1::date AND u.created_at < $1::date + interval '1 day' AND u.type = 'REGISTERED' THEN u.id END) as new_registered_users,
        COUNT(DISTINCT CASE WHEN u.last_active_at >= $1::date AND u.last_active_at < $1::date + interval '1 day' THEN u.id END) as active_users,

        COUNT(DISTINCT CASE WHEN c.created_at >= $1::date AND c.created_at < $1::date + interval '1 day' THEN c.id END) as new_conversations,
        0 as total_messages,
        0 as user_messages,
        0 as assistant_messages,
        0 as avg_conversation_turns,

        COUNT(DISTINCT CASE WHEN o.created_at >= $1::date AND o.created_at < $1::date + interval '1 day' THEN o.id END) as new_orders,
        COUNT(DISTINCT CASE WHEN o.paid_at >= $1::date AND o.paid_at < $1::date + interval '1 day' THEN o.id END) as paid_orders,
        COALESCE(SUM(CASE WHEN o.created_at >= $1::date AND o.created_at < $1::date + interval '1 day' THEN o.amount ELSE 0 END), 0) as total_order_amount,
        COALESCE(SUM(CASE WHEN o.paid_at >= $1::date AND o.paid_at < $1::date + interval '1 day' THEN o.paid_amount ELSE 0 END), 0) as total_paid_amount,
        0 as refunded_orders,
        0 as refund_amount,

        COUNT(DISTINCT CASE WHEN c.has_converted = true AND c.created_at >= $1::date AND c.created_at < $1::date + interval '1 day' THEN c.id END) as conversion_count,
        0 as conversion_rate,

        COALESCE(SUM(c.total_input_tokens), 0) as total_input_tokens,
        COALESCE(SUM(c.total_output_tokens), 0) as total_output_tokens,
        COALESCE(SUM(c.total_input_tokens), 0) * 3.0 / 1000000 + COALESCE(SUM(c.total_output_tokens), 0) * 15.0 / 1000000 as estimated_api_cost

      FROM users u
      LEFT JOIN conversations c ON u.id = c.user_id
      LEFT JOIN orders o ON u.id = o.user_id
      WHERE u.source_channel IS NOT NULL
      GROUP BY u.source_channel
      ON CONFLICT (stat_date, dimension, dimension_value)
      DO UPDATE SET
        new_users = EXCLUDED.new_users,
        new_registered_users = EXCLUDED.new_registered_users,
        active_users = EXCLUDED.active_users,
        new_conversations = EXCLUDED.new_conversations,
        new_orders = EXCLUDED.new_orders,
        paid_orders = EXCLUDED.paid_orders,
        total_order_amount = EXCLUDED.total_order_amount,
        total_paid_amount = EXCLUDED.total_paid_amount,
        conversion_count = EXCLUDED.conversion_count,
        total_input_tokens = EXCLUDED.total_input_tokens,
        total_output_tokens = EXCLUDED.total_output_tokens,
        estimated_api_cost = EXCLUDED.estimated_api_cost,
        updated_at = NOW()
      `,
      [statDate]
    );
  }

  private async aggregateByCategory(statDate: string): Promise<void> {
    await this.dataSource.query(
      `
      INSERT INTO daily_statistics (
        stat_date, dimension, dimension_value,
        new_users, new_registered_users, active_users,
        new_conversations, total_messages, user_messages, assistant_messages, avg_conversation_turns,
        new_orders, paid_orders, total_order_amount, total_paid_amount, refunded_orders, refund_amount,
        conversion_count, conversion_rate,
        total_input_tokens, total_output_tokens, estimated_api_cost
      )
      SELECT
        $1::date as stat_date,
        'CATEGORY' as dimension,
        c.category as dimension_value,

        0 as new_users,
        0 as new_registered_users,
        COUNT(DISTINCT c.user_id) as active_users,

        COUNT(DISTINCT CASE WHEN c.created_at >= $1::date AND c.created_at < $1::date + interval '1 day' THEN c.id END) as new_conversations,
        0 as total_messages,
        0 as user_messages,
        0 as assistant_messages,
        COALESCE(AVG(c.message_count / 2.0), 0) as avg_conversation_turns,

        COUNT(DISTINCT CASE WHEN o.created_at >= $1::date AND o.created_at < $1::date + interval '1 day' THEN o.id END) as new_orders,
        COUNT(DISTINCT CASE WHEN o.paid_at >= $1::date AND o.paid_at < $1::date + interval '1 day' THEN o.id END) as paid_orders,
        COALESCE(SUM(CASE WHEN o.created_at >= $1::date AND o.created_at < $1::date + interval '1 day' THEN o.amount ELSE 0 END), 0) as total_order_amount,
        COALESCE(SUM(CASE WHEN o.paid_at >= $1::date AND o.paid_at < $1::date + interval '1 day' THEN o.paid_amount ELSE 0 END), 0) as total_paid_amount,
        0 as refunded_orders,
        0 as refund_amount,

        COUNT(DISTINCT CASE WHEN c.has_converted = true THEN c.id END) as conversion_count,
        0 as conversion_rate,

        COALESCE(SUM(c.total_input_tokens), 0) as total_input_tokens,
        COALESCE(SUM(c.total_output_tokens), 0) as total_output_tokens,
        COALESCE(SUM(c.total_input_tokens), 0) * 3.0 / 1000000 + COALESCE(SUM(c.total_output_tokens), 0) * 15.0 / 1000000 as estimated_api_cost

      FROM conversations c
      LEFT JOIN orders o ON c.id = o.conversation_id
      WHERE c.category IS NOT NULL
        AND c.created_at >= $1::date AND c.created_at < $1::date + interval '1 day'
      GROUP BY c.category
      ON CONFLICT (stat_date, dimension, dimension_value)
      DO UPDATE SET
        active_users = EXCLUDED.active_users,
        new_conversations = EXCLUDED.new_conversations,
        avg_conversation_turns = EXCLUDED.avg_conversation_turns,
        new_orders = EXCLUDED.new_orders,
        paid_orders = EXCLUDED.paid_orders,
        total_order_amount = EXCLUDED.total_order_amount,
        total_paid_amount = EXCLUDED.total_paid_amount,
        conversion_count = EXCLUDED.conversion_count,
        total_input_tokens = EXCLUDED.total_input_tokens,
        total_output_tokens = EXCLUDED.total_output_tokens,
        estimated_api_cost = EXCLUDED.estimated_api_cost,
        updated_at = NOW()
      `,
      [statDate]
    );
  }

  private toDto(orm: DailyStatisticsORM): DailyStatisticsDto {
    // Handle statDate being either Date or string
    const statDateStr = orm.statDate instanceof Date
      ? orm.statDate.toISOString().split('T')[0]
      : String(orm.statDate).split('T')[0];

    return {
      statDate: statDateStr,
      dimension: orm.dimension,
      dimensionValue: orm.dimensionValue,
      newUsers: Number(orm.newUsers),
      newRegisteredUsers: Number(orm.newRegisteredUsers),
      activeUsers: Number(orm.activeUsers),
      newConversations: Number(orm.newConversations),
      totalMessages: Number(orm.totalMessages),
      userMessages: Number(orm.userMessages),
      assistantMessages: Number(orm.assistantMessages),
      avgConversationTurns: Number(orm.avgConversationTurns),
      newOrders: Number(orm.newOrders),
      paidOrders: Number(orm.paidOrders),
      totalOrderAmount: Number(orm.totalOrderAmount),
      totalPaidAmount: Number(orm.totalPaidAmount),
      refundedOrders: Number(orm.refundedOrders),
      refundAmount: Number(orm.refundAmount),
      conversionCount: Number(orm.conversionCount),
      conversionRate: Number(orm.conversionRate),
      totalInputTokens: Number(orm.totalInputTokens),
      totalOutputTokens: Number(orm.totalOutputTokens),
      estimatedApiCost: Number(orm.estimatedApiCost),
    };
  }
}
