from __future__ import annotations

from dataclasses import dataclass

import pytest

from entropic_core.core.entropy_monitor import EntropyMonitor


@dataclass
class Agent:
    last_decision: str = "unknown"
    state: float = 0.0
    messages: list | None = None


def test_shannon_entropy_basic() -> None:
    m = EntropyMonitor()

    assert m._shannon_entropy([]) == 0.0
    assert m._shannon_entropy(["a", "a", "a"]) == 0.0
    assert m._shannon_entropy(["a", "b"]) == pytest.approx(1.0, abs=1e-9)


def test_dispersion_numeric_states() -> None:
    m = EntropyMonitor()
    assert m._calculate_dispersion([0.0, 0.0, 0.0]) == pytest.approx(0.0, abs=1e-9)
    # std([0,1])=0.5 -> normalized min(std*2,1) == 1.0
    assert m._calculate_dispersion([0.0, 1.0]) >= 0.99


def test_communication_complexity() -> None:
    m = EntropyMonitor()
    assert m._communication_complexity([[], []]) == 0.0
    assert m._communication_complexity([[1] * 50, [1] * 50]) == pytest.approx(1.0, abs=1e-9)


def test_measure_system_entropy_shape_and_history() -> None:
    m = EntropyMonitor()
    agents = [
        Agent(last_decision="a", state=0.0, messages=["x"] * 10),
        Agent(last_decision="b", state=1.0, messages=["y"] * 5),
    ]
    res = m.measure_system_entropy(agents)
    assert set(res.keys()) >= {"decision", "dispersion", "communication", "combined", "timestamp"}
    assert 0.0 <= res["decision"] <= 1.0
    assert 0.0 <= res["dispersion"] <= 1.0
    assert 0.0 <= res["communication"] <= 1.0
    assert 0.0 <= res["combined"] <= 1.0
    assert m.last_measurement == res
    assert len(m.get_history()) == 1


def test_trend_insufficient_and_stable() -> None:
    m = EntropyMonitor()
    assert m.get_trend() == "insufficient_data"
    m.metrics_history = [{"combined": 0.5}, {"combined": 0.51}, {"combined": 0.50}]
    assert m.get_trend() == "stable"


def test_no_signal_is_flagged_and_neutral() -> None:
    m = EntropyMonitor()

    class Empty:
        pass

    res = m.measure_system_entropy([Empty(), Empty()])
    assert res["signal"]["signal_ok"] is False
    assert "no_signal" in res["signal"]["issues"]

    # Neutral defaults to avoid the dangerous "0.0 == perfectly stable" interpretation.
    assert res["decision"] == pytest.approx(0.5, abs=1e-9)
    assert res["dispersion"] == pytest.approx(0.5, abs=1e-9)
    assert res["communication"] == pytest.approx(0.5, abs=1e-9)
    assert res["combined"] == pytest.approx(0.5, abs=1e-9)
    assert 0.0 <= res["health"]["score"] <= 1.0


def test_baseline_per_workload_and_zscores() -> None:
    m = EntropyMonitor()

    # Build a baseline (stable workload): two agents, fixed decisions, fixed messages.
    for _ in range(12):
        agents = [
            Agent(last_decision="a", state=0.0, messages=["x"] * 2),
            Agent(last_decision="a", state=0.0, messages=["x"] * 2),
        ]
        m.measure_system_entropy(agents, workload_id="w1")

    res = m.measure_system_entropy(
        [
            Agent(last_decision="a", state=0.0, messages=["x"] * 2),
            Agent(last_decision="b", state=1.0, messages=["x"] * 50),
        ],
        workload_id="w1",
    )

    assert res["baseline"]["ready"] is True
    assert res["baseline"]["n"] >= 10
    assert set(res["z"].keys()) >= {"decision", "dispersion", "communication", "combined"}
    # With a deviation introduced, at least one z-score should move away from 0.
    assert any(abs(float(v)) > 0.0 for v in res["z"].values())
