"""Tests for messages app models."""

import pytest
from django.db import IntegrityError
from django.utils import timezone

from .factories import MessageFactory, SignalFactory, TagFactory, ThreadFactory


@pytest.mark.django_db
class TestTag:
    """Tests for the Tag model."""

    def test_create_tag(self):
        """Test creating a tag with the factory."""
        tag = TagFactory()
        assert tag.pk is not None
        assert tag.name.startswith("tag-")
        assert tag.workspace is not None

    def test_tag_str(self):
        """Test tag string representation."""
        tag = TagFactory(name="feature-request")
        assert str(tag) == "feature-request"

    def test_tag_unique_per_workspace(self):
        """Test that tag name must be unique within a workspace."""
        tag = TagFactory(name="unique-tag")
        with pytest.raises(IntegrityError):
            TagFactory(workspace=tag.workspace, name="unique-tag")

    def test_tag_same_name_different_workspace(self):
        """Test same tag name in different workspaces is allowed."""
        tag1 = TagFactory(name="common")
        tag2 = TagFactory(name="common")
        assert tag1.workspace != tag2.workspace
        assert tag1.name == tag2.name

    def test_tag_has_timestamps(self):
        """Test that tag has created_at and updated_at."""
        tag = TagFactory()
        assert tag.created_at is not None
        assert tag.updated_at is not None


@pytest.mark.django_db
class TestThread:
    """Tests for the Thread model."""

    def test_create_thread(self):
        """Test creating a thread with the factory."""
        thread = ThreadFactory()
        assert thread.pk is not None
        assert thread.title.startswith("Thread")
        assert thread.workspace is not None
        assert thread.source is not None

    def test_thread_str(self):
        """Test thread string representation."""
        thread = ThreadFactory(title="Bug: Login fails")
        assert str(thread) == "Bug: Login fails"

    def test_thread_default_status(self):
        """Test thread default status is open."""
        thread = ThreadFactory()
        assert thread.status == "open"

    def test_thread_status_choices(self):
        """Test thread with different status choices."""
        statuses = ["open", "triaged", "closed"]
        for status in statuses:
            thread = ThreadFactory(status=status)
            assert thread.status == status

    def test_thread_unique_source_external_id(self):
        """Test that source + external_id must be unique."""
        thread = ThreadFactory(external_id="issue-123")
        with pytest.raises(IntegrityError):
            ThreadFactory(source=thread.source, external_id="issue-123")

    def test_thread_with_tags(self):
        """Test thread can have tags."""
        thread = ThreadFactory()
        tag1 = TagFactory(workspace=thread.workspace)
        tag2 = TagFactory(workspace=thread.workspace)
        thread.tags.add(tag1, tag2)
        assert thread.tags.count() == 2

    def test_thread_with_metadata(self):
        """Test thread with custom metadata."""
        metadata = {"github": {"state": "open", "labels": ["bug"]}}
        thread = ThreadFactory(metadata=metadata)
        assert thread.metadata == metadata

    def test_thread_default_metadata(self):
        """Test thread has empty dict as default metadata."""
        thread = ThreadFactory()
        assert thread.metadata == {}

    def test_thread_activity_at(self):
        """Test thread activity_at can be set."""
        now = timezone.now()
        thread = ThreadFactory(activity_at=now)
        assert thread.activity_at is not None

    def test_thread_has_timestamps(self):
        """Test that thread has created_at and updated_at."""
        thread = ThreadFactory()
        assert thread.created_at is not None
        assert thread.updated_at is not None


