from __future__ import annotations

from typing import TYPE_CHECKING, Any

import pytest

import altair.vegalite.v6 as alt

if TYPE_CHECKING:
    from collections.abc import MutableMapping

    from altair.utils.data import SupportsGeoInterface


def geom_obj(geom: dict[str, Any]) -> SupportsGeoInterface:
    class Geom:
        __geo_interface__: MutableMapping[str, Any]

    geom_obj = Geom()
    geom_obj.__geo_interface__ = geom
    return geom_obj


# correct translation of Polygon geometry to Feature type
def test_geo_interface_polygon_feature() -> None:
    geom: dict[str, Any] = {
        "coordinates": [[(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)]],
        "type": "Polygon",
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"]["type"] == "Feature"


# merge geometry with empty properties dictionary
def test_geo_interface_removal_empty_properties() -> None:
    geom = {
        "geometry": {
            "coordinates": [
                [[6.90, 53.48], [5.98, 51.85], [6.07, 53.51], [6.90, 53.48]]
            ],
            "type": "Polygon",
        },
        "id": None,
        "properties": {},
        "type": "Feature",
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"]["type"] == "Feature"


# only register metadata in the properties member
def test_geo_interface_register_foreign_member() -> None:
    geom = {
        "geometry": {
            "coordinates": [
                [[6.90, 53.48], [5.98, 51.85], [6.07, 53.51], [6.90, 53.48]]
            ],
            "type": "Polygon",
        },
        "id": 2,
        "properties": {"foo": "bah"},
        "type": "Feature",
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    with pytest.raises(KeyError):
        spec["data"]["values"]["id"]
    assert spec["data"]["values"]["foo"] == "bah"


# correct serializing of arrays and nested tuples
def test_geo_interface_serializing_arrays_tuples() -> None:
    import array as arr

    geom = {
        "bbox": arr.array("d", [1, 2, 3, 4]),
        "geometry": {
            "coordinates": [
                (
                    (6.90, 53.48),
                    (5.98, 51.85),
                    (6.07, 53.51),
                    (6.90, 53.48),
                )
            ],
            "type": "Polygon",
        },
        "id": 27,
        "properties": {},
        "type": "Feature",
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"]["geometry"]["coordinates"][0][0] == [6.9, 53.48]


# overwrite existing 'type' value in properties with `Feature`
def test_geo_interface_reserved_members() -> None:
    geom = {
        "geometry": {
            "coordinates": [
                [[6.90, 53.48], [5.98, 51.85], [6.07, 53.51], [6.90, 53.48]]
            ],
            "type": "Polygon",
        },
        "id": 27,
        "properties": {"type": "foo"},
        "type": "Feature",
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"]["type"] == "Feature"


# an empty FeatureCollection is valid
def test_geo_interface_empty_feature_collection() -> None:
    geom = {"type": "FeatureCollection", "features": []}
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"] == []


# Features in a FeatureCollection only keep properties and geometry
def test_geo_interface_feature_collection() -> None:
    geom = {
        "type": "FeatureCollection",
        "features": [
            {
                "geometry": {
                    "coordinates": [
                        [[6.90, 53.48], [5.98, 51.85], [6.07, 53.51], [6.90, 53.48]]
                    ],
                    "type": "Polygon",
                },
                "id": 27,
                "properties": {"type": "foo", "id": 1, "geometry": 1},
                "type": "Feature",
            },
            {
                "geometry": {
                    "coordinates": [
                        [[8.90, 53.48], [7.98, 51.85], [8.07, 53.51], [8.90, 53.48]]
                    ],
                    "type": "Polygon",
                },
                "id": 28,
                "properties": {"type": "foo", "id": 2, "geometry": 1},
                "type": "Feature",
            },
        ],
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"][0]["id"] == 1
    assert spec["data"]["values"][1]["id"] == 2
    assert "coordinates" in spec["data"]["values"][0]["geometry"]
    assert "coordinates" in spec["data"]["values"][1]["geometry"]
    assert spec["data"]["values"][0]["type"] == "Feature"
    assert spec["data"]["values"][1]["type"] == "Feature"


# typical output of a __geo_interface__ from geopandas GeoDataFrame
# notice that the index value is registered as a commonly used identifier
# with the name "id" (in this case 49). Similar to serialization of a
# pandas DataFrame is the index not included in the output
def test_geo_interface_feature_collection_gdf() -> None:
    geom = {
        "bbox": (19.89, -26.82, 29.43, -17.66),
        "features": [
            {
                "bbox": (19.89, -26.82, 29.43, -17.66),
                "geometry": {
                    "coordinates": [
                        [[6.90, 53.48], [5.98, 51.85], [6.07, 53.51], [6.90, 53.48]]
                    ],
                    "type": "Polygon",
                },
                "id": "49",
                "properties": {
                    "continent": "Africa",
                    "gdp_md_est": 35900.0,
                    "id": "BWA",
                    "iso_a3": "BWA",
                    "name": "Botswana",
                    "pop_est": 2214858,
                },
                "type": "Feature",
            }
        ],
        "type": "FeatureCollection",
    }
    feat = geom_obj(geom)

    with alt.data_transformers.enable(consolidate_datasets=False):
        spec = alt.Chart(feat).mark_geoshape().to_dict()
    assert spec["data"]["values"][0]["id"] == "BWA"
