import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, MoreThan, Between } from 'typeorm';
import { ServerStatsHourly } from '../../entities/server-stats-hourly.entity';
import { ServerStats } from '../../entities/server-stats.entity';
import { WipeHistory } from '../../entities/wipe-history.entity';
import { PlayerSession } from '../../entities/player-session.entity';
import { MapLibrary } from '../../entities/map-library.entity';

@Injectable()
export class AnalyticsService {
  constructor(
    @InjectRepository(ServerStatsHourly)
    private readonly statsHourlyRepo: Repository<ServerStatsHourly>,
    @InjectRepository(ServerStats)
    private readonly statsRepo: Repository<ServerStats>,
    @InjectRepository(WipeHistory)
    private readonly wipeHistoryRepo: Repository<WipeHistory>,
    @InjectRepository(PlayerSession)
    private readonly playerSessionRepo: Repository<PlayerSession>,
    @InjectRepository(MapLibrary)
    private readonly mapLibraryRepo: Repository<MapLibrary>,
  ) {}

  async getSummary(licenseId: string, rangeHours: number) {
    const cutoff = new Date(Date.now() - rangeHours * 3600 * 1000);

    const stats = await this.statsHourlyRepo
      .createQueryBuilder('stats')
      .select('MAX(stats.max_players)', 'peak_players')
      .addSelect('AVG(stats.avg_players)', 'avg_players')
      .addSelect('AVG(stats.uptime_percentage)', 'uptime_percentage')
      .where('stats.license_id = :licenseId', { licenseId })
      .andWhere('stats.hour >= :cutoff', { cutoff })
      .getRawOne();

    return {
      peak_players: stats?.peak_players || 0,
      avg_players: parseFloat(stats?.avg_players || 0),
      uptime_percentage: parseFloat(stats?.uptime_percentage || 0),
      unique_players: null, // Not implemented yet
    };
  }

  async getTimeseries(licenseId: string, rangeHours: number, granularity: 'raw' | 'hourly') {
    const cutoff = new Date(Date.now() - rangeHours * 3600 * 1000);

    if (granularity === 'hourly') {
      const data = await this.statsHourlyRepo.find({
        where: { license_id: licenseId, hour: MoreThan(cutoff) },
        order: { hour: 'ASC' },
      });

      return {
        timestamps: data.map(d => d.hour),
        player_count: data.map(d => d.avg_players),
        fps: data.map(d => d.avg_fps),
        entity_count: data.map(d => d.avg_entities),
        memory_usage_mb: data.map(() => null), // Not in schema
      };
    } else {
      const data = await this.statsRepo.find({
        where: { license_id: licenseId, recorded_at: MoreThan(cutoff) },
        order: { recorded_at: 'ASC' },
      });

      return {
        timestamps: data.map(d => d.recorded_at),
        player_count: data.map(d => d.player_count),
        fps: data.map(d => d.fps),
        entity_count: data.map(d => d.entity_count),
        memory_usage_mb: data.map(d => d.memory_usage_mb),
      };
    }
  }

  async getWipePerformance(licenseId: string, rangeHours: number) {
    const cutoff = new Date(Date.now() - rangeHours * 3600 * 1000);

    const wipes = await this.wipeHistoryRepo.find({
      where: {
        license_id: licenseId,
        status: 'success',
        started_at: MoreThan(cutoff),
      },
      order: { started_at: 'DESC' },
    });

    const durations = wipes
      .filter(w => w.started_at && w.completed_at)
      .map(w => (w.completed_at!.getTime() - w.started_at!.getTime()) / 1000);

    return {
      total_wipes: wipes.length,
      avg_duration_seconds: durations.length > 0
        ? durations.reduce((a, b) => a + b, 0) / durations.length
        : 0,
      min_duration_seconds: durations.length > 0 ? Math.min(...durations) : 0,
      max_duration_seconds: durations.length > 0 ? Math.max(...durations) : 0,
      wipe_types: wipes.reduce((acc, w) => {
        acc[w.wipe_type] = (acc[w.wipe_type] || 0) + 1;
        return acc;
      }, {} as Record<string, number>),
    };
  }

