import { createClient } from 'redis'
import { DataRelationManager } from '../../core/utils/data-relations'
import { globalConfig } from '../../core/config'
import { vezaFaker } from '../../core/utils/faker-config'
import type { Audio, User } from '../../core/schemas/database'

/**
 * Stream server fixtures - Audio streaming data and cache
 */
export class StreamServerFixtures {
  private static redisClient: any = null
  private static streamingSessions: Map<string, any> = new Map()
  private static audioQueue: Map<string, string[]> = new Map()

  /**
   * Initialize Redis connection for streaming data
   */
  static async initialize(): Promise<void> {
    console.log('🎵 Initializing stream server fixtures...')
    
    // Initialize Redis connection
    this.redisClient = createClient({
      socket: {
        host: globalConfig.redis.host,
        port: globalConfig.redis.port,
      },
      password: globalConfig.redis.password || undefined,
    })
    
    try {
      await this.redisClient.connect()
      console.log('✅ Connected to Redis (stream)')
    } catch (error) {
      console.warn('⚠️ Could not connect to Redis (stream):', error)
      this.redisClient = null
    }
  }

  /**
   * Seed Redis with streaming data
   */
  static async seedStreamingData(): Promise<void> {
    if (!this.redisClient) {
      console.warn('⚠️ No Redis connection available')
      return
    }

    console.log('🎶 Seeding streaming data...')
    
    try {
      const allData = DataRelationManager.getAll()
      const users = Array.from(allData.users.values())
      const tracks = Array.from(allData.tracks.values())
      
      // Cache audio metadata for fast access
      console.log(`📝 Caching ${tracks.length} track metadata...`)
      await this.cacheAudioMetadata(tracks)
      
      // Create streaming sessions for active users
      const activeUsers = users.filter(u => u.status === 'active')
      console.log(`📝 Creating streaming sessions for ${activeUsers.length} users...`)
      await this.createStreamingSessions(activeUsers, tracks)
      
      // Cache popular tracks and playlists
      await this.cachePopularContent(tracks)
      
      // Cache audio processing queue
      await this.cacheAudioQueue(tracks)
      
      // Cache streaming statistics
      await this.cacheStreamingStats(tracks)
      
      console.log('✅ Streaming data seeded successfully')
      
    } catch (error) {
      console.error('❌ Error seeding streaming data:', error)
      throw error
    }
  }

  /**
   * Generate streaming session data
   */
  static generateStreamingSession(userId: string, trackId: string): any {
    const sessionId = `stream_${userId}_${Date.now()}`
    const startTime = new Date()
    
    const session = {
      id: sessionId,
      userId,
      trackId,
      startTime: startTime.toISOString(),
      duration: 0, // Will be updated as stream progresses
      quality: vezaFaker.helpers.arrayElement(['low', 'medium', 'high', 'lossless']),
      bitrate: vezaFaker.helpers.arrayElement([128, 192, 256, 320]),
      format: vezaFaker.helpers.arrayElement(['mp3', 'flac', 'wav']),
      platform: vezaFaker.helpers.arrayElement(['web', 'mobile', 'desktop']),
      location: {
        country: 'FR',
        region: vezaFaker.user.location(),
        coordinates: {
          lat: vezaFaker.location.latitude({ min: 41, max: 51 }),
          lng: vezaFaker.location.longitude({ min: -5, max: 9 })
        }
      },
      device: {
        type: vezaFaker.helpers.arrayElement(['desktop', 'mobile', 'tablet']),
        os: vezaFaker.helpers.arrayElement(['Windows', 'macOS', 'Linux', 'iOS', 'Android']),
        browser: vezaFaker.helpers.arrayElement(['Chrome', 'Firefox', 'Safari', 'Edge'])
      },
      network: {
        type: vezaFaker.helpers.arrayElement(['wifi', '4g', '5g', 'ethernet']),
        speed: vezaFaker.number.int({ min: 1, max: 100 }) // Mbps
      },
      status: 'active',
      progress: 0, // Percentage of track completed
      volume: vezaFaker.number.float({ min: 0.1, max: 1.0, precision: 0.1 }),
      repeat: vezaFaker.helpers.arrayElement(['none', 'one', 'all']),
      shuffle: vezaFaker.datatype.boolean({ probability: 0.3 }),
      createdAt: startTime.toISOString(),
      updatedAt: startTime.toISOString()
    }
    
    this.streamingSessions.set(sessionId, session)
    return session
  }

