import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import { AggregatedMetric, MetricType, TimeGranularity } from '../entities/aggregated-metric.entity';

export interface RevenueSummary {
  totalRevenue: number;
  transactionCount: number;
  averageOrderValue: number;
  revenueGrowth: number;
}

@Injectable()
export class RevenueService {
  constructor(
    @InjectRepository(AggregatedMetric)
    private readonly metricsRepository: Repository<AggregatedMetric>,
  ) {}

  async getRevenueSummary(startDate: string, endDate: string): Promise<RevenueSummary> {
    // Calculate period duration for comparison
    const start = new Date(startDate);
    const end = new Date(endDate);
    const periodMs = end.getTime() - start.getTime();

    // Previous period dates
    const prevEnd = new Date(start.getTime());
    const prevStart = new Date(start.getTime() - periodMs);

    // Current period revenue
    const result = await this.metricsRepository
      .createQueryBuilder('m')
      .select('SUM(m.value)', 'totalRevenue')
      .addSelect('SUM(m.count)', 'transactionCount')
      .where('m.metricType = :type', { type: MetricType.REVENUE })
      .andWhere('m.timestamp BETWEEN :start AND :end', {
        start,
        end,
      })
      .getRawOne();

    // Previous period revenue for comparison
    const prevResult = await this.metricsRepository
      .createQueryBuilder('m')
      .select('SUM(m.value)', 'totalRevenue')
      .where('m.metricType = :type', { type: MetricType.REVENUE })
      .andWhere('m.timestamp BETWEEN :start AND :end', {
        start: prevStart,
        end: prevEnd,
      })
      .getRawOne();

    const totalRevenue = Number(result?.totalRevenue ?? 0);
    const transactionCount = Number(result?.transactionCount ?? 0);
    const prevRevenue = Number(prevResult?.totalRevenue ?? 0);

    // Calculate growth percentage
    const revenueGrowth = prevRevenue > 0 ? ((totalRevenue - prevRevenue) / prevRevenue) * 100 : totalRevenue > 0 ? 100 : 0;

    return {
      totalRevenue,
      transactionCount,
      averageOrderValue: transactionCount > 0 ? totalRevenue / transactionCount : 0,
      revenueGrowth: Math.round(revenueGrowth * 10) / 10,
    };
  }

  async getLTV(startDate: string, endDate: string) {
    // Calculate customer lifetime value from revenue metrics
    // Group revenue by dimension (user cohort) and calculate averages
    const revenueData = await this.metricsRepository
      .createQueryBuilder('m')
      .select('m.dimensionValue', 'cohort')
      .addSelect('SUM(m.value)', 'totalRevenue')
      .addSelect('SUM(m.count)', 'transactionCount')
      .where('m.metricType = :type', { type: MetricType.REVENUE })
      .andWhere('m.timestamp BETWEEN :start AND :end', {
        start: new Date(startDate),
        end: new Date(endDate),
      })
      .andWhere('m.dimension = :dim', { dim: 'user_cohort' })
      .groupBy('m.dimensionValue')
      .getRawMany();

    // If no cohort data, fall back to overall revenue / unique visitors
    if (revenueData.length === 0) {
      const summary = await this.getRevenueSummary(startDate, endDate);
      const visitors = await this.metricsRepository
        .createQueryBuilder('m')
        .select('SUM(m.count)', 'total')
        .where('m.metricType = :type', { type: MetricType.UNIQUE_VISITORS })
        .andWhere('m.timestamp BETWEEN :start AND :end', {
          start: new Date(startDate),
          end: new Date(endDate),
        })
        .getRawOne();

      const visitorCount = Number(visitors?.total ?? 0);
      const averageLTV = visitorCount > 0 ? summary.totalRevenue / visitorCount : 0;

      return {
        averageLTV,
        medianLTV: averageLTV, // Without cohort data, median ≈ average
        ltvDistribution: [{ bucket: 'all', value: averageLTV, count: visitorCount }],
        startDate,
        endDate,
      };
    }

    // Calculate LTV distribution from cohort data
    const ltvValues = revenueData.map((r) => Number(r.totalRevenue));
    const sortedLtv = [...ltvValues].sort((a, b) => a - b);
    const averageLTV = ltvValues.reduce((sum, v) => sum + v, 0) / ltvValues.length;
    const medianLTV =
      sortedLtv.length % 2 === 0
        ? ((sortedLtv[sortedLtv.length / 2 - 1] ?? 0) + (sortedLtv[sortedLtv.length / 2] ?? 0)) / 2
        : sortedLtv[Math.floor(sortedLtv.length / 2)] ?? 0;

    // Create distribution buckets
    const buckets = [0, 10, 50, 100, 250, 500, 1000, Infinity];
    const ltvDistribution = buckets.slice(0, -1).map((min, i) => {
      const max = buckets[i + 1] ?? Infinity;
      const count = ltvValues.filter((v) => v >= min && v < max).length;
      return { bucket: `${min}-${max === Infinity ? '+' : max}`, value: (min + (max === Infinity ? min * 2 : max)) / 2, count };
    });

    return {
      averageLTV,
      medianLTV,
      ltvDistribution: ltvDistribution.filter((b) => b.count > 0),
      startDate,
      endDate,
    };
  }

