import { DatePipe, NgFor, NgIf } from '@angular/common'
import { HttpClient } from '@angular/common/http'
import {
  AfterViewInit,
  Component,
  ElementRef,
  OnDestroy,
  OnInit,
  ViewChild,
  inject
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import type Hls from 'hls.js'
import { Subscription, firstValueFrom } from 'rxjs'
import { LocalStorageService } from '../core/wrappers/storage.service'
import { LiveChatMessage, LiveChatService } from './live-chat.service'
import { StreamCardsComponent } from './stream-cards.component'

@Component({
  selector: 'my-live',
  standalone: true,
  templateUrl: './live-stream.component.html',
  styleUrls: [ './live-stream.component.scss' ],
  imports: [ NgIf, NgFor, FormsModule, DatePipe, StreamCardsComponent ]
})
export class LiveStreamComponent implements OnInit, AfterViewInit, OnDestroy {
  private static readonly CHAT_USERNAME_LS_KEY = 'live-chat-username'

  private route = inject(ActivatedRoute)
  private router = inject(Router)
  private http = inject(HttpClient)
  private liveChat = inject(LiveChatService)
  private localStorage = inject(LocalStorageService)

  @ViewChild('nativeVideo')
  set nativeVideoRef (value: ElementRef<HTMLVideoElement>) {
    this.nativeVideo = value

    this.nativeVideo.nativeElement.addEventListener('pause', () => {
      this.nativeVideo.nativeElement.play()
    })

    if (value && this.playerUrl && !this.error) {
      setTimeout(() => this.attachPlayer(), 0)
    }
  }

  @ViewChild('chatMessageInput') chatMessageInput?: ElementRef<HTMLInputElement>
  @ViewChild('chatMessagesContainer') chatMessagesContainer?: ElementRef<HTMLDivElement>

  private nativeVideo?: ElementRef<HTMLVideoElement>
  private hls?: Hls
  private viewReady = false
  private refreshTimer: any
  private readonly chatSubscriptions: Subscription[] = []
  private shouldStickChatToBottom = true

  streamTitles: Record<string, string> = {
    camera001: 'Kafka Mesh',
    camera002: 'Solace Partition Queue',
    camera003: 'Integration Reimagined'
  }

  streamThumbnails: Record<string, string> = {
    camera001: 'assets/images/sumeet-puri.png',
    camera002: 'assets/images/AaronLee.png',
    camera003: 'assets/images/denis-king.jpg'
  }

  streamConfig: Record<string, { baseHour: number }> = {
    camera001: { baseHour: 2 },
    camera002: { baseHour: 3 },
    camera003: { baseHour: 4 }
  }

  private durationMinutes = 15
  private rotationHours = 3

  streams: string[] = []
  streamKey = ''
  playerUrl = ''
  error = ''
  loadingStreams = true
  showPlayer = false

  showUsernamePrompt = false
  usernameDraft = ''
  chatUsername = ''
  chatMessages: LiveChatMessage[] = []
  chatMessage = ''
  chatPresenceCount = 0
  chatError = ''
  showEmojiPicker = false
  readonly chatEmojiOptions = [
    '😀', '😁', '😂', '😊', '😍', '😘',
    '😎', '🤩', '🥳', '😇', '🤔', '😴',
    '👍', '👏', '🙌', '🙏', '💯', '🔥',
    '🎉', '❤️', '💙', '🚀', '👀', '✅'
  ]

  ngOnInit () {
    this.loadStreams()
    this.startUtcQuarterRefreshWatcher()
    this.initChatSubscriptions()

    this.route.paramMap.subscribe(params => {
      const stream = params.get('streamKey')

      if (stream) {
        this.streamKey = stream
        this.showPlayer = true
        this.joinChatAsViewer()
        this.loadStream()
      } else {
        this.showPlayer = false
        this.showUsernamePrompt = false
        this.liveChat.leaveRoom()
        this.destroyHlsPlayer()
      }
    })
  }

  ngAfterViewInit () {
    this.viewReady = true
  }

  ngOnDestroy () {
    this.destroyHlsPlayer()
    this.liveChat.disconnect()
    this.chatSubscriptions.forEach(subscription => subscription.unsubscribe())
    clearTimeout(this.refreshTimer)
  }

  async loadStream () {
    this.updatePlayerUrl()

    if (this.error) return

    await this.attachPlayer()
  }

  selectStream (stream: string) {
    this.router.navigate([ '/live', stream ])
  }

  joinChat () {
    const username = this.usernameDraft.trim().replace(/\s+/g, ' ')

    if (username.length < 2 || username.length > 40) {
      this.chatError = 'Username must be between 2 and 40 characters.'
      return
    }

    this.chatUsername = username
    this.chatError = ''
    this.showUsernamePrompt = false
    this.localStorage.setItem(LiveStreamComponent.CHAT_USERNAME_LS_KEY, username)

    void this.liveChat.joinRoom(this.streamKey, username)
  }

  sendChatMessage () {
    if (!this.chatUsername) {
      this.chatError = 'Add your name to join chat and send messages.'
      return
    }

    const text = this.chatMessage.trim()
    if (!text) return

    this.chatError = ''
    this.liveChat.sendMessage(text)
    this.chatMessage = ''
    this.showEmojiPicker = false
  }

  reopenUsernamePrompt () {
    this.usernameDraft = this.chatUsername || this.usernameDraft
    this.chatError = ''
    this.showUsernamePrompt = true
  }

  get canSendChat () {
    return !!this.chatUsername
  }

  closeUsernamePrompt () {
    this.showUsernamePrompt = false
  }

  onChatScroll () {
    const container = this.chatMessagesContainer?.nativeElement
    if (!container) return

    const threshold = 24
    const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight
    this.shouldStickChatToBottom = distanceFromBottom <= threshold
  }

  toggleEmojiPicker () {
    if (!this.canSendChat || this.showUsernamePrompt) return

    this.showEmojiPicker = !this.showEmojiPicker
  }

  addEmoji (emoji: string) {
    const input = this.chatMessageInput?.nativeElement

    if (!input) {
      this.chatMessage += emoji
      this.showEmojiPicker = false
      return
    }

    const start = input.selectionStart ?? this.chatMessage.length
    const end = input.selectionEnd ?? this.chatMessage.length

    this.chatMessage = this.chatMessage.slice(0, start) + emoji + this.chatMessage.slice(end)
    this.showEmojiPicker = false

    queueMicrotask(() => {
      input.focus()
      const cursor = start + emoji.length
      input.setSelectionRange(cursor, cursor)
    })
  }

  get streamTitle () {
    return this.streamTitles[this.streamKey] || this.streamKey
  }

  isStreamLive = (stream: string): boolean => {
    const config = this.streamConfig[stream]
    if (!config) return false

    const now = new Date()
    const minutesNow = now.getUTCHours() * 60 + now.getUTCMinutes()

    const baseMinutes = config.baseHour * 60
    const rotationMinutes = this.rotationHours * 60

    if (minutesNow < baseMinutes) return false

    const elapsed = minutesNow - baseMinutes
    const positionInCycle = elapsed % rotationMinutes

    return positionInCycle < this.durationMinutes
  }

  getStreamStartTime = (stream: string): string => {
    const config = this.streamConfig[stream]
    if (!config) return ''

    const now = new Date()
    const minutesNow = now.getUTCHours() * 60 + now.getUTCMinutes()

    const baseMinutes = config.baseHour * 60
    const rotationMinutes = this.rotationHours * 60

    let nextStart: number

    if (minutesNow < baseMinutes) {
      nextStart = baseMinutes
    } else {
      const elapsed = minutesNow - baseMinutes
      const cyclesPassed = Math.floor(elapsed / rotationMinutes)
      nextStart = baseMinutes + cyclesPassed * rotationMinutes

      if (minutesNow >= nextStart + this.durationMinutes) {
        nextStart += rotationMinutes
      }
    }

    const hours = Math.floor(nextStart / 60) % 24
    const minutes = nextStart % 60

    const date = new Date()
    date.setUTCHours(hours, minutes, 0, 0)

    return date.toLocaleTimeString([], {
      hour: '2-digit',
      minute: '2-digit'
    })
  }

  private initChatSubscriptions () {
    this.chatSubscriptions.push(
      this.liveChat.messages$.subscribe(messages => {
        this.chatMessages = messages
        if (this.shouldStickChatToBottom) {
          this.scrollChatToBottomAfterRender()
        }
      })
    )

    this.chatSubscriptions.push(
      this.liveChat.presence$.subscribe(count => {
        this.chatPresenceCount = count
      })
    )

    this.chatSubscriptions.push(
      this.liveChat.errors$.subscribe(message => {
        this.chatError = message
      })
    )
  }

  private joinChatAsViewer () {
    this.liveChat.leaveRoom()
    this.chatMessages = []
    this.chatMessage = ''
    this.chatPresenceCount = 0
    this.chatError = ''
    this.showEmojiPicker = false
    this.shouldStickChatToBottom = true
    this.chatUsername = this.localStorage.getItem<string>(LiveStreamComponent.CHAT_USERNAME_LS_KEY) || ''
    this.usernameDraft = this.chatUsername
    this.showUsernamePrompt = false

    void this.liveChat.joinRoom(this.streamKey, this.chatUsername || undefined)
  }

  private scrollChatToBottom () {
    const container = this.chatMessagesContainer?.nativeElement
    if (!container) return

    container.scrollTop = container.scrollHeight
  }

  private scrollChatToBottomAfterRender () {
    requestAnimationFrame(() => {
      requestAnimationFrame(() => this.scrollChatToBottom())
    })
  }

  private startUtcQuarterRefreshWatcher () {
    const now = new Date()
    const nextQuarter = new Date(now)
    const minutes = now.getUTCMinutes()
    const nextQuarterMinute = Math.floor(minutes / 15) * 15 + 15

    nextQuarter.setUTCMinutes(nextQuarterMinute, 0, 0)

    if (nextQuarterMinute === 60) {
      nextQuarter.setUTCHours(now.getUTCHours() + 1, 0, 0, 0)
    }

    const msUntilNextQuarter = nextQuarter.getTime() - now.getTime()

    this.refreshTimer = setTimeout(() => {
      if (!this.showPlayer) {
        this.streams = [ ...this.streams ]
      }

      this.startUtcQuarterRefreshWatcher()
    }, msUntilNextQuarter)
  }

  private async loadStreams () {
    this.loadingStreams = true
    this.error = ''

    try {
      const result = await firstValueFrom(
        this.http.get<{ data: string[] }>('/api/v1/live/streams')
      )

      this.streams = result.data || []
    } catch (err) {
      this.error = 'Could not load streams from livestream-sources.yaml.'
      this.loadingStreams = false
      return
    }

    this.loadingStreams = false
  }

  private updatePlayerUrl () {
    const trimmedStreamKey = this.streamKey.trim()

    if (!trimmedStreamKey) {
      this.error = 'Please provide a stream key (for example: camera001).'
      return
    }

    if (this.streams.length !== 0 && !this.streams.includes(trimmedStreamKey)) {
      this.error = `Stream "${trimmedStreamKey}" not found in go2rtc.yaml.`
      return
    }

    this.error = ''
    this.streamKey = trimmedStreamKey
    this.playerUrl = `/api/v1/live/streams/${encodeURIComponent(trimmedStreamKey)}/index.m3u8`
  }

  private async attachPlayer () {
    if (!this.viewReady || !this.nativeVideo || this.error) return

    this.destroyHlsPlayer()

    const video = this.nativeVideo.nativeElement
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)

    if (isSafari) {
      video.src = this.playerUrl
      video.muted = true
      video.play().catch(() => {})
      return
    }

    const { default: HlsJs } = await import('hls.js')
    if (!HlsJs.isSupported()) {
      this.error = 'HLS is not supported by this browser.'
      return
    }

    this.hls = new HlsJs({
      lowLatencyMode: false,
      liveSyncDurationCount: 4,
      liveMaxLatencyDurationCount: 12,
      maxLiveSyncPlaybackRate: 1,
      backBufferLength: 90
    })

    this.hls.on(HlsJs.Events.ERROR, (_event, data) => {
      if (!data?.fatal) return

      this.error = `HLS error: ${data.type} / ${data.details}`
    })

    this.hls.loadSource(this.playerUrl)
    this.hls.attachMedia(video)

    void video.play().catch(() => {})
  }

  private destroyHlsPlayer () {
    if (this.hls) {
      this.hls.destroy()
      this.hls = undefined
    }

    if (!this.nativeVideo) return

    const video = this.nativeVideo.nativeElement
    video.pause()
    video.removeAttribute('src')
    video.load()
  }
}
