"""Module grouping tests for the boring search module."""
import glob
from io import open

import pytest

import owslib
import pydov
from owslib.etree import etree
from owslib.fes import (
    SortBy,
    SortProperty,
    And,
)
from owslib.wfs import WebFeatureService
from pydov.search.boring import BoringSearch
from pydov.search.grondwaterfilter import GrondwaterFilterSearch

from numpy.compat import unicode

from pydov.search.grondwatermonster import GrondwaterMonsterSearch
from pydov.search.interpretaties import (
    InformeleStratigrafieSearch,
    FormeleStratigrafieSearch,
    InformeleHydrogeologischeStratigrafieSearch,
    GeotechnischeCoderingSearch,
    QuartairStratigrafieSearch,
)
from pydov.search.interpretaties import HydrogeologischeStratigrafieSearch
from pydov.search.interpretaties import GecodeerdeLithologieSearch
from pydov.search.interpretaties import LithologischeBeschrijvingenSearch
from pydov.search.sondering import SonderingSearch
from pydov.search.grondmonster import GrondmonsterSearch

from pydov.util.dovutil import build_dov_url

from pydov.util.errors import (
    InvalidSearchParameterError,
)
from pydov.util.location import (
    WithinDistance,
    Point,
)
from tests.abstract import service_ok

search_objects = [BoringSearch(),
                  SonderingSearch(),
                  GrondwaterFilterSearch(),
                  GrondwaterMonsterSearch(),
                  FormeleStratigrafieSearch(),
                  InformeleHydrogeologischeStratigrafieSearch(),
                  GeotechnischeCoderingSearch(),
                  QuartairStratigrafieSearch(),
                  InformeleStratigrafieSearch(),
                  HydrogeologischeStratigrafieSearch(),
                  GecodeerdeLithologieSearch(),
                  LithologischeBeschrijvingenSearch(),
                  GrondmonsterSearch()]


@pytest.fixture(scope='module')
def mp_wfs(monkeymodule):
    """Monkeypatch the call to the remote GetCapabilities request.

    Parameters
    ----------
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.

    """
    def read(*args, **kwargs):
        with open('tests/data/util/owsutil/wfscapabilities.xml', 'r',
                  encoding='utf-8') as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
            data = etree.fromstring(data)
        return data

    monkeymodule.setattr(
        owslib.feature.common.WFSCapabilitiesReader, 'read', read)


@pytest.fixture(scope='module')
def wfs(mp_wfs):
    """PyTest fixture providing an instance of a WebFeatureService based on
    a local copy of a GetCapabilities request.

    Parameters
    ----------
    mp_wfs : pytest.fixture
        Monkeypatch the call to the remote GetCapabilities request.

    Returns
    -------
    owslib.wfs.WebFeatureService
        WebFeatureService based on the local GetCapabilities.

    """
    return WebFeatureService(
        url=build_dov_url('geoserver/wfs'), version="1.1.0")


@pytest.fixture()
def mp_remote_fc_notfound(monkeypatch):
    """Monkeypatch the call to get an inexistent remote featurecatalogue.

    Parameters
    ----------
    monkeypatch : pytest.fixture
        PyTest monkeypatch fixture.

    """
    def __get_remote_fc(*args, **kwargs):
        with open('tests/data/util/owsutil/fc_featurecatalogue_notfound.xml',
                  'r') as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
        return data

    monkeypatch.setattr(pydov.util.owsutil, '__get_remote_fc', __get_remote_fc)


@pytest.fixture(scope='module')
def mp_remote_md(wfs, monkeymodule, request):
    """Monkeypatch the call to get the remote metadata of the layer.

    This monkeypatch requires a module variable ``location_md_metadata``
    with the path to the md_metadata file on disk.

    Parameters
    ----------
    wfs : pytest.fixture returning owslib.wfs.WebFeatureService
        WebFeatureService based on the local GetCapabilities.
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.
    request : pytest.fixtue
        PyTest fixture providing request context.

    """
    def __get_remote_md(*args, **kwargs):
        file_path = getattr(request.module, "location_md_metadata")
        with open(file_path, 'r') as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
        return data

    monkeymodule.setattr(pydov.util.owsutil, '__get_remote_md',
                         __get_remote_md)