  async getMapAnalytics(licenseId: string, rangeHours: number) {
    const cutoff = new Date(Date.now() - rangeHours * 3600 * 1000);

    const mapUsage = await this.wipeHistoryRepo
      .createQueryBuilder('wipe')
      .leftJoinAndSelect('wipe.map', 'map')
      .select('map.id', 'map_id')
      .addSelect('map.name', 'map_name')
      .addSelect('COUNT(wipe.id)', 'usage_count')
      .where('wipe.license_id = :licenseId', { licenseId })
      .andWhere('wipe.started_at >= :cutoff', { cutoff })
      .andWhere('wipe.map_id IS NOT NULL')
      .groupBy('map.id')
      .addGroupBy('map.name')
      .getRawMany();

    return {
      map_usage: mapUsage.map(m => ({
        map_id: m.map_id,
        map_name: m.map_name,
        usage_count: parseInt(m.usage_count),
      })),
    };
  }

  async getPlayerAnalytics(licenseId: string, rangeHours: number, metric: 'sessions' | 'retention') {
    const cutoff = new Date(Date.now() - rangeHours * 3600 * 1000);

    if (metric === 'sessions') {
      const sessions = await this.playerSessionRepo.find({
        where: {
          license_id: licenseId,
          session_start: MoreThan(cutoff),
        },
        order: { session_start: 'DESC' },
      });

      const totalDuration = sessions
        .filter(s => s.duration_seconds)
        .reduce((sum, s) => sum + (s.duration_seconds || 0), 0);

      return {
        total_sessions: sessions.length,
        avg_session_duration: sessions.length > 0 ? totalDuration / sessions.length : 0,
        unique_players: new Set(sessions.map(s => s.steam_id)).size,
      };
    }

    return { message: 'Retention metric not implemented' };
  }

  async getRetention(licenseId: string, wipeCount: number) {
    const recentWipes = await this.wipeHistoryRepo.find({
      where: { license_id: licenseId, status: 'success' },
      order: { started_at: 'DESC' },
      take: wipeCount,
    });

    if (recentWipes.length === 0) {
      return { wipe_count: 0, retention_data: [] };
    }

    const retentionData = await Promise.all(
      recentWipes.map(async (wipe) => {
        const wipeDate = wipe.started_at;
        // Find the next wipe chronologically after this one (wipes are DESC ordered)
        const nextWipe = recentWipes
          .slice()
          .reverse()
          .find(w => w.started_at && wipeDate && w.started_at > wipeDate);
        const endDate = nextWipe?.started_at || new Date();

        // Query sessions strictly within this wipe cycle: [wipeDate, endDate)
        const sessionsInPeriod = await this.playerSessionRepo.find({
          where: {
            license_id: licenseId,
            session_start: Between(wipeDate!, endDate),
          },
        });

        const uniquePlayers = new Set(sessionsInPeriod.map(s => s.steam_id)).size;

        return {
          wipe_date: wipeDate,
          end_date: endDate,
          unique_players: uniquePlayers,
          total_sessions: sessionsInPeriod.length,
        };
      }),
    );

    return {
      wipe_count: recentWipes.length,
      retention_data: retentionData,
    };
  }

  async exportData(licenseId: string, rangeHours: number): Promise<string> {
    const cutoff = new Date(Date.now() - rangeHours * 3600 * 1000);

    const stats = await this.statsRepo.find({
      where: { license_id: licenseId, recorded_at: MoreThan(cutoff) },
      order: { recorded_at: 'ASC' },
    });

    // Generate CSV
    const headers = 'timestamp,player_count,fps,entity_count,memory_mb\n';
    const rows = stats.map(s =>
      `${s.recorded_at.toISOString()},${s.player_count},${s.fps},${s.entity_count},${s.memory_usage_mb}`
    ).join('\n');

    return headers + rows;
  }
}
