# Copyright (C) 2019-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""Tests for here.inspector common functions."""

import os

import pytest
from here.inspector import Color, Theme, external_basemaps, here_basemaps, inspect
from here.inspector.ipyleaflet import IpyleafletInspector, options
from here.inspector.ipyleaflet.basemaps import basemap_to_tiles
from ipyleaflet import GeoData, GeoJSON, TileLayer
from shapely.geometry import LinearRing, LineString, Point, Polygon

TEST_API_KEY_VAR_NAME = "HERE_ACCESS_SECRET"
TEST_API_KEY = os.environ.get(TEST_API_KEY_VAR_NAME)


def test_default_theme():
    """Test default theme is correctly set."""
    i = IpyleafletInspector()
    options.api_key = None
    m = i.backend()
    assert i._theme == options.default_theme
    assert m.basemap.name == "Esri.WorldTopoMap"


def test_change_default_theme():
    """Test changing the default theme."""
    options.default_theme = Theme.DARK_MAP
    options.api_key = None
    i = IpyleafletInspector()
    m = i.backend()
    options.default_theme = Theme.LIGHT_MAP  # clean-up
    assert i._theme == Theme.DARK_MAP
    assert m.basemap.name == "CartoDB.DarkMatter"


def test_reuse_default_theme():
    """Test reusing the default theme after setting some other first."""
    options.api_key = None
    i = IpyleafletInspector()
    i.set_theme(Theme.LIGHT_BLANK)
    i.set_theme()
    m = i.backend()
    assert i._theme == options.default_theme
    assert m.basemap.name == "Esri.WorldTopoMap"


def test_default_colors():
    """Test default colors are correctly set."""
    options.api_key = None
    i = IpyleafletInspector()
    m = i.backend()
    assert i._colors == options.default_colors


def test_change_default_colors():
    """Test changing the default colors."""
    options.default_colors = list(reversed(options.default_colors))
    options.api_key = None
    i = IpyleafletInspector()
    m = i.backend()
    options.default_colors = list(reversed(options.default_colors))  # clean-up
    assert i._colors == list(reversed(options.default_colors))


def test_reuse_default_colors():
    """Test reusing the default colors after setting some others first."""
    options.api_key = None
    i = IpyleafletInspector()
    i.set_colors([Color.BLUE, Color.YELLOW])
    i.set_colors()
    m = i.backend()
    assert i._colors == options.default_colors


def test_default_basemap():
    """Test default basemap is correctly set."""
    options.api_key = None
    i = IpyleafletInspector()
    m = i.backend()
    assert i._custom_basemap is None
    assert m.basemap.name == "Esri.WorldTopoMap"


def test_default_basemap_api_key():
    """Test default basemap is correctly set to HERE base map when API key is present."""
    try:
        options.api_key = "SOME_KEY"  # this is not used anyway during tests
        i = IpyleafletInspector()
        m = i.backend()
        assert i._custom_basemap is None
        assert "HEREv3.reducedDay" == m.basemap.name
    finally:
        options.api_key = None


def test_custom_basemap():
    """Test custom basemap is correctly set."""
    from ipyleaflet import basemaps

    options.api_key = None
    i = IpyleafletInspector()
    i.set_basemap(basemaps.CartoDB.Positron)
    m = i.backend()
    assert i._custom_basemap == basemaps.CartoDB.Positron
    assert m.basemap.name == "CartoDB.Positron"


def test_custom_basemap_api_key():
    """Test default basemap is correctly set to HERE base map when API key is present."""
    try:
        options.api_key = "SOME_KEY"  # this is not used anyway during tests
        i = IpyleafletInspector()
        i.set_theme(Theme.DARK_MAP)
        m = i.backend()
        assert i._custom_basemap is None
        assert "HEREv3.reducedNight" == m.basemap.name
    finally:
        options.api_key = None


def test_reuse_default_basemap():
    """Test reusing the default basemap after setting one other first."""
    from ipyleaflet import basemaps

    options.api_key = None
    i = IpyleafletInspector()
    i.set_basemap(basemaps.CartoDB.Positron)
    i.set_basemap()
    m = i.backend()
    assert i._custom_basemap is None
    assert m.basemap.name == "Esri.WorldTopoMap"


def test_custom_external_basemap():
    """Test external basemap"""
    options.api_key = None
    i = IpyleafletInspector()
    bm = external_basemaps.OpenStreetMap.Mapnik
    i.set_basemap(bm)
    m = i.backend()
    assert "openstreetmap.org" in i._custom_basemap["url"]
    assert "OpenStreetMap" in i._custom_basemap["attribution"]
    assert m.basemap.name == i._custom_basemap.name


@pytest.mark.skipif(
    not TEST_API_KEY, reason=f"Test APIKEY var. name {TEST_API_KEY_VAR_NAME} not found."
)
def test_custom_here_basemap_from_options():
    """Test using a HERE basemap with an API key from configuration options."""

    i = IpyleafletInspector()
    options.api_key = TEST_API_KEY
    bm = here_basemaps.normalDay
    i.set_basemap(bm)
    m = i.backend()
    assert "hereapi.com" in i._custom_basemap["url"]
    assert "HERE" in i._custom_basemap["attribution"]
    assert m.basemap.name == i._custom_basemap["name"]