  async getARPU(startDate: string, endDate: string, granularity: 'day' | 'week' | 'month') {
    // Get revenue data by period
    const revenueData = await this.metricsRepository.find({
      where: {
        metricType: MetricType.REVENUE,
        granularity: granularity as TimeGranularity,
        timestamp: Between(new Date(startDate), new Date(endDate)),
      },
      order: { timestamp: 'ASC' },
    });

    // Get unique visitors data by period for the same granularity
    const visitorData = await this.metricsRepository.find({
      where: {
        metricType: MetricType.UNIQUE_VISITORS,
        granularity: granularity as TimeGranularity,
        timestamp: Between(new Date(startDate), new Date(endDate)),
      },
      order: { timestamp: 'ASC' },
    });

    // Create a map of timestamp -> visitor count
    const visitorMap = new Map<string, number>();
    for (const v of visitorData) {
      visitorMap.set(v.timestamp.toISOString(), Number(v.count));
    }

    // Calculate ARPU for each period
    const data = revenueData.map((d) => {
      const visitors = visitorMap.get(d.timestamp.toISOString()) ?? 0;
      const revenue = Number(d.value);
      return {
        timestamp: d.timestamp,
        revenue,
        visitors,
        arpu: visitors > 0 ? revenue / visitors : 0,
      };
    });

    // Calculate average ARPU across all periods
    const totalRevenue = data.reduce((sum, d) => sum + d.revenue, 0);
    const totalVisitors = data.reduce((sum, d) => sum + d.visitors, 0);
    const averageARPU = totalVisitors > 0 ? totalRevenue / totalVisitors : 0;

    return {
      data,
      averageARPU,
    };
  }

  async getMRR(month: string) {
    // Current month range
    const startDate = new Date(month + '-01');
    const endDate = new Date(startDate);
    endDate.setMonth(endDate.getMonth() + 1);

    // Previous month range
    const prevStartDate = new Date(startDate);
    prevStartDate.setMonth(prevStartDate.getMonth() - 1);
    const prevEndDate = new Date(startDate);

    // Get current month MRR
    const currentResult = await this.metricsRepository
      .createQueryBuilder('m')
      .select('SUM(m.value)', 'mrr')
      .where('m.metricType = :type', { type: MetricType.REVENUE })
      .andWhere('m.dimension = :dim', { dim: 'recurring' })
      .andWhere('m.timestamp BETWEEN :start AND :end', {
        start: startDate,
        end: endDate,
      })
      .getRawOne();

    // Get previous month MRR for comparison
    const prevResult = await this.metricsRepository
      .createQueryBuilder('m')
      .select('SUM(m.value)', 'mrr')
      .where('m.metricType = :type', { type: MetricType.REVENUE })
      .andWhere('m.dimension = :dim', { dim: 'recurring' })
      .andWhere('m.timestamp BETWEEN :start AND :end', {
        start: prevStartDate,
        end: prevEndDate,
      })
      .getRawOne();

    const mrr = Number(currentResult?.mrr ?? 0);
    const prevMrr = Number(prevResult?.mrr ?? 0);
    const mrrGrowth = prevMrr > 0 ? ((mrr - prevMrr) / prevMrr) * 100 : mrr > 0 ? 100 : 0;

    return {
      month,
      mrr,
      previousMrr: prevMrr,
      mrrGrowth,
      mrrChange: mrr - prevMrr,
    };
  }
}
