##############################################################################
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import os
import urlparse
import multiprocessing
from time import sleep
import tempfile
from BaseHTTPServer import HTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
import cgi
import json

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from PIL import Image
from io import BytesIO
import requests

from utils import SlapOSInstanceTestCase
from utils import findFreeTCPPort


debug_mode = os.environ.get('DEBUG')
# for development: debugging logs and install Ctrl+C handler
if debug_mode:
  import logging
  logging.basicConfig(level=logging.DEBUG)
  import unittest
  unittest.installHandler()


class WebServerMixin(object):
  """Mixin class which provides a simple web server reachable at self.server_url
  """
  def setUp(self):
    """Start a minimal web server.
    """
    class TestHandler(BaseHTTPRequestHandler):
      """Request handler for our test server.

      The implemented server is: submit q and you'll get a page with q as title.
      """
      def log_message(self, *args, **kw):
        if debug_mode:
          BaseHTTPRequestHandler.log_message(self, *args, **kw)
      def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write('''
          <html>
            <title>Test page</title>
            <body>
              <form action="/" method="POST">
                <input name="q" type="text"></input>
                <input type="submit" value="I'm feeling lucky"></input>
              </form>
            </body>
          </html>''')
      def do_POST(self):
        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD':'POST',
                     'CONTENT_TYPE':self.headers['Content-Type'],})
        self.send_response(200)
        self.end_headers()
        self.wfile.write('''
          <html>
            <title>%s</title>
          </html>
        ''' % form['q'].value)

    super(WebServerMixin, self).setUp()
    ip = os.environ.get('LOCAL_IPV4', '127.0.1.1')
    port = findFreeTCPPort(ip)
    server = HTTPServer((ip, port), TestHandler)
    self.server_process = multiprocessing.Process(target=server.serve_forever)
    self.server_process.start()
    self.server_url = 'http://%s:%s/' % (ip, port)

  def tearDown(self):
    self.server_process.terminate()
    self.server_process.join()
    super(WebServerMixin, self).tearDown()


class BrowserCompatibilityMixin(WebServerMixin):
  """Mixin class to run validation tests on a specific browser
  """
  desired_capabilities = NotImplemented
  user_agent = NotImplemented

  def setUp(self):
    super(BrowserCompatibilityMixin, self).setUp()
    self.driver = webdriver.Remote(
         command_executor=self.computer_partition.getConnectionParameterDict()['backend-url'],
         desired_capabilities=self.desired_capabilities)

  def tearDown(self):
    self.driver.quit()
    super(BrowserCompatibilityMixin, self).tearDown()

  def test_user_agent(self):
    self.assertIn(
        self.user_agent,
        self.driver.execute_script('return navigator.userAgent'))

  def test_screenshot(self):
    self.driver.get(self.server_url)
    screenshot = Image.open(BytesIO(self.driver.get_screenshot_as_png()))
    # just check it's not a white screen
    self.assertGreater(len(screenshot.getcolors()), 2)

  def test_scenario(self):
    self.driver.get(self.server_url)
    input_element  = WebDriverWait(self.driver, 3).until(
        EC.visibility_of_element_located((By.NAME, 'q')))
    input_element.send_keys(self.id())
    input_element.submit()
    self.assertEqual(self.id(), self.driver.title)

  def test_window_and_screen_size(self):
    size = json.loads(self.driver.execute_script('''
      return JSON.stringify({
        'screen.width': window.screen.width,
        'screen.height': window.screen.height,
        'screen.pixelDepth': window.screen.pixelDepth,
        'innerWidth': window.innerWidth,
        'innerHeight': window.innerHeight })'''))
    # Xvfb is configured like this
    self.assertEqual(1024, size['screen.width'])
    self.assertEqual(768, size['screen.height'])
    self.assertEqual(24, size['screen.pixelDepth'])

    # window size must not be 0 (wrong firefox integration report this)
    self.assertGreater(size['innerWidth'], 0)
    self.assertGreater(size['innerHeight'], 0)

  #def test_resize_window(self):
  #  self.fail('TODO')

  #def test_upload_file(self):
  #  self.fail('TODO')


class SeleniumServerTestCase(SlapOSInstanceTestCase):
  """Test the remote driver on a minimal web server.
  """
  @classmethod
  def getSoftwareURLList(cls):
    return (os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'software.cfg')), )


