# frozen_string_literal: true

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Appium
  module Core
    class Base
      class Bridge < ::Selenium::WebDriver::Remote::Bridge
        # Prefix for extra capability defined by W3C
        APPIUM_PREFIX = 'appium:'

        # TODO: Remove the forceMjsonwp after Appium server won't need it
        FORCE_MJSONWP = :forceMjsonwp

        # Almost same as self.handshake in ::Selenium::WebDriver::Remote::Bridge
        #
        # Implements protocol handshake which:
        #
        #   1. Creates session with driver.
        #   2. Sniffs response.
        #   3. Based on the response, understands which dialect we should use.
        #
        # @return [Bridge::MJSONWP, Bridge::W3C]
        #
        def self.handshake(**opts)
          desired_capabilities = opts.delete(:desired_capabilities) { ::Selenium::WebDriver::Remote::Capabilities.new }

          if desired_capabilities.is_a?(Symbol)
            unless ::Selenium::WebDriver::Remote::Capabilities.respond_to?(desired_capabilities)
              raise ::Selenium::WebDriver::Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
            end

            desired_capabilities = ::Selenium::WebDriver::Remote::Capabilities.__send__(desired_capabilities)
          end

          bridge = new(opts)
          capabilities = bridge.create_session(desired_capabilities)

          case bridge.dialect
          when :oss # for MJSONWP
            Bridge::MJSONWP.new(capabilities, bridge.session_id, opts)
          when :w3c
            Bridge::W3C.new(capabilities, bridge.session_id, opts)
          else
            raise CoreError, 'cannot understand dialect'
          end
        end

        # Override
        # Creates session handling both OSS and W3C dialects.
        # Copy from Selenium::WebDriver::Remote::Bridge to keep using +merged_capabilities+ for Appium
        #
        # If +desired_capabilities+ has +forceMjsonwp: true+ in the capability, this bridge works with mjsonwp protocol.
        # If +forceMjsonwp: false+ or no the capability, it depends on server side whether this bridge works as w3c or mjsonwp.
        #
        # @param [::Selenium::WebDriver::Remote::W3C::Capabilities, Hash] desired_capabilities A capability
        # @return [::Selenium::WebDriver::Remote::Capabilities, ::Selenium::WebDriver::Remote::W3C::Capabilities]
        #
        # @example
        #
        #   opts = {
        #     caps: {
        #       platformName: :ios,
        #       automationName: 'XCUITest',
        #       app: 'test/functional/app/UICatalog.app.zip',
        #       platformVersion: '11.4',
        #       deviceName: 'iPhone Simulator',
        #       useNewWDA: true,
        #       forceMjsonwp: true
        #     },
        #     appium_lib: {
        #       wait: 30
        #     }
        #   }
        #   core = ::Appium::Core.for(caps)
        #   driver = core.start_driver #=> driver.dialect == :oss
        #
        # @example
        #
        #   opts = {
        #     caps: {
        #       platformName: :ios,
        #       automationName: 'XCUITest',
        #       app: 'test/functional/app/UICatalog.app.zip',
        #       platformVersion: '11.4',
        #       deviceName: 'iPhone Simulator',
        #       useNewWDA: true,
        #     },
        #     appium_lib: {
        #       wait: 30
        #     }
        #   }
        #   core = ::Appium::Core.for(caps)
        #   driver = core.start_driver #=> driver.dialect == :w3c if the Appium server support W3C.
        #
        def create_session(desired_capabilities)
          response = execute(:new_session, {}, merged_capabilities(desired_capabilities))

          @session_id = response['sessionId']
          oss_status = response['status'] # for compatibility with Appium 1.7.1-
          value = response['value']

          if value.is_a?(Hash) # include for W3C format
            @session_id = value['sessionId'] if value.key?('sessionId')

            if value.key?('capabilities')
              value = value['capabilities']
            elsif value.key?('value')
              value = value['value']
            end
          end

          raise ::Selenium::WebDriver::Error::WebDriverError, 'no sessionId in returned payload' unless @session_id

          json_create(oss_status, value)
        end

        # Append +appium:+ prefix for Appium following W3C spec
        # https://www.w3.org/TR/webdriver/#dfn-validate-capabilities
        #
        # @param [::Selenium::WebDriver::Remote::W3C::Capabilities, Hash] capabilities A capability
        # @return [::Selenium::WebDriver::Remote::W3C::Capabilities]
        def add_appium_prefix(capabilities)
          w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.new

          capabilities = capabilities.__send__(:capabilities) unless capabilities.is_a?(Hash)

          capabilities.each do |name, value|
            next if value.nil?
            next if value.is_a?(String) && value.empty?

            capability_name = name.to_s
            w3c_name = extension_prefix?(capability_name) ? name : "#{APPIUM_PREFIX}#{capability_name}"

            w3c_capabilities[w3c_name] = value
          end

          w3c_capabilities
        end

        private

        def camel_case(str)
          str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
        end

        def extension_prefix?(capability_name)
          snake_cased_capability_names = ::Selenium::WebDriver::Remote::W3C::Capabilities::KNOWN.map(&:to_s)
          camel_cased_capability_names = snake_cased_capability_names.map { |v| camel_case(v) }

          snake_cased_capability_names.include?(capability_name) ||
            camel_cased_capability_names.include?(capability_name) ||
            capability_name.match(::Selenium::WebDriver::Remote::W3C::Capabilities::EXTENSION_CAPABILITY_PATTERN)
        end

        def json_create(oss_status, value)
          if oss_status
            ::Selenium::WebDriver.logger.info 'Detected OSS dialect.'
            @dialect = :oss
            ::Selenium::WebDriver::Remote::Capabilities.json_create(value)
          else
            ::Selenium::WebDriver.logger.info 'Detected W3C dialect.'
            @dialect = :w3c
            ::Selenium::WebDriver::Remote::W3C::Capabilities.json_create(value)
          end
        end

        def delete_force_mjsonwp(capabilities)
          w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.new

          capabilities = capabilities.__send__(:capabilities) unless capabilities.is_a?(Hash)
          capabilities.each do |name, value|
            next if value.nil?
            next if value.is_a?(String) && value.empty?
            next if name == FORCE_MJSONWP

            w3c_capabilities[name] = value
          end

          w3c_capabilities
        end

        def merged_capabilities(desired_capabilities)
          force_mjsonwp = desired_capabilities[FORCE_MJSONWP]
          desired_capabilities = delete_force_mjsonwp(desired_capabilities) unless force_mjsonwp.nil?

          if force_mjsonwp
            {
              desiredCapabilities: desired_capabilities
            }
          else
            new_caps = add_appium_prefix(desired_capabilities)
            w3c_capabilities = ::Selenium::WebDriver::Remote::W3C::Capabilities.from_oss(new_caps)

            {
              desiredCapabilities: desired_capabilities,
              capabilities: {
                firstMatch: [w3c_capabilities]
              }
            }
          end
        end
      end # class Bridge
    end # class Base
  end # module Core
end # module Appium