import polars as pl
import pytest


def _make_lff(rows):
    return pl.LazyFrame(rows)


def test_compute_considerations_stats_basic():
    from src.figures import compute_considerations_stats

    lff = _make_lff(
        [
            # u1 : social oui (Clause), env non (Sans objet)
            {
                "uid": "u1",
                "considerationsSociales": "Clause sociale",
                "considerationsEnvironnementales": "Sans objet",
            },
            # u2 : social non (Sans objet), env oui (Critère)
            {
                "uid": "u2",
                "considerationsSociales": "Sans objet",
                "considerationsEnvironnementales": "Critère environnemental",
            },
            # u3 : social oui (Marché réservé), env null
            {
                "uid": "u3",
                "considerationsSociales": "Marché réservé",
                "considerationsEnvironnementales": None,
            },
            # u4 : social autre valeur (pas "Sans objet"), env null
            {
                "uid": "u4",
                "considerationsSociales": "Pas de considération sociale",
                "considerationsEnvironnementales": "Sans objet",
            },
        ]
    )

    stats = compute_considerations_stats(lff)

    # champs_renseignes : basé sur sociales. 4 non-null / 4 total -> (4, 100%).
    assert stats["champs_renseignes"] == (4, 100)
    # Sociales renseignées : dén=4 non-null, num=3 != "Sans objet" (u1/u3/u4) -> (4, 3, 75%).
    assert stats["sociales_renseignees"] == (4, 3, 75)
    # Env renseignées : dén=3 non-null (u1/u2/u4), num=1 != "Sans objet" (u2) -> (3, 1, 33%).
    assert stats["environnementales_renseignees"] == (3, 1, 33)


def test_compute_considerations_stats_dedup_per_uid():
    from src.figures import compute_considerations_stats

    lff = _make_lff(
        [
            # u1 présent 2 fois (2 titulaires) -> compté une seule fois
            {
                "uid": "u1",
                "considerationsSociales": "Clause sociale",
                "considerationsEnvironnementales": "Sans objet",
            },
            {
                "uid": "u1",
                "considerationsSociales": "Clause sociale",
                "considerationsEnvironnementales": "Sans objet",
            },
            {
                "uid": "u2",
                "considerationsSociales": "Sans objet",
                "considerationsEnvironnementales": "Sans objet",
            },
        ]
    )

    stats = compute_considerations_stats(lff)

    # 2 uid distincts. Social 2 non-null, 1 != "Sans objet" (u1) -> (2, 1, 50%).
    assert stats["champs_renseignes"] == (2, 100)
    assert stats["sociales_renseignees"] == (2, 1, 50)
    # Env 2 non-null, 0 != "Sans objet" -> (2, 0, 0%).
    assert stats["environnementales_renseignees"] == (2, 0, 0)


def test_compute_considerations_stats_missing_column():
    from src.figures import compute_considerations_stats

    lff = _make_lff(
        [
            {"uid": "u1", "considerationsSociales": "Clause sociale"},
            {"uid": "u2", "considerationsSociales": "Sans objet"},
        ]
    )

    stats = compute_considerations_stats(lff)

    # Colonne env absente -> (0, 0). Social : 2 non-null, 1 != "Sans objet" -> (2, 1, 50%).
    assert stats["champs_renseignes"] == (2, 100)
    assert stats["sociales_renseignees"] == (2, 1, 50)
    assert stats["environnementales_renseignees"] == (0, 0)


def test_compute_considerations_stats_empty():
    from src.figures import compute_considerations_stats

    lff = pl.LazyFrame(
        {
            "uid": pl.Series([], dtype=pl.String),
            "considerationsSociales": pl.Series([], dtype=pl.String),
            "considerationsEnvironnementales": pl.Series([], dtype=pl.String),
        }
    )

    stats = compute_considerations_stats(lff)

    assert stats["champs_renseignes"] == (0, 0)
    assert stats["sociales_renseignees"] == (0, 0)
    assert stats["environnementales_renseignees"] == (0, 0)


def test_get_considerations_card_content_returns_three_progress_bars():
    import dash_bootstrap_components as dbc
    from dash import html

    from src.figures import get_considerations_card_content

    lff = pl.LazyFrame(
        [
            {
                "uid": "u1",
                "considerationsSociales": "Clause sociale",
                "considerationsEnvironnementales": "Sans objet",
            },
            {
                "uid": "u2",
                "considerationsSociales": "Sans objet",
                "considerationsEnvironnementales": "Critère environnemental",
            },
        ]
    )

    div = get_considerations_card_content(lff)

    assert isinstance(div, html.Div)

    def find_progress(component, found):
        if isinstance(component, dbc.Progress):
            found.append(component)
        children = getattr(component, "children", None)
        if isinstance(children, (list, tuple)):
            for c in children:
                find_progress(c, found)
        elif children is not None and not isinstance(children, str):
            find_progress(children, found)
        return found

    inner_bars = [b for b in find_progress(div, []) if getattr(b, "bar", False)]
    assert len(inner_bars) == 3

    bar_ren, bar_social, bar_env = inner_bars
    # Bar 1 : champs renseignés (2/2 = 100%, gris)
    assert bar_ren.value == 100
    assert bar_ren.color == "#6c757d"
    # Bar 2 : sociales parmi renseignés (u1 != "Sans objet" -> 1/2 = 50%, rose)
    assert bar_social.value == 50
    assert bar_social.color == "#CC6677"
    # Bar 3 : env parmi renseignés (u2 != "Sans objet" -> 1/2 = 50%, vert)
    assert bar_env.value == 50
    assert bar_env.color == "#117733"