def test_default():
    options.api_key = None
    i = IpyleafletInspector()
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [(m.basemap.name, TileLayer)]

    assert layers == expected


def test_theme_map():
    options.api_key = None
    i = IpyleafletInspector()
    i.set_theme(Theme.DARK_MAP)
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [(m.basemap.name, TileLayer)]

    assert layers == expected


def test_theme_blank():
    options.api_key = None
    i = IpyleafletInspector()
    i.set_theme(Theme.LIGHT_BLANK)
    m = i.backend()

    assert len(m.layers) == 0


def test_shapely():
    from shapely.geometry import LineString, Point, Polygon

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)
    ls1 = LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)])
    poly1 = Polygon([(28, 46), (28, 52), (29, 53), (31, 53), (33, 48), (32, 47), (28, 46)])
    options.api_key = None
    i = IpyleafletInspector()
    i.add_features([pt1, pt2], "A")  # collection of shapely geometries
    i.add_features([pt2, ls1, poly1], "B")  # collection of shapely geometries
    i.add_features([poly1])  # collection of shapely geometries
    i.add_features([])  # empty collection
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("A", GeoData),
        ("B", GeoData),
        ("Unnamed 1", GeoData),
        ("Unnamed 2", GeoData),
    ]

    assert layers == expected


def test_geojson_features():
    from geojson import Feature, LineString, Point, Polygon

    pt1 = Feature(geometry=Point((30, 50)))
    pt2 = Feature(geometry=Point((32, 49)))
    ls1 = Feature(geometry=LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)]))
    poly1 = Feature(
        geometry=Polygon(
            [
                [(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)],
                [(-5.21, 23.51), (15.21, -10.81), (-20.51, 1.51), (-5.21, 23.51)],
            ]
        )
    )

    def to_dict(x):
        return dict(x.items())

    options.api_key = None
    i = IpyleafletInspector()
    i.add_features([pt1, pt2], "A")  # single GeoJSON features
    i.add_features([pt2, ls1, poly1], "B")  # single GeoJSON features
    i.add_features([poly1])  # one single GeoJSON feature
    i.add_features([])
    i.add_features(list(map(to_dict, [pt1, pt2])), "C")  # single GeoJSON feature dictionaries
    i.add_features(
        [to_dict(g) for g in [pt2, ls1, poly1]], "D"
    )  # single GeoJSON feature dictionaries
    i.add_features(to_dict(poly1), "E")  # one single GeoJSON feature dictionary
    i.add_features(
        [to_dict(poly1), pt1], "F"
    )  # mix of  single GeoJSON feature dictionary or GeoJSON features
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("A", GeoJSON),
        ("B", GeoJSON),
        ("Unnamed 1", GeoJSON),
        ("Unnamed 2", GeoData),
        ("C", GeoJSON),
        ("D", GeoJSON),
        ("E", GeoJSON),
        ("F", GeoJSON),
    ]

    assert layers == expected


def test_geojson_collections():
    from geojson import Feature, FeatureCollection, LineString, Point, Polygon

    pt1 = Feature(geometry=Point((30, 50)))
    pt2 = Feature(geometry=Point((32, 49)))
    ls1 = Feature(geometry=LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)]))
    poly1 = Feature(
        geometry=Polygon(
            [
                [(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)],
                [(-5.21, 23.51), (15.21, -10.81), (-20.51, 1.51), (-5.21, 23.51)],
            ]
        )
    )

    def to_dict(x):
        return dict(x.items())

    c1 = FeatureCollection([pt1, pt2, ls1, poly1])
    c2 = to_dict(c1)
    c3 = FeatureCollection([])
    options.api_key = None
    i = IpyleafletInspector()
    i.add_features(c1, "C1")  # single GeoJSON feature collection
    i.add_features(c2, "C2")  # single GeoJSON feature collection dictionary
    i.add_features(c3, "C3")  # empty GeoJSON feature collection
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [(m.basemap.name, TileLayer), ("C1", GeoJSON), ("C2", GeoJSON), ("C3", GeoJSON)]

    assert layers == expected


def test_geoseries():
    from geopandas import GeoSeries
    from shapely.geometry import LineString, Point, Polygon

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)
    ls1 = LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)])
    poly1 = Polygon([(28, 46), (28, 52), (29, 53), (31, 53), (33, 48), (32, 47), (28, 46)])

    gs1 = GeoSeries([pt1, pt2, poly1])
    gs2 = GeoSeries([pt1, ls1, poly1])
    gs3 = GeoSeries([pt1, pt2])

    assert gs1.dtype == "geometry"
    assert gs2.dtype == "geometry"
    assert gs3.dtype == "geometry"
    options.api_key = None
    i = IpyleafletInspector()
    i.add_features(gs1, "A")  # a GeoSeries
    i.add_features(gs2)  # a GeoSeries
    i.add_features(gs3, "B")  # a GeoSeries
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("A", GeoData),
        ("Unnamed 1", GeoData),
        ("B", GeoData),
    ]

    assert layers == expected