  /**
   * Generate audio processing queue
   */
  static generateAudioProcessingJobs(tracks: Audio[]): any[] {
    const jobs: any[] = []
    
    // Select some tracks for processing (transcoding, analysis, etc.)
    const tracksToProcess = vezaFaker.helpers.arrayElements(tracks, Math.floor(tracks.length * 0.1))
    
    tracksToProcess.forEach(track => {
      const jobTypes = ['transcode', 'analyze', 'thumbnail', 'waveform', 'metadata']
      const jobType = vezaFaker.helpers.arrayElement(jobTypes)
      
      jobs.push({
        id: `job_${track.id}_${jobType}_${Date.now()}`,
        type: jobType,
        trackId: track.id,
        status: vezaFaker.helpers.arrayElement(['pending', 'processing', 'completed', 'failed']),
        priority: vezaFaker.helpers.arrayElement(['low', 'medium', 'high']),
        progress: jobType === 'processing' ? vezaFaker.number.int({ min: 10, max: 90 }) : 
                 jobType === 'completed' ? 100 : 0,
        startedAt: vezaFaker.date.recent({ days: 1 }),
        estimatedCompletion: vezaFaker.date.soon({ days: 1 }),
        metadata: {
          inputFormat: track.metadata.format,
          outputFormat: jobType === 'transcode' ? 
            vezaFaker.helpers.arrayElement(['mp3', 'flac', 'wav']) : undefined,
          quality: jobType === 'transcode' ? 
            vezaFaker.helpers.arrayElement(['low', 'medium', 'high']) : undefined,
          fileSize: track.metadata.fileSize,
          duration: track.metadata.duration
        },
        createdAt: vezaFaker.date.recent({ days: 2 }),
        updatedAt: vezaFaker.date.recent({ days: 1 })
      })
    })
    
    return jobs.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
  }

  /**
   * Generate streaming analytics
   */
  static generateStreamingAnalytics(): any {
    const sessions = Array.from(this.streamingSessions.values())
    
    return {
      overview: {
        totalSessions: sessions.length,
        activeSessions: sessions.filter(s => s.status === 'active').length,
        totalDuration: sessions.reduce((sum, s) => sum + s.duration, 0),
        averageSessionDuration: sessions.length > 0 ? 
          sessions.reduce((sum, s) => sum + s.duration, 0) / sessions.length : 0,
        uniqueUsers: new Set(sessions.map(s => s.userId)).size,
        totalBandwidth: sessions.reduce((sum, s) => sum + (s.bitrate * s.duration / 8), 0) // MB
      },
      byQuality: this.groupSessionsByField(sessions, 'quality'),
      byPlatform: this.groupSessionsByField(sessions, 'platform'),
      byLocation: this.groupSessionsByLocation(sessions),
      byDevice: this.groupSessionsByDevice(sessions),
      byNetwork: this.groupSessionsByField(sessions, 'network.type'),
      realtime: {
        currentListeners: sessions.filter(s => s.status === 'active').length,
        peakListeners: vezaFaker.number.int({ min: 100, max: 1000 }),
        averageBitrate: sessions.length > 0 ? 
          sessions.reduce((sum, s) => sum + s.bitrate, 0) / sessions.length : 0,
        totalBandwidthUsage: vezaFaker.number.float({ min: 10, max: 100 }), // Mbps
        serverLoad: vezaFaker.number.float({ min: 0.1, max: 0.9 }),
        errorRate: vezaFaker.number.float({ min: 0, max: 0.05 })
      },
      trends: {
        hourly: this.generateHourlyTrends(),
        daily: this.generateDailyTrends(),
        weekly: this.generateWeeklyTrends()
      }
    }
  }

  /**
   * Private helper methods
   */
  private static async cacheAudioMetadata(tracks: Audio[]): Promise<void> {
    if (!this.redisClient) return
    
    for (const track of tracks) {
      const metadata = {
        id: track.id,
        title: track.title,
        artist: track.artist,
        duration: track.metadata.duration,
        bitrate: track.metadata.bitrate,
        format: track.metadata.format,
        fileSize: track.metadata.fileSize,
        fileUrl: track.fileUrl,
        waveformData: track.waveformData,
        genre: track.genre,
        isPublic: track.isPublic,
        stats: track.stats
      }
      
      await this.redisClient.setEx(
        `track:${track.id}:metadata`,
        3600, // 1 hour TTL
        JSON.stringify(metadata)
      )
    }
  }