def test_build_org_markers_groups_and_counts():
    from src.figures import build_org_markers

    lff = pl.LazyFrame(
        [
            {
                "uid": "u1",
                "acheteur_longitude": 2.35,
                "acheteur_latitude": 48.85,
                "acheteur_nom": "ACHETEUR A",
            },
            {
                "uid": "u2",
                "acheteur_longitude": 2.35,
                "acheteur_latitude": 48.85,
                "acheteur_nom": "ACHETEUR A",
            },
            {
                "uid": "u3",
                "acheteur_longitude": -1.68,
                "acheteur_latitude": 48.11,
                "acheteur_nom": "ACHETEUR B",
            },
        ]
    )

    markers = build_org_markers(lff, "acheteur")

    assert len(markers) == 2
    marker_a = next(m for m in markers if m["tooltip"].startswith("ACHETEUR A"))
    assert marker_a["tooltip"] == "ACHETEUR A (2 marchés)"
    assert marker_a["lat"] == 48.85
    assert marker_a["lon"] == 2.35
    assert marker_a["marker_color"] == "#E69F00"


def test_build_org_markers_filters_null_coordinates():
    from src.figures import build_org_markers

    lff = pl.LazyFrame(
        [
            {
                "uid": "u1",
                "acheteur_longitude": None,
                "acheteur_latitude": None,
                "acheteur_nom": "ACHETEUR A",
            },
            {
                "uid": "u2",
                "acheteur_longitude": 2.35,
                "acheteur_latitude": 48.85,
                "acheteur_nom": "ACHETEUR B",
            },
        ]
    )

    markers = build_org_markers(lff, "acheteur")

    assert len(markers) == 1
    assert markers[0]["tooltip"] == "ACHETEUR B (1 marchés)"


def test_build_org_markers_missing_columns_returns_empty():
    from src.figures import build_org_markers

    lff = pl.LazyFrame([{"uid": "u1", "acheteur_nom": "ACHETEUR A"}])

    assert build_org_markers(lff, "acheteur") == []
    assert build_org_markers(lff, "titulaire") == []


def _org_map_dff():
    return pl.DataFrame(
        [
            {
                "uid": "u1",
                "acheteur_longitude": 2.35,
                "acheteur_latitude": 48.85,
                "acheteur_nom": "ACHETEUR A",
                "titulaire_longitude": -1.68,
                "titulaire_latitude": 48.11,
                "titulaire_nom": "TITULAIRE A",
            }
        ]
    )


def test_get_org_location_map_centers_on_home_and_counterpart():
    import dash_leaflet as dl

    from src.figures import get_org_location_map

    leaflet_map = get_org_location_map(_org_map_dff(), "acheteur", "test-map")

    assert isinstance(leaflet_map, dl.Map)
    # Centre = milieu du point acheteur (2.35, 48.85) et titulaire (-1.68, 48.11).
    assert leaflet_map.center == pytest.approx([48.48, 0.335])
    assert leaflet_map.zoom == 6

    geojson_layers = [c for c in leaflet_map.children if isinstance(c, dl.GeoJSON)]
    assert {layer.id for layer in geojson_layers} == {
        "test-map-acheteur",
        "test-map-titulaire",
    }


def test_get_org_location_map_marks_home_org_marker_acheteur():
    import dash_leaflet as dl

    from src.figures import get_org_location_map

    leaflet_map = get_org_location_map(_org_map_dff(), "acheteur", "test-map")

    geojson_layers = {
        layer.id: layer
        for layer in leaflet_map.children
        if isinstance(layer, dl.GeoJSON)
    }
    acheteur_features = geojson_layers["test-map-acheteur"].data["features"]
    titulaire_features = geojson_layers["test-map-titulaire"].data["features"]

    assert all(f["properties"]["is_home"] for f in acheteur_features)
    assert all(not f["properties"].get("is_home") for f in titulaire_features)


def test_get_org_location_map_marks_home_org_marker_titulaire():
    import dash_leaflet as dl

    from src.figures import get_org_location_map

    leaflet_map = get_org_location_map(_org_map_dff(), "titulaire", "test-map")

    geojson_layers = {
        layer.id: layer
        for layer in leaflet_map.children
        if isinstance(layer, dl.GeoJSON)
    }
    acheteur_features = geojson_layers["test-map-acheteur"].data["features"]
    titulaire_features = geojson_layers["test-map-titulaire"].data["features"]

    assert all(f["properties"]["is_home"] for f in titulaire_features)
    assert all(not f["properties"].get("is_home") for f in acheteur_features)


def test_get_org_location_map_defaults_to_france_view_without_coordinates():
    from src.figures import get_org_location_map

    dff = pl.DataFrame(
        [{"uid": "u1", "acheteur_nom": "ACHETEUR A", "titulaire_nom": "TITULAIRE A"}]
    )

    leaflet_map = get_org_location_map(dff, "acheteur", "test-map")

    assert leaflet_map.center == [46.6, 2.2]
    assert leaflet_map.zoom == 5


def test_bounds_to_center_zoom_single_point_uses_max_zoom():
    from src.figures import bounds_to_center_zoom

    center, zoom = bounds_to_center_zoom(48.85, 2.35, 48.85, 2.35, max_zoom=12)

    assert center == [48.85, 2.35]
    assert zoom == 12


def test_bounds_to_center_zoom_wider_bounds_give_smaller_zoom():
    from src.figures import bounds_to_center_zoom

    _, zoom_narrow = bounds_to_center_zoom(48.11, -1.68, 48.85, 2.35)
    _, zoom_wide = bounds_to_center_zoom(16.23, -61.55, 51.0, 8.0)

    assert zoom_wide < zoom_narrow


def test_bounds_to_center_zoom_center_is_bbox_midpoint():
    from src.figures import bounds_to_center_zoom

    center, _ = bounds_to_center_zoom(40.0, 0.0, 50.0, 4.0)

    assert center == [45.0, 2.0]