@pytest.fixture(scope='module')
def mp_remote_fc(monkeymodule, request):
    """Monkeypatch the call to get the remote feature catalogue.

    This monkeypatch requires a module variable
    ``location_fc_featurecatalogue`` with the path to the fc_featurecatalogue
    file on disk.

    Parameters
    ----------
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.
    request : pytest.fixtue
        PyTest fixture providing request context.

    """
    def __get_remote_fc(*args, **kwargs):
        file_path = getattr(request.module, "location_fc_featurecatalogue")
        with open(file_path, 'r') as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
        return data

    monkeymodule.setattr(pydov.util.owsutil, '__get_remote_fc',
                         __get_remote_fc)


@pytest.fixture(scope='module')
def mp_remote_describefeaturetype(monkeymodule, request):
    """Monkeypatch the call to a remote DescribeFeatureType.

    This monkeypatch requires a module variable
    ``location_wfs_describefeaturetype`` with the path to the
    wfs_describefeaturetype file on disk.

    Parameters
    ----------
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.
    request : pytest.fixtue
        PyTest fixture providing request context.

    """
    def __get_remote_describefeaturetype(*args, **kwargs):
        file_path = getattr(request.module, "location_wfs_describefeaturetype")
        with open(file_path, 'r') as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
        return data

    monkeymodule.setattr(pydov.util.owsutil,
                         '__get_remote_describefeaturetype',
                         __get_remote_describefeaturetype)


@pytest.fixture(scope='module')
def wfs_getfeature(request):
    """PyTest fixture providing a WFS GetFeature response.

    This monkeypatch requires a module variable ``location_wfs_getfeature``
    with the path to the wfs_getfeature file on disk.

    Parameters
    ----------
    request : pytest.fixtue
        PyTest fixture providing request context.

    Returns
    -------
    str
        WFS response of a GetFeature call to the dov-pub:Boringen layer.

    """
    file_path = getattr(request.module, "location_wfs_getfeature")
    with open(file_path, 'r') as f:
        data = f.read()
        return data


@pytest.fixture(scope='module')
def wfs_feature(request):
    """PyTest fixture providing an XML of a WFS feature element.

    This monkeypatch requires a module variable ``location_wfs_feature``
    with the path to the wfs_feature file on disk.

    Parameters
    ----------
    request : pytest.fixtue
        PyTest fixture providing request context.

    Returns
    -------
    etree.Element
        XML element representing a single record of the Boring WFS layer.

    """
    file_path = getattr(request.module, "location_wfs_feature")
    with open(file_path, 'r') as f:
        return etree.fromstring(f.read())


@pytest.fixture(scope='module')
def mp_remote_wfs_feature(monkeymodule, request):
    """Monkeypatch the call to get WFS features.

    This monkeypatch requires a module variable ``location_wfs_getfeature``
    with the path to the wfs_getfeature file on disk.

    Parameters
    ----------
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.
    request : pytest.fixtue
        PyTest fixture providing request context.

    """
    def __get_remote_wfs_feature(*args, **kwargs):
        file_path = getattr(request.module, "location_wfs_getfeature")
        with open(file_path, 'r') as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
        return data

    monkeymodule.setattr(pydov.util.owsutil,
                         'wfs_get_feature',
                         __get_remote_wfs_feature)


@pytest.fixture(scope='module')
def mp_dov_xml(monkeymodule, request):
    """Monkeypatch the call to get the remote XML data.

    This monkeypatch requires a module variable ``location_dov_xml``
    with the path to the dov_xml file on disk.

    Parameters
    ----------
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.
    request : pytest.fixtue
        PyTest fixture providing request context.

    """

    def _get_xml_data(*args, **kwargs):
        file_path = getattr(request.module, "location_dov_xml")
        with open(file_path, 'r', encoding="utf-8") as f:
            data = f.read()
            if type(data) is not bytes:
                data = data.encode('utf-8')
        return data

    monkeymodule.setattr(pydov.types.abstract.AbstractDovType,
                         '_get_xml_data', _get_xml_data)