def test_geodataframe():
    from geopandas import GeoDataFrame
    from shapely.geometry import LineString, Point, Polygon

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)
    ls1 = LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)])
    poly1 = Polygon([(28, 46), (28, 52), (29, 53), (31, 53), (33, 48), (32, 47), (28, 46)])

    gdf1 = GeoDataFrame({"geometry": [pt1, pt2, poly1]})
    gdf2 = GeoDataFrame(geometry=[pt1, ls1, poly1])
    gdf3 = GeoDataFrame(geometry=[pt1, pt2])
    options.api_key = None
    i = IpyleafletInspector()
    i.add_features(gdf1, "A")  # a GeoDataFrame
    i.add_features(gdf2)  # a GeoDataFrame
    i.add_features(gdf3, "B")  # a GeoDataFrame
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("A", GeoData),
        ("Unnamed 1", GeoData),
        ("B", GeoData),
    ]

    assert layers == expected


def test_tiling_grid():
    from shapely.geometry import Point

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)

    tiles1 = [4, 5]
    tiles2 = [17, 18, 19]
    options.api_key = None
    i = IpyleafletInspector()
    i.add_tiles(tiles1, name="G1")
    i.add_tiles(tiles2, name="G2")
    i.add_features([pt1, pt2], "X")
    m = i.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [(m.basemap.name, TileLayer), ("G1", GeoJSON), ("G2", GeoJSON), ("X", GeoData)]

    assert layers == expected


def test_single_shapes_inspect():
    """Test render single Shapely BaseGeometry subclasses with inspect."""
    from geojson.utils import coords
    from shapely.geometry import LineString

    try:
        options.api_key = None
        options.inspector_class = IpyleafletInspector
        line = LineString([[35, 50], [35, 55]])
        m = inspect(features=line, center=line.centroid)
        layers = [layer for layer in m.layers if type(layer) == GeoData]
        assert len(layers) == 1
        assert list(coords(layers[0].data)) == list(line.coords)
    finally:
        options.inspector_class = None


def test_single_shapes_ipyleafletinspector():
    """Test render single Shapely BaseGeometry subclasses with IpyleafletInspector."""
    from geojson.utils import coords
    from shapely.geometry import LineString

    options.api_key = None
    line = LineString([[30, 50], [35, 55]])
    insp = IpyleafletInspector()
    insp.set_center(line.centroid)
    insp.set_zoom(10)
    insp.add_features(line)
    m = insp.backend()
    layers = [layer for layer in m.layers if type(layer) == GeoData]
    assert len(layers) == 1
    assert list(coords(layers[0].data)) == list(line.coords)


def test_add_point():
    """Test by adding single point with IpyleafletInspector."""
    options.api_key = None
    insp = IpyleafletInspector()
    pt1 = Point(30, 50)
    insp.add_features(pt1)
    m = insp.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("Unnamed 1", GeoData),
    ]
    assert layers == expected


def test_no_area_polygon():
    """Test for polygon having 0 area and only one point with IpyleafletInspector."""
    options.api_key = None
    insp = IpyleafletInspector()
    poly1 = Polygon([(28, 46), (28, 46), (28, 46), (28, 46), (28, 46)])
    insp.add_features([poly1])  # collection of shapely geometries
    m = insp.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("Unnamed 1", GeoData),
    ]
    assert layers == expected


def test_no_length_line():
    """Test with line with 0 length"""
    options.api_key = None
    insp = IpyleafletInspector()
    line = LineString([[35, 35], [35, 35]])
    insp.add_features(line)
    m = insp.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        (m.basemap.name, TileLayer),
        ("Unnamed 1", GeoData),
    ]
    assert layers == expected


def test_no_linearring():
    """Test with 0 area linear ring"""
    options.api_key = None
    insp = IpyleafletInspector()
    linear_ring = LinearRing(
        [
            (13, 13),
            (13, 13),
            (13, 13),
        ]
    )
    insp.add_features(linear_ring)
    m = insp.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [(m.basemap.name, TileLayer), ("Unnamed 1", GeoData)]
    assert layers == expected


def test_basemap_to_tiles_dict():
    """Test basemap_to_tiles if basemap is dictionary."""
    basemap = dict(
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        max_zoom=19,
        attribution='Map data (c) <a href="https://openstreetmap.org">OpenStreetMap</a> contributors',
    )
    tilelayer = basemap_to_tiles(basemap)
    assert isinstance(tilelayer, TileLayer)


def test_basemap_to_tiles_exception():
    """Test exception is raised for invalid basemap type."""
    with pytest.raises(ValueError):
        basemap_to_tiles(basemap=[])