  private static async createStreamingSessions(users: User[], tracks: Audio[]): Promise<void> {
    if (!this.redisClient) return
    
    // Create active streaming sessions for some users
    const activeListeners = vezaFaker.helpers.arrayElements(users, Math.floor(users.length * 0.1))
    
    for (const user of activeListeners) {
      const track = vezaFaker.helpers.arrayElement(tracks)
      const session = this.generateStreamingSession(user.id, track.id)
      
      // Cache session in Redis
      await this.redisClient.setEx(
        `session:${session.id}`,
        1800, // 30 minutes TTL
        JSON.stringify(session)
      )
      
      // Add to active sessions set
      await this.redisClient.sAdd('sessions:active', session.id)
      
      // Add to user's active sessions
      await this.redisClient.sAdd(`user:${user.id}:sessions`, session.id)
      
      // Track current playing
      await this.redisClient.setEx(
        `user:${user.id}:current_track`,
        1800,
        track.id
      )
    }
  }

  private static async cachePopularContent(tracks: Audio[]): Promise<void> {
    if (!this.redisClient) return
    
    // Sort tracks by popularity (plays)
    const popularTracks = tracks
      .sort((a, b) => b.stats.plays - a.stats.plays)
      .slice(0, 50)
    
    // Cache popular tracks list
    await this.redisClient.setEx(
      'tracks:popular',
      3600, // 1 hour TTL
      JSON.stringify(popularTracks.map(t => t.id))
    )
    
    // Cache trending tracks (recent + popular)
    const trendingTracks = tracks
      .filter(track => {
        const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
        return track.createdAt > weekAgo
      })
      .sort((a, b) => b.stats.plays - a.stats.plays)
      .slice(0, 20)
    
    await this.redisClient.setEx(
      'tracks:trending',
      1800, // 30 minutes TTL
      JSON.stringify(trendingTracks.map(t => t.id))
    )
    
    // Cache by genre
    const genres = [...new Set(tracks.map(t => t.genre))]
    for (const genre of genres) {
      const genreTracks = tracks
        .filter(t => t.genre === genre)
        .sort((a, b) => b.stats.plays - a.stats.plays)
        .slice(0, 20)
      
      await this.redisClient.setEx(
        `tracks:genre:${genre}`,
        3600,
        JSON.stringify(genreTracks.map(t => t.id))
      )
    }
  }

  private static async cacheAudioQueue(tracks: Audio[]): Promise<void> {
    if (!this.redisClient) return
    
    const processingJobs = this.generateAudioProcessingJobs(tracks)
    
    // Group jobs by status
    const jobsByStatus = {
      pending: processingJobs.filter(j => j.status === 'pending'),
      processing: processingJobs.filter(j => j.status === 'processing'),
      completed: processingJobs.filter(j => j.status === 'completed'),
      failed: processingJobs.filter(j => j.status === 'failed')
    }
    
    // Cache job queues
    for (const [status, jobs] of Object.entries(jobsByStatus)) {
      await this.redisClient.setEx(
        `queue:${status}`,
        300, // 5 minutes TTL
        JSON.stringify(jobs.map(j => j.id))
      )
      
      // Cache individual job data
      for (const job of jobs) {
        await this.redisClient.setEx(
          `job:${job.id}`,
          3600,
          JSON.stringify(job)
        )
      }
    }
    
    // Cache queue statistics
    const queueStats = {
      total: processingJobs.length,
      pending: jobsByStatus.pending.length,
      processing: jobsByStatus.processing.length,
      completed: jobsByStatus.completed.length,
      failed: jobsByStatus.failed.length,
      averageProcessingTime: vezaFaker.number.int({ min: 30, max: 300 }), // seconds
      throughput: vezaFaker.number.int({ min: 5, max: 50 }), // jobs per hour
      lastUpdated: new Date().toISOString()
    }
    
    await this.redisClient.setEx(
      'queue:stats',
      60, // 1 minute TTL
      JSON.stringify(queueStats)
    )
  }

  private static async cacheStreamingStats(tracks: Audio[]): Promise<void> {
    if (!this.redisClient) return
    
    const analytics = this.generateStreamingAnalytics()
    
    // Cache overall streaming statistics
    await this.redisClient.setEx(
      'streaming:stats',
      300, // 5 minutes TTL
      JSON.stringify(analytics)
    )
    
    // Cache real-time metrics
    await this.redisClient.setEx(
      'streaming:realtime',
      10, // 10 seconds TTL
      JSON.stringify(analytics.realtime)
    )
    
    // Cache individual track streaming stats
    for (const track of tracks) {
      const trackStats = {
        plays: track.stats.plays,
        currentListeners: vezaFaker.number.int({ min: 0, max: 50 }),
        peakListeners: vezaFaker.number.int({ min: 0, max: 200 }),
        totalDuration: track.stats.plays * track.metadata.duration,
        averageCompletion: vezaFaker.number.float({ min: 0.3, max: 0.95 }),
        skipRate: vezaFaker.number.float({ min: 0.05, max: 0.4 }),
        lastPlayed: vezaFaker.date.recent({ days: 1 })
      }
      
      await this.redisClient.setEx(
        `track:${track.id}:streaming_stats`,
        600, // 10 minutes TTL
        JSON.stringify(trackStats)
      )
    }
  }

