import { HttpException, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import { FeatureAnalytics } from './entities/feature-analytics.entity';
import { CreateFeatureAnalyticsDto } from './dto/creaate-feature-analytics.dto';
import { AnalyticsQueryDto } from './dto/query-analytics.dto';
import { GeoDetails } from './interface/feature-analytics.interface';
import {
  FeatureMetricsResponse,
  FeatureUsageOverTimeResponse,
  GeoDistributionResponse,
  UserAgentDistributionResponse,
  HomeAnalyticsRespone,
} from './dto/analytics-response.dto';
import { UAParser } from 'ua-parser-js';

@Injectable()
export class FeatureAnalyticsService {
  private readonly logger = new Logger(FeatureAnalyticsService.name);
  constructor(
    @InjectRepository(FeatureAnalytics)
    private readonly analyticsRepository: Repository<FeatureAnalytics>,
  ) {}

  // Create a new analytics record
  async createAnalytics(data: CreateFeatureAnalyticsDto): Promise<FeatureAnalytics> {
    const newEntry = this.analyticsRepository.create(data);
    return await this.analyticsRepository.save(newEntry);
  }

  // Get feature usage count per feature
  async getFeatureUsageCount(): Promise<
    Array<{
      featureName: string;
      count: number;
      uniqueUsers: number;
      firstUsed: Date;
      lastUsed: Date;
    }>
  > {
    this.logger.log('Fetching enhanced feature usage count');

    const result = await this.analyticsRepository
      .createQueryBuilder('analytics')
      .select('analytics.featureName', 'featureName')
      .addSelect('COUNT(analytics.id)', 'count')
      .addSelect('COUNT(DISTINCT analytics.userIp)', 'uniqueUsers')
      .addSelect('MIN(analytics.timestamp)', 'firstUsed')
      .addSelect('MAX(analytics.timestamp)', 'lastUsed')
      .groupBy('analytics.featureName')
      .orderBy('count', 'DESC')
      .getRawMany();

    return result.map(item => ({
      featureName: item.featureName,
      count: parseInt(item.count, 10),
      uniqueUsers: parseInt(item.uniqueUsers, 10),
      firstUsed: new Date(item.firstUsed),
      lastUsed: new Date(item.lastUsed),
    }));
  }

  // Get feature usage over time (daily)
  async getFeatureUsageOverTime(featureName?: string, startDate?: Date, endDate?: Date): Promise<FeatureUsageOverTimeResponse[]> {
    this.logger.log('Fetching feature usage over time', { featureName, startDate, endDate });
    try {
      const query = this.analyticsRepository
        .createQueryBuilder('analytics')
        .select('DATE(analytics.timestamp)', 'date')
        .addSelect('COUNT(analytics.id)', 'count');

      this.logger.log('Feature usage over time query', query.getQuery());
      if (featureName) {
        query.where('analytics.featureName = :featureName', { featureName });
      }

      if (startDate) {
        query.andWhere('analytics.timestamp >= :startDate', { startDate });
      }

      if (endDate) {
        query.andWhere('analytics.timestamp <= :endDate', { endDate });
      }

      this.logger.log('Feature usage over time query with filters', query.getQueryAndParameters());
      const response = await query.groupBy('date').orderBy('date', 'ASC').getRawMany();
      this.logger.log('Feature usage over time response', response);
      return response.map(item => ({
        featureName: featureName || 'All Features',
        date: item.date,
        count: parseInt(item.count, 10),
      }));
    } catch (error) {
      this.logger.error('Error fetching feature usage over time', error);
      throw new Error('Error fetching feature usage over time');
    }
  }

  // Get geographical distribution of feature usage
  async getGeoDistribution(featureName?: string): Promise<GeoDistributionResponse[]> {
    this.logger.log('Fetching geographical distribution', { featureName });
    try {
      const query = this.analyticsRepository
        .createQueryBuilder('analytics')
        .select("JSON_UNQUOTE(JSON_EXTRACT(analytics.geoDetails, '$.country'))", 'country')
        .addSelect('COUNT(*)', 'count')
        .where("JSON_EXTRACT(analytics.geoDetails, '$.country') IS NOT NULL");

      if (featureName) {
        query.andWhere('analytics.featureName = :featureName', { featureName });
      }

      return await query.groupBy('country').orderBy('count', 'DESC').getRawMany();
    } catch (error) {
      this.logger.error('Error fetching geographical distribution', error);
      throw new Error('Error fetching geographical distribution');
    }
  }

  // Get usage by user agent (browser/device type)
  async getUserAgentDistribution(featureName?: string): Promise<Array<{ userAgent: string; count: number }>> {
    const query = this.analyticsRepository
      .createQueryBuilder('analytics')
      .select('analytics.userAgent', 'userAgent')
      .addSelect('COUNT(analytics.id)', 'count');

    if (featureName) {
      query.andWhere('analytics.featureName = :featureName', { featureName });
    }

    return await query
      .groupBy('userAgent')
      .orderBy('count', 'DESC')
      .limit(10) // Limit to top 10 user agents
      .getRawMany();
  }

  // Get feature usage by time of day
  async getUsageByTimeOfDay(featureName?: string): Promise<Array<{ hour: number; count: number }>> {
    const query = this.analyticsRepository
      .createQueryBuilder('analytics')
      .select('EXTRACT(HOUR FROM analytics.timestamp)', 'hour')
      .addSelect('COUNT(analytics.id)', 'count');

    if (featureName) {
      query.andWhere('analytics.featureName = :featureName', { featureName });
    }

    return await query.groupBy('hour').orderBy('hour', 'ASC').getRawMany();
  }

  // Get unique users count (based on IP)
  async getUniqueUsersCount(featureName?: string, startDate?: Date, endDate?: Date): Promise<{ totalUsers: number }> {
    try {
      const query = this.analyticsRepository.createQueryBuilder('analytics').select('COUNT(DISTINCT analytics.userIp)', 'uniqueUsers');

      if (featureName) {
        query.where('analytics.featureName = :featureName', { featureName });
      }

      if (startDate) {
        query.andWhere('analytics.timestamp >= :startDate', { startDate });
      }

      if (endDate) {
        query.andWhere('analytics.timestamp <= :endDate', { endDate });
      }

      const result = await query.getRawOne();
      return { totalUsers: parseInt(result.uniqueUsers, 10) };
    } catch (error) {
      this.logger.error('Error fetching unique users count', error);
      throw new Error('Error fetching unique users count');
    }
  }

  // Get feature usage metrics within a specific date range
  async getFeatureMetrics(startDate: Date, endDate: Date): Promise<FeatureMetricsResponse[]> {
    try {
      const results = await this.analyticsRepository
        .createQueryBuilder('fa')
        .select('fa.featureName', 'featureName')
        .addSelect('COUNT(*)', 'totalUsage')
        .addSelect('COUNT(DISTINCT fa.userIp)', 'uniqueUsers')
        .where('fa.timestamp BETWEEN :startDate AND :endDate', { startDate, endDate })
        .groupBy('fa.featureName')
        .orderBy('totalUsage', 'DESC')
        .getRawMany();

      return results.map(item => ({
        featureName: item.featureName,
        totalUsage: parseInt(item.totalUsage, 10),
        uniqueUsers: parseInt(item.uniqueUsers, 10),
      }));
    } catch (error) {
      this.logger.error('Error fetching feature metrics', error);
      throw new Error('Error fetching feature metrics');
    }
  }

  // Get metadata analysis (assumes metadata contains specific fields you want to analyze)
  async getMetadataAnalysis(featureName: string, metadataKey: string): Promise<Array<{ value: any; count: number }>> {
    return await this.analyticsRepository.query(
      `
      SELECT 
        JSON_UNQUOTE(JSON_EXTRACT(metadata, ?)) AS value,
        COUNT(*) AS count
      FROM 
        feature_analytics
      WHERE 
        feature_name = ?
        AND JSON_UNQUOTE(JSON_EXTRACT(metadata, ?)) IS NOT NULL
      GROUP BY 
        JSON_UNQUOTE(JSON_EXTRACT(metadata, ?))
      ORDER BY 
        count DESC
      LIMIT 20
    `,
      [`$.${metadataKey}`, featureName, `$.${metadataKey}`, `$.${metadataKey}`],
    );
  }

  // ENHANCED METHODS FOR ADMIN DASHBOARD
  async getHomeAnalytics(queryDto: AnalyticsQueryDto): Promise<HomeAnalyticsRespone> {
    try {
      // Extract filter parameters
      const { startDate, endDate } = queryDto;

      // Build base query conditions
      const queryConditions: any = {
        timestamp: {
          $gte: new Date(startDate),
          $lte: new Date(endDate),
        },
      };

      // Fetch analytics data based on query conditions
      const analyticsData = await this.analyticsRepository.find(queryConditions);

      if (!analyticsData.length) {
        throw new NotFoundException('No analytics data found for the given filters');
      }

      // Calculate basic metrics
      const uniqueUserIds = new Set(analyticsData.map(item => item.userIp));
      const uniqueUserCount = uniqueUserIds.size;
      const totalUsageCount = analyticsData.length;

      // Calculate daily usage
      const dateDiffInDays = Math.ceil((new Date(endDate).getTime() - new Date(startDate).getTime()) / (1000 * 3600 * 24));
      const averageDailyUsage = totalUsageCount / (dateDiffInDays || 1);

      // Group by date to get usage over time and find peak usage
      const usageByDate = analyticsData.reduce((acc, item) => {
        const date = item.timestamp.toISOString().split('T')[0];
        acc[date] = (acc[date] || 0) + 1;
        return acc;
      }, {});

      this.logger.log('Usage by date:', usageByDate);

      const usageOverTime: FeatureUsageOverTimeResponse[] = Object.entries(usageByDate).map(([date, count]) => ({
        featureName: 'All Features',
        date,
        count: count as number,
      }));

      // List unique features used
      const totalFeatures = new Set(analyticsData.map(item => item.featureName));

      const peakUsage = Math.max(...(Object.values(usageByDate) as number[]));

      // Calculate geo distribution
      const geoDistribution = this.calculateGeoDistribution(analyticsData);

      // Calculate user agent distribution
      const userAgentDistribution = this.calculateUserAgentDistribution(analyticsData);

      // Extract geo metrics
      const countries = new Set(analyticsData.map(item => item.geoDetails?.country).filter(Boolean));
      const cities = new Set(analyticsData.map(item => item.geoDetails?.city).filter(Boolean));
      const regions = new Set(analyticsData.map(item => item.geoDetails?.region).filter(Boolean));
      const timezones = new Set(analyticsData.map(item => item.geoDetails?.timezone).filter(Boolean));
      const userAgents = new Set(analyticsData.map(item => item.userAgent).filter(Boolean));

      // Find most active locations
      const countryUsage = this.getUsageCountByProperty(analyticsData, 'geoDetails.country');
      const cityUsage = this.getUsageCountByProperty(analyticsData, 'geoDetails.city');
      const timezoneUsage = this.getUsageCountByProperty(analyticsData, 'geoDetails.timezone');

      const mostActiveCountry = this.getHighestUsageItem(countryUsage);
      const mostActiveCity = this.getHighestUsageItem(cityUsage);
      const topTimezones = this.getTopItems(timezoneUsage, 5);

      // Compile full response
      const response: HomeAnalyticsRespone = {
        totalFeatures: Array.from(totalFeatures),
        geoDetails: analyticsData.map(item => item.geoDetails),
        metadata: analyticsData.map(item => item.metadata),
        uniqueUserCount,
        totalUsageCount,
        averageDailyUsage,
        peakUsage,
        usageOverTime,
        geoDistribution,
        userAgentDistribution,
        totalUniqueCountry: countries.size,
        totalUniqueCity: cities.size,
        totalUniqueRegion: regions.size,
        totalUniqueTimezone: timezones.size,
        totalUniqueUserAgent: userAgents.size,
        topTimezones,
        mostActiveCity,
        mostActiveCountry,
      };

      return response;
    } catch (error) {
      this.logger.error(`Error fetching analytics data: ${error.message}`, error.stack);
      throw error instanceof HttpException ? error : new InternalServerErrorException('Failed to retrieve analytics data');
    }
  }

  // Helper methods
  private calculateGeoDistribution(analyticsData: any[]): GeoDistributionResponse[] {
    this.logger.log('Calculating geographical distribution');
    const geoMap = new Map<string, number>();
    const featureName = analyticsData[0]?.featureName || 'All Features';

    analyticsData.forEach(item => {
      if (item.geoDetails?.country) {
        const key = item.geoDetails.country;
        geoMap.set(key, (geoMap.get(key) || 0) + 1);
      }
    });

    return Array.from(geoMap.entries())
      .map(([country, count]) => ({ featureName, country, count }))
      .sort((a, b) => b.count - a.count);
  }

  private calculateUserAgentDistribution(analyticsData: any[]): UserAgentDistributionResponse[] {
    const userAgentMap = new Map<string, number>();

    analyticsData.forEach(item => {
      if (item.userAgent) {
        // Extract browser/device info from user agent string
        const parsedAgent = this.parseUserAgent(item.userAgent);
        const key = parsedAgent;
        userAgentMap.set(key, (userAgentMap.get(key) || 0) + 1);
      }
    });

    return Array.from(userAgentMap.entries())
      .map(([userAgent, count]) => ({ userAgent, count }))
      .sort((a, b) => b.count - a.count);
  }

  private parseUserAgent(userAgentString: string): any {
    const parser = UAParser(userAgentString);
    const browser = parser.browser.name || 'Unknown Browser';
    const os = parser.os.name || 'Unknown OS';
    const device = parser.device.vendor || 'Unknown Device';
    const engine = parser.engine.name || 'Unknown Engine';
    const version = parser.browser.version || 'Unknown Version';
    const engineVersion = parser.engine.version || 'Unknown Engine Version';
    const deviceType = parser.device.type || 'Unknown Device Type';
    const deviceModel = parser.device.model || 'Unknown Device Model';
    const deviceVendor = parser.device.vendor || 'Unknown Device Vendor';
    const deviceVendorType = parser.device.vendor || 'Unknown Device Vendor Type';
    const deviceVendorModel = parser.device.vendor || 'Unknown Device Vendor Model';

    return {
      browser,
      os,
      device,
      engine,
      version,
      engineVersion,
      deviceType,
      deviceModel,
      deviceVendor,
      deviceVendorType,
      deviceVendorModel,
    };
  }

  private getUsageCountByProperty(data: any[], propertyPath: string): Map<string, number> {
    const countMap = new Map<string, number>();

    data.forEach(item => {
      const value = this.getNestedProperty(item, propertyPath);
      if (value) {
        countMap.set(value, (countMap.get(value) || 0) + 1);
      }
    });

    return countMap;
  }

  private getNestedProperty(obj: any, path: string): any {
    return path.split('.').reduce((o, key) => (o && o[key] !== undefined ? o[key] : null), obj);
  }

  private getHighestUsageItem(usageMap: Map<string, number>): string {
    let maxItem = '';
    let maxCount = 0;

    usageMap.forEach((count, item) => {
      if (count > maxCount) {
        maxCount = count;
        maxItem = item;
      }
    });

    return maxItem;
  }

  private getTopItems(usageMap: Map<string, number>, limit: number): string[] {
    return Array.from(usageMap.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, limit)
      .map(entry => entry[0]);
  }
}
