"""
Unit tests for KonstructMessage normalization from Slack event payloads.

Tests CHAN-01: Channel Gateway normalizes messages from all channels into
unified KonstructMessage format.

These tests exercise normalization logic without requiring a live database.
"""

from __future__ import annotations

from datetime import datetime, timezone

import pytest

from shared.models.message import ChannelType, KonstructMessage, MessageContent, SenderInfo


def make_slack_event() -> dict:
    """Minimal valid Slack message event payload."""
    return {
        "type": "message",
        "channel": "C12345ABC",
        "user": "U98765XYZ",
        "text": "Hey @bot can you help me?",
        "ts": "1711234567.123456",
        "thread_ts": "1711234567.123456",
        "team": "T11223344",
        "blocks": [],
        "event_ts": "1711234567.123456",
    }


def normalize_slack_event(payload: dict) -> KonstructMessage:
    """
    Minimal normalizer for test purposes.

    The real normalizer lives in packages/gateway/channels/slack.py.
    This function mirrors the expected output for unit testing the model.
    """
    ts = float(payload["ts"])
    timestamp = datetime.fromtimestamp(ts, tz=timezone.utc)

    return KonstructMessage(
        channel=ChannelType.SLACK,
        channel_metadata={
            "workspace_id": payload["team"],
            "channel_id": payload["channel"],
            "event_ts": payload["event_ts"],
        },
        sender=SenderInfo(
            user_id=payload["user"],
            display_name=payload["user"],  # Display name resolved later by Slack API
        ),
        content=MessageContent(
            text=payload["text"],
            mentions=[u for u in payload["text"].split() if u.startswith("@")],
        ),
        timestamp=timestamp,
        thread_id=payload.get("thread_ts"),
        reply_to=None if payload.get("thread_ts") == payload.get("ts") else payload.get("thread_ts"),
    )


class TestKonstructMessageNormalization:
    """Tests for Slack event normalization to KonstructMessage."""

    def test_channel_type_is_slack(self) -> None:
        """ChannelType must be set to 'slack' for Slack events."""
        msg = normalize_slack_event(make_slack_event())
        assert msg.channel == ChannelType.SLACK
        assert msg.channel == "slack"

    def test_sender_info_extracted(self) -> None:
        """SenderInfo user_id must match Slack user field."""
        payload = make_slack_event()
        msg = normalize_slack_event(payload)
        assert msg.sender.user_id == "U98765XYZ"
        assert msg.sender.is_bot is False

    def test_content_text_preserved(self) -> None:
        """MessageContent.text must contain original Slack message text."""
        payload = make_slack_event()
        msg = normalize_slack_event(payload)
        assert msg.content.text == "Hey @bot can you help me?"

    def test_thread_id_from_thread_ts(self) -> None:
        """thread_id must be populated from Slack's thread_ts field."""
        payload = make_slack_event()
        payload["thread_ts"] = "1711234500.000001"  # Different from ts — it's a reply
        msg = normalize_slack_event(payload)
        assert msg.thread_id == "1711234500.000001"

    def test_thread_id_none_when_no_thread(self) -> None:
        """thread_id must be None when the message is not in a thread."""
        payload = make_slack_event()
        del payload["thread_ts"]
        msg = normalize_slack_event(payload)
        assert msg.thread_id is None

    def test_channel_metadata_contains_workspace_id(self) -> None:
        """channel_metadata must contain workspace_id (Slack team ID)."""
        payload = make_slack_event()
        msg = normalize_slack_event(payload)
        assert "workspace_id" in msg.channel_metadata
        assert msg.channel_metadata["workspace_id"] == "T11223344"

    def test_channel_metadata_contains_channel_id(self) -> None:
        """channel_metadata must contain the Slack channel ID."""
        payload = make_slack_event()
        msg = normalize_slack_event(payload)
        assert msg.channel_metadata["channel_id"] == "C12345ABC"

    def test_tenant_id_is_none_before_resolution(self) -> None:
        """tenant_id must be None immediately after normalization (Router populates it)."""
        msg = normalize_slack_event(make_slack_event())
        assert msg.tenant_id is None

    def test_message_has_uuid_id(self) -> None:
        """KonstructMessage must have a UUID id assigned at construction."""
        import uuid

        msg = normalize_slack_event(make_slack_event())
        # Should not raise
        parsed = uuid.UUID(msg.id)
        assert str(parsed) == msg.id

    def test_timestamp_is_utc_datetime(self) -> None:
        """timestamp must be a timezone-aware datetime in UTC."""
        msg = normalize_slack_event(make_slack_event())
        assert msg.timestamp.tzinfo is not None
        assert msg.timestamp.tzinfo == timezone.utc

    def test_pydantic_validation_rejects_invalid_channel(self) -> None:
        """KonstructMessage must reject unknown ChannelType values."""
        with pytest.raises(Exception):  # pydantic.ValidationError
            KonstructMessage(
                channel="fax_machine",  # type: ignore[arg-type]
                channel_metadata={},
                sender=SenderInfo(user_id="u1", display_name="User"),
                content=MessageContent(text="hello"),
                timestamp=datetime.now(timezone.utc),
            )
