"""Tests for Phase 2.5b per-channel-type renderers."""

import json
import re
import time

import pytest

from meshai.notifications.events import NotificationPayload, make_event, make_payload_from_event
from meshai.notifications.renderers import MeshRenderer, EmailRenderer, WebhookRenderer


# ============================================================
# MESH RENDERER TESTS
# ============================================================

def test_mesh_render_short_message_single_chunk():
    """Short message produces a single chunk."""
    payload = NotificationPayload(
        message="Test alert",
        category="test",
        severity="routine",
        timestamp=time.time(),
    )
    renderer = MeshRenderer()
    chunks = renderer.render(payload)
    assert len(chunks) == 1
    assert "Test alert" in chunks[0]


def test_mesh_render_passes_message_verbatim():
    """v0.5.7-regression: MeshRenderer no longer prepends '[<Family>] '.
    The composer (compose_mesh_message) is the single source of truth for
    mesh formatting since v0.5.2 -- its output already starts with the
    family emoji + label (e.g. '🚨 ROADS:'). The renderer used to wrap
    that with '[Roads] ' producing the visually-broken duplicate
    '[Roads] 🚨 ROADS: ...' that hit the live mesh during the v0.5.7
    staged flip. Now the renderer is a verbatim pass-through."""
    payload = NotificationPayload(
        message="Severe storm",
        category="weather_warning",
        severity="priority",
        timestamp=time.time(),
        event_type="weather_warning",
    )
    renderer = MeshRenderer()
    chunks = renderer.render(payload)
    assert len(chunks) == 1
    assert chunks[0] == "Severe storm", chunks[0]
    assert not chunks[0].startswith("["), chunks[0]


def test_mesh_render_unknown_event_type_no_prefix():
    """v0.5.7-regression: same verbatim pass-through behavior regardless of
    whether the event_type is in the registry."""
    payload = NotificationPayload(
        message="Hello",
        category="made_up_thing",
        severity="routine",
        timestamp=time.time(),
        event_type="made_up_thing",
    )
    renderer = MeshRenderer()
    chunks = renderer.render(payload)
    assert len(chunks) == 1
    assert chunks[0] == "Hello"
    assert not chunks[0].startswith("[")


def test_mesh_render_long_message_chunks():
    """Long message splits into multiple chunks with counters."""
    # Build a ~500 char message
    long_message = "This is a very long alert message that should definitely exceed the two hundred character limit. " * 5
    payload = NotificationPayload(
        message=long_message,
        category="weather_warning",
        severity="priority",
        timestamp=time.time(),
        event_type="weather_warning",
    )
    renderer = MeshRenderer()
    chunks = renderer.render(payload)

    assert len(chunks) >= 2
    for chunk in chunks:
        assert len(chunk) <= 200, f"Chunk exceeds limit: {len(chunk)} chars"
    # Check each chunk ends with "(k/N)" counter
    for chunk in chunks:
        assert re.search(r"\(\d+/\d+\)$", chunk), f"Missing counter: {chunk}"


def test_mesh_render_chunks_preserve_full_content():
    """Chunking preserves all words from the original message."""
    # Build a message with distinct tokens
    words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
             "hotel", "india", "juliet", "kilo", "lima", "mike", "november",
             "oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform",
             "victor", "whiskey", "xray", "yankee", "zulu"]
    long_message = " ".join(words * 3)  # Repeat to span multiple chunks

    payload = NotificationPayload(
        message=long_message,
        category="test",
        severity="routine",
        timestamp=time.time(),
    )
    renderer = MeshRenderer()
    chunks = renderer.render(payload)

    # Join all chunks and remove counters
    combined = " ".join(chunks)
    combined = re.sub(r"\s*\(\d+/\d+\)", "", combined)

    # Every original word should appear
    for word in words:
        assert word in combined, f"Missing word: {word}"


def test_mesh_render_no_event_type():
    """Payload without event_type has no prefix."""
    payload = NotificationPayload(
        message="Plain message",
        category="test",
        severity="routine",
        timestamp=time.time(),
        event_type=None,
    )
    renderer = MeshRenderer()
    chunks = renderer.render(payload)
    assert len(chunks) == 1
    assert chunks[0] == "Plain message"


# ============================================================
# EMAIL RENDERER TESTS
# ============================================================

def test_email_render_subject_includes_severity_and_type():
    """Subject line includes severity and event type."""
    payload = NotificationPayload(
        message="Storm approaching",
        category="weather_warning",
        severity="immediate",
        timestamp=time.time(),
        event_type="weather_warning",
    )
    renderer = EmailRenderer()
    rendered = renderer.render(payload)

    assert "IMMEDIATE" in rendered["subject"]
    assert "Weather Warning" in rendered["subject"]


