# -*- coding: utf-8 -*-
##
## This file is part of Invenio.
## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 CERN.
##
## Invenio 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 2 of the
## License, or (at your option) any later version.
##
## Invenio 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 Invenio; if not, write to the Free Software Foundation, Inc.,
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""Helper functions for building and running test suites."""

from __future__ import print_function, with_statement

# pylint: disable=E1102

CFG_TESTUTILS_VERBOSE = 1

import os
import sys
import time
pyv = sys.version_info
if pyv[0] == 2 and pyv[1] < 7:
    import unittest2 as unittest
else:
    import unittest
import cgi
import subprocess
import binascii
import StringIO

from flask import url_for
from functools import wraps
from warnings import warn
from six import iteritems
from six.moves.urllib.parse import urlsplit, urlunsplit
from urllib import urlencode
from itertools import chain, repeat

try:
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
except ImportError:
    # web tests will not be available, but unit and regression tests will:
    pass

#try:
#    from nose.tools import nottest
#except ImportError:
#    def nottest(f):
#        """Helper decorator to mark a function as not to be tested by nose."""
#        f.__test__ = False
#        return f

nottest = unittest.skip('nottest')


#@nottest
def warn_user_about_tests(test_suite_type='regression'):
    """Display a standard warning.

    Display a standard warning about running tests that might modify
    user data, and wait for user confirmation, unless --yes-i-know
    was specified in the comman line.
    """
    # Provide a command line option to avoid having to type the
    # confirmation every time during development.
    if '--yes-i-know' in sys.argv:
        return

    if test_suite_type == 'web':
        sys.stderr.write("""\
**********************************************************************
**                                                                  **
**     A B O U T   T H E   W E B   T E S T   S U I T E              **
**                                                                  **
** The web test suite will be launched in Firefox.  You must have   **
** the Selenium IDE extension installed to be able to run the web   **
** test suite.  If you do, please check out the results of the web  **
** test suite in the Selenium IDE window.                           **
**                                                                  **
**********************************************************************

""")

    sys.stderr.write("""\
**********************************************************************
**                                                                  **
**     I M P O R T A N T   W A R N I N G                            **
**                                                                  **
** T
he %s test suite needs to be run on a clean demo site   **
** that you can obtain by doing:    
                                **
**                                                                  **
**    $ inveniocfg --drop-demo-site                                 **
**    $ inveniocfg --create-demo-site                               **
**    $ inveniocfg --load-demo-records                              **
**                                                                  **
** Note that DOING THE ABOVE WILL ERASE YOUR ENTIRE DATABASE.       **
**                                                                  **
** In addition, due to the write nature of some of the tests,       **
** the demo DATABASE will be ALTERED WITH JUNK DATA, so that        **
** it is recommended to rebuild the demo site anew afterwards.      **
**                                                                  **
**********************************************************************

Please confirm by typing 'Yes, I know!': """ % test_suite_type)

    answer = raw_input('')
    if answer != 'Yes, I know!':
        sys.stderr.write("Aborted.\n")
        raise SystemExit(0)

    return


#@nottest
def make_test_suite(*test_cases):
    """Build up a test suite given separate test cases."""
    return unittest.TestSuite([unittest.makeSuite(case, 'test')
                               for case in test_cases])

from invenio.base.factory import create_app
# pyparsing needed to import here before flask.ext.testing in order to avoid
# pyparsing troubles due to twill
import pyparsing  # pylint: disable=W0611
from flask.ext.testing import TestCase


class InvenioFixture(object):

    """Fixtures."""

    def __init__(self, fixture_builder=None):
        """Initialize the fixture."""
        self.fixture = None
        self.fixture_builder = fixture_builder

    def with_data(self, *datatypes):
        """FIXME: documentation is missing."""
        def dictate(func):
            @wraps(func)
            def patched(*args, **kwargs):
                if self.fixture is None:
                    self.fixture = self.fixture_builder()

                @self.fixture.with_data(*datatypes)
                def with_data_func(data):
                    return func(data, *args, **kwargs)
                return with_data_func()
            return patched
        return dictate


class InvenioTestCase(TestCase):

    """Base test case for invenio."""

    @property
    def config(self):
        """Configuration property."""
        cfg = {
            'engine': 'CFG_DATABASE_TYPE',
            'host': 'CFG_DATABASE_HOST',
            'port': 'CFG_DATABASE_PORT',
            'username': 'CFG_DATABASE_USER',
            'password': 'CFG_DATABASE_PASS',
            'database': 'CFG_DATABASE_NAME',
        }
        out = {}
        for (k, v) in iteritems(cfg):
            if hasattr(self, k):
                out[v] = getattr(self, k)
        return out

    def create_app(self):
        """Create the Flask application for testing."""
        app = create_app(**self.config)
        app.testing = True
        return app

    def login(self, username, password):
        """Log in as username and password."""
        from invenio.config import CFG_SITE_SECURE_URL
        #from invenio.utils.url import rewrite_to_secure_url
        return self.client.post(url_for('webaccount.login'),
                                base_url=CFG_SITE_SECURE_URL,
                                #rewrite_to_secure_url(request.base_url),
                                data=dict(nickname=username,
                                          password=password),
                                follow_redirects=True)

    def logout(self):
        """Log out."""
        from invenio.config import CFG_SITE_SECURE_URL
        return self.client.get(url_for('webaccount.logout'),
                               base_url=CFG_SITE_SECURE_URL,
                               follow_redirects=True)

    def shortDescription(self):
        """Return a short description of the test case."""
        return


class FlaskSQLAlchemyTest(InvenioTestCase):

    """Setting up and tearing down the database during tests."""

    def setUp(self):
        """Create the tables."""
        from invenio.ext.sqlalchemy import db
        db.create_all()

    def tearDown(self):
        """Drop the tables."""
        from invenio.ext.sqlalchemy import db
        db.session.expunge_all()
        db.session.rollback()
        db.drop_all()


#@nottest
def make_flask_test_suite(*test_cases):
    """Build up a Flask test suite given separate test cases."""
    from operator import add
    from invenio.config import CFG_DEVEL_TEST_DATABASE_ENGINES as engines