require 'smoca_common/base/page_helper.rb'
require './core/api/video_player/stream_metadata_api'
require './core/api/video_player/vod_metadata_api'
require './core/api/video_player/channel_metadata_api'
require 'smoca_common/utils/twitch_utils'
require 'smoca_common/utils/console_print_utils'

include ConsolePrintsUtils

# PlayerPage contains methods related to elements
# on player.twitch environments.
class PlayerPage < PageHelper

  @live_status_ui             = 'LIVE'
  @live_status_data_attr      = 'live'
  @data_content_stream_attr   = 'data-content-stream'
  @src_attr                   = 'src'
  @style_attr                 = 'style'

  @player                     = '.player'
  @video                      = 'video'
  @player_div                 = '#player'
  @video_playback             = '#video-playback'
  @play_btn_overlay_css       = '.player-overlay>.js-control-play-button'
  @channel_preview_url        = '.js-video-background-banner'

  @player_controls_top_css    = '.js-controls-top'
  @logo_css                   = '.qa-broadcaster-logo'
  @display_name_css           = '.qa-display-name'
  @title_css                  = '.qa-stream-title'
  @game_css                   = '.qa-game-name'
  @live_status_css            = '.qa-live-label'

  @mature_channel_css         = '.js-player-mature-accept'
  @player_controls_bottom_css = '.qa-controls-bottom'
  @settings_btn_css           = '.qa-settings-button'
  @advanced_settings_css      = '.qa-advanced-button'
  @play_pause_btn_css         = '.qa-pause-play-button'
  @unmute_btn                 = '.unmute-button'
  @volume_btn_css             = '.qa-control-volume'
  @settings_toggle            = '.pl-toggle__button'
  @options_menu_css           = '.player-menu-options'
  @options_btn_css            = '.qa-settings-button'
  @fullscreen_btn_css         = '.qa-fullscreen-button'
  @theatremode_btn_css        = '.qa-theatre-mode-button'
  @theatremode                = '.theatre'
  @twitch_logo_btn_css        = '.qa-watch-twitch-button'

  # metadata must be either 'vod' or 'stream' this will determine which apis to initialize.
  def initialize(metadata)
    if metadata == 'vod'
      VodMetadataApi.get_vod_metatdata(VideoPlayerData.player_vod)
      ChannelMetadataApi.get_channel_metadata(VodMetadataApi.broadcaster_name)
    elsif metadata == 'stream'
      StreamMetadataApi.get_stream_metadata(VideoPlayerData.player_channel)
    end
  end

  class << self

    # @return [String] || [nil] The text value of 'src' otherwise nil if 'src' is empty.
    def get_broadcasters_logo
      within @player_controls_top_css do
        if page.has_css?(@logo_css)
          return find(@logo_css)[@src_attr]
        else
          return nil
        end
      end
    end

    # @return [String] || [nil] The display name, otherwise nil if display name is empty.
    def get_broadcasters_display_name
      within @player_controls_top_css do
        if page.has_css?(@display_name_css)
          return find(@display_name_css).text
        else
          return nil
        end
      end
    end

    # @return [String] || [nil] The title, otherwise nil if title is empty.
    def get_broadcasters_title
      within @player_controls_top_css do
        if page.has_css?(@title_css)
          return find(@title_css).text
        else
          return nil
        end
      end
    end

    # @return [String] || [nil] The game, otherwise nil if game is empty.
    def get_broadcasters_game
      within @player_controls_top_css do
        if find(@game_css)
          find(@game_css).text
        else
          return nil
        end
      end
    end

    # @return [String] || [nil] The game, otherwise nil if game is empty.
    def get_channel_background_preview
      if page.has_css?(@channel_preview_url)
        return find(@channel_preview_url)[@style_attr]
      else
        return nil
      end
    end

    # get_broadcaster_livestatus is only for testing against Streams.
    # @return [TrueClass] True if stream is live, otherwise false.
    def broadcaster_livestatus?
      return live_status_css? && live_status_data_attr?
    end

    # @return [TrueClass] True if LIVE is displayed, otherwise an error is raised
    private def live_status_css?
      begin
        return find(@live_status_css).text.downcase == @live_status_ui.downcase
      rescue Capybara::ElementNotFound, NoMethodError => e
        error_message('Live status was not found. Please ensure this is a streaming video and not a VOD.')
        raise e
      end
    end

    # @return [TrueClass] True if live status is equal to 'live', otherwise false.
    private def live_status_data_attr?
      return find(@video_playback)[@data_content_stream_attr] == @live_status_data_attr
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def play_pause_btn_present?
      within @player_controls_bottom_css do
        return page.has_css?(@play_pause_btn_css)
      end
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def volume_btn_present?
      within @player_controls_bottom_css do
        return page.has_css?(@volume_btn_css)
      end
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def options_btn_present?
      within @player_controls_bottom_css do
        return page.has_css?(@options_btn_css)
      end
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def fullscreen_btn_present?
      within @player_controls_bottom_css do
        return page.has_css?(@fullscreen_btn_css)
      end
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def twitch_logo_btn_present?
      return page.has_css?(@twitch_logo_btn_css)
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def player_div_present?
      return page.has_css?(@player_div)
    end

    # @return [TrueClass] True if element is found, otherwise false.
    def player_controls_bottom_present?
      return page.has_css?(@player_controls_bottom_css)
    end

    def theatremode_btn_present?
      return page.has_css?(@theatremode_btn_css)
    end

    # Hovers on player to activate controls overlay
    def click_play_pause_btn
      find(@player).hover
      find(@play_pause_btn_css).click
    end

    # Hovers on player to activate controls overlay
    def click_theatremode_btn
      find(@player_div).hover
      find(@theatremode_btn_css).click
    end

    def double_click_player
      find(@player_div).double_click
    end

    def send_esc_key
      find(@player_div).send_keys :escape
    end

    def click_overlay_play_btn
      find(@play_btn_overlay_css).click
    end

    # @return [TrueClass] True if event is found within script_timeout, otherwise false
    def video_ready_event?
      TwitchUtils.execute_event_listener_script(get_player_script('ready'), 10)
    end

    # @return [TrueClass] True if event is found within script_timeout, otherwise false
    def video_pause_event?
      TwitchUtils.execute_event_listener_script(get_player_script('pause'), 10)
    end

    # @return [TrueClass] True if overlay is found, otherwise false
    def play_btn_overlay_css?
      return page.has_css?(@play_btn_overlay_css)
    end

    # @return [TrueClass] True if overlay is not found, otherwise false
    def no_play_btn_overlay_css?
      return page.has_no_css?(@play_btn_overlay_css, visible: true)
    end

    def no_theatre_mode?
      return page.has_no_css?(@theatremode)
    end

    # @return [TrueClass] True if event is found within script_timeout, otherwise false
    def video_ended_event?
      TwitchUtils.execute_event_listener_script(get_player_script('ended'), 10)
    end

    # @return [TrueClass] True if event is found within script_timeout, otherwise false
    def video_online_event?
      TwitchUtils.execute_event_listener_script(get_player_script('online'), 10)
    end

    # @return [TrueClass] True if event is found within script_timeout, otherwise false
    def video_offline_event?
      TwitchUtils.execute_event_listener_script(get_player_script('offline'), 10)
    end

    # Executes a script to set the last known ad to a time far in the future
    def remove_ads
      # Wait until the page is fully loaded before injecting javascript before attempting to inject the script.
      # Otherwise fail.
      if player_present?
        page.execute_script('localStorage.setItem(\'lastAdDisplay\', 9999999999999)')
        visit current_url # Refresh
      else
        raise "Error. Failed 'execute_script'. HTML did not load within 5 seconds."
      end
    end

    def display_player_stats
      player = '.player'
      gear_button_css = '.qa-settings-button'
      advanced_menu_css = '.qa-advanced-button'
      stats_toggle_css = '.qa-show-stats-button'

      Capybara.find(player).hover
      Capybara.find(gear_button_css).click
      Capybara.find(advanced_menu_css).click
      Capybara.find(stats_toggle_css).click
    end

    # Retrieve player stats data.
    def get_player_stats
      if player_present?
        return page.evaluate_script("player.getVideoInfo();")
      end
    end

    def execute_async_script(script, timeout)
      # Wait until the page is fully loaded before injecting javascript before attempting to inject the script.
      # Otherwise fail.
      if player_present?
        TwitchUtils.execute_event_listener_script(script, timeout)
      else
        raise "Error. Failed 'execute_async_script'. HTML did not load within 5 seconds."
      end
    end

    def hover_ui
      player = '.player'

      Capybara.find(player).hover
    end

    def visit_with_localstore_settings(url)
      visit url

      if page.has_css?('.player', wait: 5)
        page.execute_script("localStorage.setItem(\'backend\', JSON.stringify('#{VideoPlayerData.player_type}'))")
        page.execute_script('localStorage.setItem(\'lastAdDisplay\', 9999999999999)')
        page.execute_script('localStorage.setItem(\'quality\', "")') # Don't set quality via group name
        page.execute_script('localStorage.setItem(\'quality-bitrate\', 358879)') # Mobile Bitrate
        visit url # Refresh
      else
        raise 'HTML did not load within 5 seconds'
      end

      if VideoPlayerData.player_type == 'flash'
        # Flash player appears to need an additional second or so to initialize before calls can be made to it.
        # See Jira Ticket VP-3005
        sleep(1)
      end

      validate_backend
    end

    # Sets the player channel via console
    # @param channel_name [String] The channel name to set
    def set_channel(channel_name)
      page.execute_script("player.setChannel('#{channel_name}')")
    end

    def validate_backend
        response = page.evaluate_script("localStorage.getItem(\'backend'\, JSON.stringify('#{VideoPlayerData.player_type}'))")
        if response != "\"#{VideoPlayerData.player_type}\""
            raise "Error. Player backend does not match given player type configurations: #{response} does not match \"#{VideoPlayerData.player_type}\""
        end
    end

    def player_present?
      return page.has_css?(@player, wait: 5)
    end

    private def get_player_script(event)
      return "var callback = arguments[arguments.length - 1]; player.addEventListener('#{event}', function() { callback(true)})"
    end

    # Temp method to assist with buffering event debugging.
    # Retriving stats in this way will allow insight into 'video_buffer_size'
    def fluent_wait_stats_display(poll_wait, max_wait)
      time = 0
      while time < max_wait
        sleep(poll_wait)
        puts "\n[DEBUG]: #{get_player_stats}" # Debugging hanging browser/proxy issues. See SMOC-337.
        time = time + poll_wait
      end
    end

    # @return [TrueClass] True if event is found within default_max_wait_time, otherwise false
    def content_showing_event?
      video_content_showing_event_script =
        "var callback = arguments[arguments.length - 1];
      player.addEventListener('contentShowing', function() { callback(true)});
      player.setChannel('#{VideoPlayerData.player_channel}')"
      execute_async_script(video_content_showing_event_script, 10)
    end

    # @return [TrueClass] True if event is found within default_max_wait_time, otherwise false
    def stream_playing_event?
      video_playing_event_script =
        "var callback = arguments[arguments.length - 1];
      player.addEventListener('playing', function() { callback(true)});
      player.setChannel('#{VideoPlayerData.player_channel}')"
      execute_async_script(video_playing_event_script, 10)
    end

    # @return [TrueClass] True if event is found within default_max_wait_time, otherwise false
    def vod_playing_event?
      video_playing_event_script =
        "var callback = arguments[arguments.length - 1];
      player.addEventListener('playing', function() { callback(true)});
      player.setVideo('#{VideoPlayerData.player_vod}')"
      execute_async_script(video_playing_event_script, 10)
    end

    # @return [Capybara::Element] The video player element to interact with
    def player
      return find("#{@player} video")
    end

    # @return [Capybara::Element] The video player element to interact with
    def container
      return find(@player)
    end

    # Must be on a VOD Player
    # @return [Float] The current timestamp of the VOD. Progresses as VOD plays.
    def current_vod_time
      return page.evaluate_script('arguments[0].currentTime', self.player)
    end

    # Opens the player setting menu
    # @return [Capybara::Node::Element] Returns setting button
    def settings_btn
      return find(@settings_btn_css)
    end

    # Select Advanced settings from settings menu
    # @return [Capybara::Node::Element] Returns Advanced setting button
    def advanced_setting
      return find(@advanced_settings_css)
    end

    # Click on 'Start Watching' button if channel has mature content
    def dismiss_mature_warning
      find(@mature_channel_css, wait: 10).click if page.has_css?(@mature_channel_css, visible: true)
    end

    # clicks on mute button in main player and checks if its muted
    def mute
      return if has_unmute_btn?
      JSUtils.scroll_to find(@player)
      find(@volume_btn_css).click unless has_unmute_btn?
      expect(has_unmute_btn?).to be_truthy
    end

    # Check if player on mute
    # @return [Boolean] True if player is mute
    def has_unmute_btn?
      return page.has_selector?(@unmute_btn)
    end
  end
end
