"""Unit tests for HA bot command helpers — Phase 2.

Focus on the security-sensitive bits the reviewer flagged: attribute
filtering, error-message redaction, and the sample-context shape that
flows through Jinja preview rendering.
"""

from __future__ import annotations

from notify_bridge_server.commands.home_assistant_handler import (
    _filter_attributes,
    _is_sensitive_attr,
    _normalize_state,
)


def test_filter_attributes_drops_credential_keys() -> None:
    """HA camera entities expose an ``access_token`` attribute. The handler
    MUST NOT surface it to the chat user via /state."""
    raw = {
        "friendly_name": "Front Camera",
        "access_token": "real-camera-proxy-token",
        "entity_picture": "/api/camera_proxy/...?token=abc",
        "brightness": 200,
    }
    safe, hidden = _filter_attributes(raw)
    assert "access_token" not in safe
    # entity_picture contains 'token' substring → blocked.
    assert "entity_picture" not in safe
    # friendly_name is rendered as a top-level field, not iterated.
    assert "friendly_name" not in safe
    # brightness is a normal attribute, passes through.
    assert safe["brightness"] == 200
    assert hidden == 2


def test_filter_attributes_caps_count() -> None:
    """When an entity has dozens of attributes the renderer would overflow
    Telegram's 4096-char message limit. Cap at 30 with overflow surfaced."""
    raw = {f"attr_{i:03d}": i for i in range(50)}
    safe, hidden = _filter_attributes(raw)
    assert len(safe) == 30
    assert hidden == 20


def test_is_sensitive_attr_case_insensitive() -> None:
    """Match should not depend on key casing — custom integrations are
    inconsistent about capitalization."""
    assert _is_sensitive_attr("Access_Token") is True
    assert _is_sensitive_attr("API_KEY") is True
    assert _is_sensitive_attr("password") is True
    assert _is_sensitive_attr("brightness") is False
    assert _is_sensitive_attr("color_mode") is False


def test_normalize_state_filters_attrs() -> None:
    """End-to-end: feed _normalize_state a malicious state row, verify the
    output has redacted attributes + hidden_attr_count surfaced."""
    state_row = {
        "entity_id": "camera.front_door",
        "state": "idle",
        "attributes": {
            "friendly_name": "Front Door Camera",
            "access_token": "leaked",
            "brand": "Reolink",
        },
        "last_changed": "2026-05-13T12:00:00+00:00",
        "last_updated": "2026-05-13T12:00:00+00:00",
    }
    out = _normalize_state(state_row)
    assert out["entity_id"] == "camera.front_door"
    assert out["friendly_name"] == "Front Door Camera"
    assert out["domain"] == "camera"
    # Top-level fields preserved.
    assert out["state"] == "idle"
    # Attributes dict is filtered.
    assert "access_token" not in out["attributes"]
    assert out["attributes"].get("brand") == "Reolink"
    # Hidden count reflects access_token (friendly_name is top-level, not redacted).
    assert out["hidden_attr_count"] == 1


def test_normalize_state_handles_missing_attributes() -> None:
    """A state row with no attributes dict should not crash."""
    out = _normalize_state({"entity_id": "sensor.x", "state": "1"})
    assert out["attributes"] == {}
    assert out["hidden_attr_count"] == 0


def test_redact_ha_message_strips_userinfo() -> None:
    """The Phase 1 redact helper is re-exported via the HA package and used
    by /entities, /state, /areas before surfacing errors. Make sure the
    re-export still works and the contract is what we expect."""
    from notify_bridge_core.providers.home_assistant import redact_ha_message
    msg = "Cannot connect to https://leak-token@homeassistant.local:8123/api/websocket"
    out = redact_ha_message(msg)
    assert "leak-token@" not in out
    assert "homeassistant.local:8123" in out
