"""
Unit tests for Issue domain models.

Tests pure business logic with no external dependencies.
"""

import pytest
from datetime import datetime, timedelta, timezone

from domain.issues.models import Issue, Label, IssueState, LabelCategories
from domain.issues.exceptions import IssueStateError


class TestLabel:
    """Test Label value object."""

    def test_label_creation(self):
        # Arrange & Act
        label = Label(name="bug", color="#ff0000", description="Bug label")

        # Assert
        assert label.name == "bug"
        assert label.color == "#ff0000"
        assert label.description == "Bug label"

    def test_is_state_label(self):
        # Arrange
        state_label = Label("status:in-progress")
        regular_label = Label("bug")

        # Act & Assert
        assert state_label.is_state_label() is True
        assert regular_label.is_state_label() is False

    def test_is_priority_label(self):
        # Arrange
        priority_label = Label("priority:high")
        regular_label = Label("bug")

        # Act & Assert
        assert priority_label.is_priority_label() is True
        assert regular_label.is_priority_label() is False

    def test_is_type_label(self):
        # Arrange
        type_label = Label("bug")
        priority_label = Label("priority:high")

        # Act & Assert
        assert type_label.is_type_label() is True
        assert priority_label.is_type_label() is False

    @pytest.mark.parametrize("label_name,expected", [
        ("bug", True),
        ("enhancement", True),
        ("feature", True),
        ("documentation", True),
        ("custom-label", False),
        ("priority:high", False)
    ])
    def test_type_label_recognition(self, label_name, expected):
        # Arrange
        label = Label(label_name)

        # Act & Assert
        assert label.is_type_label() == expected


class TestIssue:
    """Test Issue aggregate root."""

    def test_issue_creation_with_valid_data(self):
        # Arrange
        created_at = datetime.now(timezone.utc)
        updated_at = datetime.now(timezone.utc)
        labels = [Label("bug"), Label("priority:high")]

        # Act
        issue = Issue(
            number=123,
            title="Test Issue",
            state=IssueState.OPEN,
            labels=labels,
            created_at=created_at,
            updated_at=updated_at
        )

        # Assert
        assert issue.number == 123
        assert issue.title == "Test Issue"
        assert issue.state == IssueState.OPEN
        assert len(issue.labels) == 2
        assert issue.created_at == created_at
        assert issue.updated_at == updated_at

    def test_categorize_labels_correctly_separates_types(self):
        # Arrange
        labels = [
            Label("bug"),                # type label
            Label("priority:high"),      # priority label
            Label("status:in-progress"), # state label
            Label("documentation"),      # type label
            Label("custom-label")        # other label
        ]
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=labels,
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act
        categories = issue.categorize_labels()

        # Assert
        assert "bug" in categories.type_labels
        assert "documentation" in categories.type_labels
        assert "priority:high" in categories.priority_labels
        assert "status:in-progress" in categories.state_labels
        assert "custom-label" in categories.other_labels

    def test_close_issue_changes_state_and_sets_closed_at(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act
        issue.close()

        # Assert
        assert issue.state == IssueState.CLOSED
        assert issue.closed_at is not None
        assert isinstance(issue.closed_at, datetime)

    def test_close_already_closed_issue_raises_error(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.CLOSED,
            labels=[],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc),
            closed_at=datetime.now(timezone.utc)
        )

        # Act & Assert
        with pytest.raises(IssueStateError) as exc_info:
            issue.close()

        assert "Issue is already closed" in str(exc_info.value)
        assert exc_info.value.current_state == "closed"
        assert exc_info.value.attempted_state == "closed"

    def test_reopen_closed_issue_changes_state_and_clears_closed_at(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.CLOSED,
            labels=[],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc),
            closed_at=datetime.now(timezone.utc)
        )

        # Act
        issue.reopen()

        # Assert
        assert issue.state == IssueState.OPEN
        assert issue.closed_at is None

    def test_reopen_open_issue_raises_error(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act & Assert
        with pytest.raises(IssueStateError) as exc_info:
            issue.reopen()

        assert "Issue is not closed" in str(exc_info.value)

    def test_add_label_to_issue(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[Label("bug")],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )
        new_label = Label("priority:high")

        # Act
        issue.add_label(new_label)

        # Assert
        assert len(issue.labels) == 2
        assert new_label in issue.labels

    def test_add_duplicate_label_does_not_duplicate(self):
        # Arrange
        label = Label("bug")
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[label],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act
        issue.add_label(label)

        # Assert
        assert len(issue.labels) == 1

    def test_remove_label_from_issue(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[Label("bug"), Label("priority:high")],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act
        issue.remove_label("bug")

        # Assert
        assert len(issue.labels) == 1
        assert not any(label.name == "bug" for label in issue.labels)

    def test_has_label_returns_correct_value(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[Label("bug"), Label("priority:high")],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act & Assert
        assert issue.has_label("bug") is True
        assert issue.has_label("priority:high") is True
        assert issue.has_label("enhancement") is False


class TestLabelCategories:
    """Test LabelCategories value object."""

    def test_label_categories_creation(self):
        # Arrange & Act
        categories = LabelCategories(
            state_labels=["status:open"],
            priority_labels=["priority:high"],
            type_labels=["bug"],
            other_labels=["custom"]
        )

        # Assert
        assert categories.state_labels == ["status:open"]
        assert categories.priority_labels == ["priority:high"]
        assert categories.type_labels == ["bug"]
        assert categories.other_labels == ["custom"]