@pytest.fixture()
def mp_dov_xml_broken(monkeypatch):
    """Monkeypatch the call to break the fetching of remote XML data.

    Parameters
    ----------
    monkeypatch : pytest.fixture
        PyTest monkeypatch fixture.

    """

    def _get_xml_data(*args, **kwargs):
        raise RuntimeError

    monkeypatch.setattr(pydov.types.abstract.AbstractDovType,
                        '_get_xml_data', _get_xml_data)


@pytest.fixture()
def mp_remote_xsd(monkeymodule, request):
    """Monkeypatch the call to get the remote XSD schemas.

    This monkeypatch requires a module variable ``location_xsd_base``
    with a glob expression to the XSD file(s) on disk.

    Parameters
    ----------
    monkeymodule : pytest.fixture
        PyTest monkeypatch fixture with module scope.
    request : pytest.fixtue
        PyTest fixture providing request context.

    """

    def _get_remote_xsd(*args, **kwargs):
        xsd_base_path = getattr(request.module, "location_xsd_base")
        schemas = []

        for xsd_file in glob.glob(xsd_base_path):
            with open(xsd_file, 'r', encoding="utf-8") as f:
                data = f.read()
                if type(data) is not bytes:
                    data = data.encode('utf-8')
                schemas.append(etree.fromstring(data))

        return schemas

    monkeymodule.setattr(pydov.search.abstract.AbstractSearch,
                         '_get_remote_xsd_schemas', _get_remote_xsd)


@pytest.mark.parametrize("objectsearch", search_objects)
def test_get_description(mp_wfs, objectsearch):
    """Test the get_description method.

    Test whether the method returns a non-empty string.

    Parameters
    ----------
    mp_wfs : pytest.fixture
        Monkeypatch the call to the remote GetCapabilities request.
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    description = objectsearch.get_description()

    assert type(description) in (str, unicode)
    assert len(description) > 0


@pytest.mark.online
@pytest.mark.skipif(not service_ok(), reason="DOV service is unreachable")
@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_location(objectsearch):
    """Test the get_description method.

    Test whether the method returns a non-empty string.

    Parameters
    ----------
    mp_wfs : pytest.fixture
        Monkeypatch the call to the remote GetCapabilities request.
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    objectsearch.search(location=WithinDistance(Point(100000, 100000), 100))


@pytest.mark.online
@pytest.mark.skipif(not service_ok(), reason="DOV service is unreachable")
@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_maxfeatures(objectsearch):
    """Test the search method with a max_features parameter.

    Test whether no error is raised.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    objectsearch.search(location=WithinDistance(Point(100000, 100000), 100),
                        max_features=10)


@pytest.mark.online
@pytest.mark.skipif(not service_ok(), reason="DOV service is unreachable")
@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_maxfeatures_only(objectsearch):
    """Test the search method with only the max_features parameter.

    Test whether no error is raised.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    objectsearch.search(max_features=1)


@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_nolocation_noquery(objectsearch):
    """Test the search method without providing a location or a query.

    Test whether an InvalidSearchParameterError is raised.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    with pytest.raises(InvalidSearchParameterError):
        objectsearch.search(location=None, query=None)


@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_both_location_query_wrongquerytype(objectsearch):
    """Test the search method providing both a location and a query,
    using a query with an invalid type.

    Test whether an InvalidSearchParameterError is raised.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    with pytest.raises(InvalidSearchParameterError):
        objectsearch.search(location=(1, 2, 3, 4),
                            query='computer says no')


@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_query_wrongtype(objectsearch):
    """Test the search method with the query parameter using a wrong
    query type.

    Test whether an InvalidSearchParameterError is raised.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    with pytest.raises(InvalidSearchParameterError):
        objectsearch.search(query='computer says no')
