"""
Unit tests for Issue domain services.

Tests business logic in issue services with no external dependencies.
"""

import pytest
from datetime import datetime, timedelta, timezone

from domain.issues.models import Issue, Label, IssueState
from domain.issues.services import IssueStatusService, IssueValidationService
from domain.issues.exceptions import IssueValidationError


class TestIssueStatusService:
    """Test business logic in issue status service."""

    @pytest.fixture
    def service(self):
        return IssueStatusService()

    def test_determine_kanban_column_for_closed_issue(self, service):
        # Arrange
        issue = Issue(
            number=1,
            title="Closed Issue",
            state=IssueState.CLOSED,
            labels=[],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )
        project_info = {"kanban_columns": ["Todo", "In Progress", "Review", "Done"]}

        # Act
        column = service.determine_kanban_column(issue, project_info)

        # Assert
        assert column == "Done"

    @pytest.mark.parametrize("status_label,expected_column", [
        ("status:in-progress", "In Progress"),
        ("status:review", "Review"),
        ("status:blocked", "Blocked"),
        ("status:ready", "Ready"),
    ])
    def test_determine_kanban_column_based_on_status_labels(self, service, status_label, expected_column):
        # Arrange
        issue = Issue(
            number=1,
            title="Test Issue",
            state=IssueState.OPEN,
            labels=[Label(status_label)],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )
        project_info = {"kanban_columns": ["Todo", "In Progress", "Review", "Blocked", "Ready", "Done"]}

        # Act
        column = service.determine_kanban_column(issue, project_info)

        # Assert
        assert column == expected_column

    def test_determine_kanban_column_defaults_to_todo(self, service):
        # Arrange
        issue = Issue(
            number=1,
            title="New Issue",
            state=IssueState.OPEN,
            labels=[Label("bug")],  # No status label
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )
        project_info = {"kanban_columns": ["Todo", "In Progress", "Done"]}

        # Act
        column = service.determine_kanban_column(issue, project_info)

        # Assert
        assert column == "Todo"

    @pytest.mark.parametrize("priority_label,expected_level", [
        ("priority:low", "Low"),
        ("priority:medium", "Medium"),
        ("priority:high", "High"),
        ("priority:critical", "Critical"),
    ])
    def test_extract_priority_info_with_priority_labels(self, service, priority_label, expected_level):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[Label(priority_label)],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act
        priority_info = service.extract_priority_info(issue)

        # Assert
        assert priority_info["level"] == expected_level
        assert priority_info["label"] == priority_label

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

        # Act
        priority_info = service.extract_priority_info(issue)

        # Assert
        assert priority_info["level"] == "Medium"
        assert priority_info["label"] is None

    def test_extract_state_info_for_open_issue(self, service):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            state=IssueState.OPEN,
            labels=[Label("status:in-progress")],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc)
        )

        # Act
        state_info = service.extract_state_info(issue)

        # Assert
        assert state_info["state"] == "open"
        assert state_info["state_labels"] == ["status:in-progress"]
        assert state_info["is_closed"] is False
        assert state_info["closed_at"] is None

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

        # Act
        state_info = service.extract_state_info(issue)

        # Assert
        assert state_info["state"] == "closed"
        assert state_info["is_closed"] is True
        assert state_info["closed_at"] == closed_at.isoformat()

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

        # Act
        age_days = service.calculate_issue_age_days(issue)

        # Assert
        assert age_days == 5

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

        # Act
        is_stale = service.is_stale_issue(issue, stale_threshold_days=30)

        # Assert
        assert is_stale is True

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

        # Act
        is_stale = service.is_stale_issue(issue, stale_threshold_days=30)

        # Assert
        assert is_stale is False

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

        # Act
        is_stale = service.is_stale_issue(issue, stale_threshold_days=30)

        # Assert
        assert is_stale is False


class TestIssueValidationService:
    """Test business logic in issue validation service."""

    @pytest.fixture
    def service(self):
        return IssueValidationService()

    def test_validate_issue_creation_with_valid_data(self, service):
        # Arrange
        title = "Valid Issue Title"
        labels = ["bug", "priority:high"]

        # Act & Assert - Should not raise exception
        service.validate_issue_creation(title, labels)

    def test_validate_issue_creation_with_empty_title_raises_error(self, service):
        # Arrange
        title = ""
        labels = ["bug"]

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_issue_creation(title, labels)

        assert "Issue title cannot be empty" in str(exc_info.value)
        assert exc_info.value.field == "title"

    def test_validate_issue_creation_with_whitespace_only_title_raises_error(self, service):
        # Arrange
        title = "   "
        labels = ["bug"]

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_issue_creation(title, labels)

        assert "Issue title cannot be empty" in str(exc_info.value)

    def test_validate_issue_creation_with_too_long_title_raises_error(self, service):
        # Arrange
        title = "x" * 256  # Too long
        labels = ["bug"]

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_issue_creation(title, labels)

        assert "Issue title cannot exceed 255 characters" in str(exc_info.value)

    def test_validate_issue_creation_with_multiple_priority_labels_raises_error(self, service):
        # Arrange
        title = "Valid Title"
        labels = ["bug", "priority:high", "priority:low"]

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_issue_creation(title, labels)

        assert "Issue cannot have multiple priority labels" in str(exc_info.value)
        assert exc_info.value.field == "labels"

    def test_validate_issue_creation_with_multiple_state_labels_raises_error(self, service):
        # Arrange
        title = "Valid Title"
        labels = ["bug", "status:open", "status:in-progress"]

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_issue_creation(title, labels)

        assert "Issue cannot have multiple state labels" in str(exc_info.value)

    def test_validate_title_update_with_valid_title(self, service):
        # Arrange
        new_title = "Updated Title"

        # Act & Assert - Should not raise exception
        service.validate_title_update(new_title)

    def test_validate_label_addition_to_issue_without_conflicts(self, service):
        # 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 = "enhancement"

        # Act & Assert - Should not raise exception
        service.validate_label_addition(issue, new_label)

    def test_validate_label_addition_with_duplicate_label_raises_error(self, service):
        # 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 = "bug"

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_label_addition(issue, new_label)

        assert "Issue already has label 'bug'" in str(exc_info.value)

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

        # Act & Assert
        with pytest.raises(IssueValidationError) as exc_info:
            service.validate_label_addition(issue, new_label)

        assert "Issue already has priority label" in str(exc_info.value)
        assert "Cannot add 'priority:low'" in str(exc_info.value)