def test_email_render_body_includes_message_and_context():
    """Body includes message and structured context fields."""
    fixed_time = 1700000000.0  # Known timestamp
    payload = NotificationPayload(
        message="Test alert message",
        category="battery_warning",
        severity="priority",
        timestamp=fixed_time,
        event_type="battery_warning",
        region="Magic Valley",
        node_name="BLD-MTN",
    )
    renderer = EmailRenderer()
    rendered = renderer.render(payload)
    body = rendered["body"]

    assert "Test alert message" in body
    assert "Magic Valley" in body
    assert "BLD-MTN" in body
    assert "priority" in body
    assert "battery_warning" in body
    # Check formatted date
    assert "2023-11-14" in body  # Date part of timestamp


def test_email_render_omits_missing_context():
    """Body omits lines for missing optional fields."""
    payload = NotificationPayload(
        message="Minimal alert",
        category="test",
        severity="routine",
        timestamp=time.time(),
        # No region, no node, no event_type
    )
    renderer = EmailRenderer()
    rendered = renderer.render(payload)
    body = rendered["body"]

    assert "Minimal alert" in body
    assert "Severity: routine" in body
    assert "Region:" not in body
    assert "Node:" not in body
    assert "Category:" not in body


def test_email_render_includes_source_event():
    """Body includes source event details when present."""
    event = make_event(
        source="weather_adapter",
        category="weather_warning",
        severity="priority",
        title="Severe Storm",
        summary="Severe storm expected",
    )
    payload = make_payload_from_event(event)

    renderer = EmailRenderer()
    rendered = renderer.render(payload)
    body = rendered["body"]

    assert "weather_adapter" in body
    assert "Severe Storm" in body


# ============================================================
# WEBHOOK RENDERER TESTS
# ============================================================

def test_webhook_render_has_schema_version():
    """Output includes schema_version field."""
    payload = NotificationPayload(
        message="Test",
        category="test",
        severity="routine",
        timestamp=time.time(),
    )
    renderer = WebhookRenderer()
    rendered = renderer.render(payload)

    assert rendered["schema_version"] == "1.0"


def test_webhook_render_omits_none_fields():
    """None optional fields are omitted, not set to null."""
    payload = NotificationPayload(
        message="Test message",
        category="test",
        severity="routine",
        timestamp=time.time(),
        # All optional fields default to None
    )
    renderer = WebhookRenderer()
    rendered = renderer.render(payload)

    # Required fields present
    assert "message" in rendered
    assert "severity" in rendered
    assert "timestamp" in rendered
    assert "schema_version" in rendered

    # Optional fields omitted
    assert "node_id" not in rendered
    assert "region" not in rendered
    assert "chunk_index" not in rendered


def test_webhook_render_includes_source_event_when_present():
    """source_event dict included when payload has source event."""
    event = make_event(
        source="test_adapter",
        category="test",
        severity="routine",
        title="Test Event",
    )
    payload = make_payload_from_event(event)

    renderer = WebhookRenderer()
    rendered = renderer.render(payload)

    assert "source_event" in rendered
    assert rendered["source_event"]["id"] == event.id
    assert rendered["source_event"]["source"] == "test_adapter"


def test_webhook_render_is_json_serializable():
    """Rendered output is JSON-serializable (no datetime objects)."""
    event = make_event(
        source="test",
        category="test",
        severity="routine",
        title="Test",
    )
    payload = make_payload_from_event(event)
    payload.chunk_index = 1
    payload.chunk_total = 3
    payload.region = "Test Region"
    payload.node_name = "Test-Node"

    renderer = WebhookRenderer()
    rendered = renderer.render(payload)

    # Should not raise
    json_str = json.dumps(rendered)
    assert isinstance(json_str, str)
    # Verify round-trip
    parsed = json.loads(json_str)
    assert parsed["message"] == payload.message


def test_webhook_render_includes_optional_fields_when_set():
    """Optional fields included when they have values."""
    payload = NotificationPayload(
        message="Test",
        category="test_category",
        severity="priority",
        timestamp=time.time(),
        event_type="battery_warning",
        node_id="!abc123",
        node_name="Test-Node",
        region="Test Region",
        chunk_index=2,
        chunk_total=5,
    )
    renderer = WebhookRenderer()
    rendered = renderer.render(payload)

    assert rendered["category"] == "test_category"
    assert rendered["event_type"] == "battery_warning"
    assert rendered["node_id"] == "!abc123"
    assert rendered["node_name"] == "Test-Node"
    assert rendered["region"] == "Test Region"
    assert rendered["chunk_index"] == 2
    assert rendered["chunk_total"] == 5