class TestBrowserSelection(WebServerMixin, SeleniumServerTestCase):
  def test_firefox(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    webdriver_url = parameter_dict['backend-url']

    driver = webdriver.Remote(
         command_executor=webdriver_url,
         desired_capabilities=DesiredCapabilities.FIREFOX)

    driver.get(self.server_url)
    self.assertEqual('Test page', driver.title)

    self.assertIn(
        'Firefox',
        driver.execute_script('return navigator.userAgent'))
    driver.quit()

  def test_chrome(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    webdriver_url = parameter_dict['backend-url']

    driver = webdriver.Remote(
         command_executor=webdriver_url,
         desired_capabilities=DesiredCapabilities.CHROME)

    driver.get(self.server_url)
    self.assertEqual('Test page', driver.title)

    self.assertIn(
        'Chrome',
        driver.execute_script('return navigator.userAgent'))
    self.assertNotIn(
        'Firefox',
        driver.execute_script('return navigator.userAgent'))
    driver.quit()

  def XXX_test_firefox_desired_version(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    webdriver_url = parameter_dict['backend-url']

    desired_capabilities = DesiredCapabilities.FIREFOX.copy()
    desired_capabilities['version'] = '51.0.1'
    driver = webdriver.Remote(
         command_executor=webdriver_url,
         desired_capabilities=desired_capabilities)
    self.assertIn(
        'Gecko/20100101 Firefox/51.0',
        driver.execute_script('return navigator.userAgent'))
    driver.quit()

    desired_capabilities['version'] = '52.9.0esr'
    driver = webdriver.Remote(
         command_executor=webdriver_url,
         desired_capabilities=desired_capabilities)
    self.assertIn(
        'Gecko/20100101 Firefox/52.0',
        driver.execute_script('return navigator.userAgent'))
    driver.quit()

  def test_parralel_connections(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    webdriver_url = parameter_dict['backend-url']
  
    queue = multiprocessing.Queue()
    def _test(q, server_url):
      driver = webdriver.Remote(
           command_executor=webdriver_url,
        # XXX
           desired_capabilities= dict(DesiredCapabilities.FIREFOX, version='52.9.0esr')) #DesiredCapabilities.FIREFOX)
      try:
        driver.get(server_url)
        q.put(driver.title == 'Test page')
      finally:
        driver.quit()

    nb_workers = 10
    workers = []
    for i in range(nb_workers):
      worker = multiprocessing.Process(
          target=_test,
          args=(queue, self.server_url))

      worker.start()
      workers.append(worker)

    _ = [worker.join() for worker in workers]

    self.assertEqual(
        [True] * nb_workers,
        [queue.get() for _ in range(nb_workers)])


class TestFrontend(WebServerMixin, SeleniumServerTestCase):
  def test_register_url(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    register_url = parameter_dict['register-url']

    parsed = urlparse.urlparse(register_url)
    self.assertEqual('node', parsed.username)
    self.assertTrue(parsed.password)

    self.assertIn(
      # we don't do a proper register request, this response is 500.
        'JsonException: Expected to read a START_MAP but instead have: END',
        requests.get(register_url, verify=False).text)

  def test_admin(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    admin_url = parameter_dict['admin-url']

    parsed = urlparse.urlparse(admin_url)
    self.assertEqual('admin', parsed.username)
    self.assertTrue(parsed.password)

    self.assertIn(
        'Grid Console',
        requests.get(admin_url, verify=False).text)

  def test_browser_use_hub(self):
    parameter_dict = self.computer_partition.getConnectionParameterDict()
    webdriver_url = parameter_dict['url']
    parsed = urlparse.urlparse(webdriver_url)
    self.assertEqual('selenium', parsed.username)
    self.assertTrue(parsed.password)

    driver = webdriver.Remote(
         command_executor=webdriver_url,
         desired_capabilities=DesiredCapabilities.CHROME)

    driver.get(self.server_url)
    self.assertEqual('Test page', driver.title)
    driver.quit()


class TestFirefox52(BrowserCompatibilityMixin, SeleniumServerTestCase):
  desired_capabilities = dict(DesiredCapabilities.FIREFOX, version='52.9.0esr')
  user_agent = 'Gecko/20100101 Firefox/52.0'


class TestFirefox60(BrowserCompatibilityMixin, SeleniumServerTestCase):
  desired_capabilities = dict(DesiredCapabilities.FIREFOX, version='60.0.2esr')
  user_agent = 'Gecko/20100101 Firefox/60.0'


class TestChrome69(BrowserCompatibilityMixin, SeleniumServerTestCase):
  desired_capabilities = dict(DesiredCapabilities.CHROME, version='69.0.3497.0')
  user_agent = 'Chrome/69.0.3497.0'


if 1:
  del TestFrontend
  del TestChrome69
  del TestFirefox52
  #del TestFirefox60
  del TestBrowserSelection


# more test:
#  XXX run ERP5 test !

