import json

import pytest

import maplibre_tiles as mlt

POINT = {"type": "Point", "coordinates": [1, 2]}
LINE = {"type": "LineString", "coordinates": [[0, 0], [5, 5]]}


def _fc(features):
    return {"type": "FeatureCollection", "features": features}


def _feature(geometry, **members):
    return {"type": "Feature", "geometry": geometry, **members}


def test_point_feature_collection_roundtrips():
    blob = mlt.encode_geojson(_fc([_feature(POINT)]), "roads", extent=4096)
    assert isinstance(blob, bytes)

    layers = mlt.decode_mlt(blob)
    assert len(layers) == 1
    layer = layers[0]
    assert layer.name == "roads"
    assert layer.extent == 4096
    assert len(layer.features) == 1
    assert layer.features[0].geometry_type == "Point"

    fc = json.loads(mlt.decode_mlt_to_geojson(blob))
    assert fc["features"][0]["geometry"] == POINT


def test_extent_defaults_to_4096():
    blob = mlt.encode_geojson(_fc([_feature(POINT)]), "roads")
    assert mlt.decode_mlt(blob)[0].extent == 4096


def test_scalar_properties_roundtrip():
    blob = mlt.encode_geojson(
        _fc(
            [
                _feature(
                    POINT,
                    properties={
                        "name": "main",
                        "lanes": 3,
                        "oneway": True,
                        "width": 3.5,
                    },
                )
            ]
        ),
        "roads",
    )
    props = mlt.decode_mlt(blob)[0].features[0].properties
    assert props["name"] == "main"
    assert props["lanes"] == 3
    assert props["oneway"] is True
    assert props["width"] == 3.5


def test_feature_id_roundtrips():
    blob = mlt.encode_geojson(
        _fc(
            [
                _feature(POINT, id=42),
                _feature({"type": "Point", "coordinates": [3, 4]}),
            ]
        ),
        "roads",
    )
    feats = mlt.decode_mlt(blob)[0].features
    assert feats[0].id == 42
    assert feats[1].id is None


@pytest.mark.parametrize(
    "geometry",
    [
        {"type": "Point", "coordinates": [2048, 1024]},
        {"type": "MultiPoint", "coordinates": [[1, 1], [2, 2]]},
        {"type": "LineString", "coordinates": [[0, 0], [10, 0], [10, 10]]},
        {
            "type": "MultiLineString",
            "coordinates": [[[0, 0], [1, 1]], [[2, 2], [3, 3]]],
        },
        {
            "type": "Polygon",
            "coordinates": [
                [[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]],
                [[2, 2], [4, 2], [4, 4], [2, 4], [2, 2]],
            ],
        },
        {
            "type": "MultiPolygon",
            "coordinates": [
                [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]],
                [[[6, 6], [8, 6], [8, 8], [6, 8], [6, 6]]],
            ],
        },
    ],
    ids=lambda g: g["type"],
)
def test_geometry_kinds_roundtrip(geometry):
    blob = mlt.encode_geojson(_fc([_feature(geometry)]), "l")
    fc = json.loads(mlt.decode_mlt_to_geojson(blob))
    assert fc["features"][0]["geometry"] == geometry


def test_multi_layer_tile_via_concatenation():
    roads = mlt.encode_geojson(
        _fc([_feature(POINT, id=1)]),
        "roads",
    )
    water = mlt.encode_geojson(
        _fc([_feature(LINE, id=2)]),
        "water",
    )
    tile = b"".join([roads, water])
    layers = mlt.decode_mlt(tile)
    assert [layer.name for layer in layers] == ["roads", "water"]
    assert mlt.list_layers(tile) == ["roads", "water"]


@pytest.mark.parametrize(
    "feature",
    [
        _feature(POINT, properties={"tags": {"a": 1}}),
        _feature(POINT, properties={"tags": [1, 2]}),
        _feature(POINT, id="way/1"),
        _feature(POINT, id=-1),
        _feature(POINT, id=3.5),
        _feature(None),
        _feature({"type": "LineString", "coordinates": []}),
        _feature({"type": "Point", "coordinates": [1.2, 3.4]}),
        _feature({"type": "Point", "coordinates": [1, 2, 3]}),
        {"geometry": POINT},  # missing "type": "Feature"
    ],
    ids=[
        "nested_dict_prop",
        "nested_list_prop",
        "string_id",
        "negative_id",
        "float_id",
        "null_geometry",
        "empty_geometry",
        "fractional_coord",
        "three_d_coord",
        "not_a_feature",
    ],
)
def test_strict_feature_errors(feature):
    with pytest.raises(ValueError):
        mlt.encode_geojson(_fc([feature]), "r")


def test_non_feature_collection_input_errors():
    # A bare Feature (not wrapped in a FeatureCollection) is rejected.
    with pytest.raises(ValueError):
        mlt.encode_geojson(_feature(POINT), "r")


def test_empty_layer_errors():
    with pytest.raises(ValueError):
        mlt.encode_geojson(_fc([]), "r")


POLYGON = {
    "type": "Polygon",
    "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]],
}


def test_tessellate_polygon_returns_bytes():
    blob = mlt.encode_geojson(_fc([_feature(POLYGON)]), "l", tessellate=True)
    assert isinstance(blob, bytes)


@pytest.mark.parametrize("sort", ["auto", "morton", "hilbert", "id", "none"])
def test_sort_modes_return_bytes(sort):
    blob = mlt.encode_geojson(_fc([_feature(POLYGON, id=1)]), "l", sort=sort)
    assert isinstance(blob, bytes)


def test_invalid_sort_errors():
    with pytest.raises(ValueError):
        mlt.encode_geojson(_fc([_feature(POINT)]), "l", sort="bogus")


@pytest.mark.parametrize("shared_dict", [True, False])
def test_shared_dict_returns_bytes(shared_dict):
    blob = mlt.encode_geojson(
        _fc([_feature(POINT, properties={"name": "main"})]),
        "l",
        shared_dict=shared_dict,
    )
    assert isinstance(blob, bytes)


@pytest.mark.parametrize("flag", ["fsst", "fastpfor"])
@pytest.mark.parametrize("value", [True, False])
def test_compression_toggles_return_bytes(flag, value):
    blob = mlt.encode_geojson(
        _fc([_feature(POINT, properties={"name": "main", "lanes": 3})]),
        "l",
        **{flag: value},
    )
    assert isinstance(blob, bytes)