  private static groupSessionsByField(sessions: any[], field: string): any {
    const groups: Record<string, number> = {}
    
    sessions.forEach(session => {
      const value = field.includes('.') ? 
        field.split('.').reduce((obj, key) => obj?.[key], session) :
        session[field]
      
      if (value) {
        groups[value] = (groups[value] || 0) + 1
      }
    })
    
    return Object.entries(groups).map(([key, count]) => ({
      label: key,
      count,
      percentage: Math.round((count / sessions.length) * 100)
    }))
  }

  private static groupSessionsByLocation(sessions: any[]): any {
    const locations: Record<string, number> = {}
    
    sessions.forEach(session => {
      const region = session.location?.region
      if (region) {
        locations[region] = (locations[region] || 0) + 1
      }
    })
    
    return Object.entries(locations).map(([region, count]) => ({
      region,
      count,
      percentage: Math.round((count / sessions.length) * 100)
    }))
  }

  private static groupSessionsByDevice(sessions: any[]): any {
    const devices: Record<string, any> = {}
    
    sessions.forEach(session => {
      const deviceType = session.device?.type
      const os = session.device?.os
      
      if (deviceType) {
        if (!devices[deviceType]) {
          devices[deviceType] = { count: 0, os: {} }
        }
        devices[deviceType].count++
        
        if (os) {
          devices[deviceType].os[os] = (devices[deviceType].os[os] || 0) + 1
        }
      }
    })
    
    return Object.entries(devices).map(([type, data]: [string, any]) => ({
      type,
      count: data.count,
      percentage: Math.round((data.count / sessions.length) * 100),
      topOS: Object.entries(data.os)
        .sort(([,a]: any, [,b]: any) => b - a)
        .slice(0, 3)
        .map(([os, count]) => ({ os, count }))
    }))
  }

  private static generateHourlyTrends(): any[] {
    return Array.from({ length: 24 }, (_, hour) => ({
      hour,
      listeners: vezaFaker.number.int({ min: 50, max: 500 }),
      streams: vezaFaker.number.int({ min: 100, max: 1000 }),
      bandwidth: vezaFaker.number.float({ min: 10, max: 100 })
    }))
  }

  private static generateDailyTrends(): any[] {
    return Array.from({ length: 7 }, (_, day) => ({
      day: day + 1,
      listeners: vezaFaker.number.int({ min: 500, max: 2000 }),
      streams: vezaFaker.number.int({ min: 1000, max: 5000 }),
      bandwidth: vezaFaker.number.float({ min: 100, max: 500 })
    }))
  }

  private static generateWeeklyTrends(): any[] {
    return Array.from({ length: 12 }, (_, week) => ({
      week: week + 1,
      listeners: vezaFaker.number.int({ min: 2000, max: 10000 }),
      streams: vezaFaker.number.int({ min: 5000, max: 25000 }),
      bandwidth: vezaFaker.number.float({ min: 500, max: 2000 })
    }))
  }

  /**
   * Utility methods
   */
  static getStreamingSession(sessionId: string): any {
    return this.streamingSessions.get(sessionId)
  }

  static getAllStreamingSessions(): any[] {
    return Array.from(this.streamingSessions.values())
  }

  static getActiveSessionsCount(): number {
    return Array.from(this.streamingSessions.values())
      .filter(session => session.status === 'active').length
  }

  /**
   * Cleanup methods
   */
  static async cleanup(): Promise<void> {
    if (this.redisClient) {
      await this.redisClient.quit()
      this.redisClient = null
    }
    
    this.streamingSessions.clear()
    this.audioQueue.clear()
    
    console.log('🧹 Stream server fixtures cleanup completed')
  }

  /**
   * Health check
   */
  static async healthCheck(): Promise<{
    redis: boolean
    sessionsActive: number
    queueSize: number
  }> {
    let redis = false
    let sessionsActive = 0
    let queueSize = 0
    
    // Check Redis connection
    if (this.redisClient) {
      try {
        await this.redisClient.ping()
        redis = true
        
        // Get active sessions count
        sessionsActive = await this.redisClient.sCard('sessions:active') || 0
        
        // Get queue size
        queueSize = await this.redisClient.lLen('queue:pending') || 0
        
      } catch (error) {
        console.error('Stream server health check failed:', error)
      }
    }
    
    return { redis, sessionsActive, queueSize }
  }
}