@pytest.mark.django_db
class TestMessage:
    """Tests for the Message model."""

    def test_create_message(self):
        """Test creating a message with the factory."""
        message = MessageFactory()
        assert message.pk is not None
        assert message.content.startswith("Message content")
        assert message.thread is not None

    def test_message_str(self):
        """Test message string representation."""
        message = MessageFactory(content="This is a long message that should be truncated")
        expected = f"{message.thread}: This is a long message that sh"
        assert str(message) == expected

    def test_message_with_author(self):
        """Test message with author."""
        message = MessageFactory()
        assert message.author is not None

    def test_message_without_author(self):
        """Test message can be created without author."""
        message = MessageFactory(author=None)
        assert message.author is None
        assert message.pk is not None

    def test_message_with_external_id(self):
        """Test message with external_id."""
        message = MessageFactory(external_id="comment-123")
        assert message.external_id == "comment-123"

    def test_message_without_external_id(self):
        """Test message without external_id."""
        message = MessageFactory(external_id="")
        assert message.external_id == ""

    def test_message_with_metadata(self):
        """Test message with custom metadata."""
        metadata = {"github": {"reactions": ["+1", "heart"]}}
        message = MessageFactory(metadata=metadata)
        assert message.metadata == metadata

    def test_message_default_metadata(self):
        """Test message has empty dict as default metadata."""
        message = MessageFactory()
        assert message.metadata == {}

    def test_message_sent_at(self):
        """Test message sent_at is set."""
        message = MessageFactory()
        assert message.sent_at is not None
        assert message.sent_at <= timezone.now()

    def test_message_has_timestamps(self):
        """Test that message has created_at and updated_at."""
        message = MessageFactory()
        assert message.created_at is not None
        assert message.updated_at is not None

    def test_message_belongs_to_thread(self):
        """Test message belongs to a thread."""
        message = MessageFactory()
        assert message.thread is not None
        assert message in message.thread.messages.all()


@pytest.mark.django_db
class TestSignal:
    """Tests for the Signal model."""

    def test_create_signal(self):
        """Test creating a signal with the factory."""
        signal = SignalFactory()
        assert signal.pk is not None
        assert signal.title.startswith("Signal")
        assert signal.workspace is not None
        assert signal.source is not None

    def test_signal_str(self):
        """Test signal string representation."""
        signal = SignalFactory(signal_type="star", title="User starred repo")
        assert str(signal) == "star: User starred repo"

    def test_signal_type_choices(self):
        """Test signal with different type choices."""
        signal_types = ["mention", "star", "fork", "release", "follow"]
        for signal_type in signal_types:
            signal = SignalFactory(signal_type=signal_type)
            assert signal.signal_type == signal_type

    def test_signal_with_actor(self):
        """Test signal with actor."""
        signal = SignalFactory()
        assert signal.actor is not None

    def test_signal_without_actor(self):
        """Test signal can be created without actor."""
        signal = SignalFactory(actor=None)
        assert signal.actor is None
        assert signal.pk is not None

    def test_signal_with_metadata(self):
        """Test signal with custom metadata."""
        metadata = {"github": {"starred_at": "2026-01-19T10:00:00Z", "repo_full_name": "org/repo"}}
        signal = SignalFactory(metadata=metadata)
        assert signal.metadata == metadata

    def test_signal_default_metadata(self):
        """Test signal has empty dict as default metadata."""
        signal = SignalFactory()
        assert signal.metadata == {}

    def test_signal_has_timestamps(self):
        """Test that signal has created_at and updated_at."""
        signal = SignalFactory()
        assert signal.created_at is not None
        assert signal.updated_at is not None

    def test_signal_github_accessor_with_metadata(self):
        """Test github typed accessor returns metadata when present."""
        metadata = {"github": {"starred_at": "2026-01-19T10:00:00Z", "repo_full_name": "org/repo"}}
        signal = SignalFactory(metadata=metadata)
        assert signal.github is not None
        assert signal.github["starred_at"] == "2026-01-19T10:00:00Z"

    def test_signal_github_accessor_without_metadata(self):
        """Test github typed accessor returns None when not present."""
        signal = SignalFactory(metadata={})
        assert signal.github is None


@pytest.mark.django_db
class TestThreadTypedAccessors:
    """Tests for Thread typed accessor properties."""

    def test_thread_default_thread_type(self):
        """Test thread default thread_type is conversation."""
        thread = ThreadFactory()
        assert thread.thread_type == "conversation"

    def test_thread_type_choices(self):
        """Test thread with different thread_type choices."""
        for thread_type in ["conversation", "post"]:
            thread = ThreadFactory(thread_type=thread_type)
            assert thread.thread_type == thread_type

    def test_thread_github_accessor_with_metadata(self):
        """Test github typed accessor returns metadata when present."""
        metadata = {
            "github": {
                "type": "issue",
                "number": 123,
                "state": "open",
                "html_url": "https://github.com/org/repo/issues/123",
                "labels": [],
                "author": {"login": "user", "id": 1},
            }
        }
        thread = ThreadFactory(metadata=metadata)
        assert thread.github is not None
        assert thread.github["number"] == 123
        assert thread.github["type"] == "issue"

    def test_thread_github_accessor_without_metadata(self):
        """Test github typed accessor returns None when not present."""
        thread = ThreadFactory(metadata={})
        assert thread.github is None
