# tests/schemas/syndarix/test_issue_schemas.py
"""
Tests for Issue schema validation.
"""

import uuid

import pytest
from pydantic import ValidationError

from app.schemas.syndarix import (
    IssueAssign,
    IssueCreate,
    IssuePriority,
    IssueStatus,
    IssueUpdate,
    SyncStatus,
)


class TestIssueCreateValidation:
    """Tests for IssueCreate schema validation."""

    def test_valid_issue_create(self, valid_issue_data):
        """Test creating issue with valid data."""
        issue = IssueCreate(**valid_issue_data)

        assert issue.title == "Test Issue"
        assert issue.body == "Issue description"

    def test_issue_create_defaults(self, valid_issue_data):
        """Test that defaults are applied correctly."""
        issue = IssueCreate(**valid_issue_data)

        assert issue.status == IssueStatus.OPEN
        assert issue.priority == IssuePriority.MEDIUM
        assert issue.labels == []
        assert issue.story_points is None
        assert issue.assigned_agent_id is None
        assert issue.human_assignee is None
        assert issue.sprint_id is None

    def test_issue_create_with_all_fields(self, valid_uuid):
        """Test creating issue with all optional fields."""
        agent_id = uuid.uuid4()
        sprint_id = uuid.uuid4()

        issue = IssueCreate(
            project_id=valid_uuid,
            title="Full Issue",
            body="Detailed body",
            status=IssueStatus.IN_PROGRESS,
            priority=IssuePriority.HIGH,
            labels=["bug", "security"],
            story_points=5,
            assigned_agent_id=agent_id,
            sprint_id=sprint_id,
            external_tracker_type="gitea",
            external_issue_id="gitea-123",
            remote_url="https://gitea.example.com/issues/123",
            external_issue_number=123,
        )

        assert issue.status == IssueStatus.IN_PROGRESS
        assert issue.priority == IssuePriority.HIGH
        assert issue.labels == ["bug", "security"]
        assert issue.story_points == 5
        assert issue.external_tracker_type == "gitea"

    def test_issue_create_title_empty_fails(self, valid_uuid):
        """Test that empty title raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            IssueCreate(
                project_id=valid_uuid,
                title="",
            )

        errors = exc_info.value.errors()
        assert any("title" in str(e) for e in errors)

    def test_issue_create_title_whitespace_only_fails(self, valid_uuid):
        """Test that whitespace-only title raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            IssueCreate(
                project_id=valid_uuid,
                title="   ",
            )

        errors = exc_info.value.errors()
        assert any("title" in str(e) for e in errors)

    def test_issue_create_title_stripped(self, valid_uuid):
        """Test that title is stripped."""
        issue = IssueCreate(
            project_id=valid_uuid,
            title="  Padded Title  ",
        )

        assert issue.title == "Padded Title"

    def test_issue_create_project_id_required(self):
        """Test that project_id is required."""
        with pytest.raises(ValidationError) as exc_info:
            IssueCreate(title="No Project Issue")

        errors = exc_info.value.errors()
        assert any("project_id" in str(e).lower() for e in errors)


class TestIssueLabelsValidation:
    """Tests for Issue labels validation."""

    def test_labels_normalized_lowercase(self, valid_uuid):
        """Test that labels are normalized to lowercase."""
        issue = IssueCreate(
            project_id=valid_uuid,
            title="Test Issue",
            labels=["Bug", "SECURITY", "FrontEnd"],
        )

        assert issue.labels == ["bug", "security", "frontend"]

    def test_labels_stripped(self, valid_uuid):
        """Test that labels are stripped."""
        issue = IssueCreate(
            project_id=valid_uuid,
            title="Test Issue",
            labels=["  bug  ", "  security  "],
        )

        assert issue.labels == ["bug", "security"]

    def test_labels_empty_strings_removed(self, valid_uuid):
        """Test that empty label strings are removed."""
        issue = IssueCreate(
            project_id=valid_uuid,
            title="Test Issue",
            labels=["bug", "", "  ", "security"],
        )

        assert issue.labels == ["bug", "security"]


class TestIssueStoryPointsValidation:
    """Tests for Issue story_points validation."""

    def test_story_points_valid_range(self, valid_uuid):
        """Test valid story_points values."""
        for points in [0, 1, 5, 13, 21, 100]:
            issue = IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                story_points=points,
            )
            assert issue.story_points == points

    def test_story_points_negative_fails(self, valid_uuid):
        """Test that negative story_points raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                story_points=-1,
            )

        errors = exc_info.value.errors()
        assert any("story_points" in str(e).lower() for e in errors)

    def test_story_points_over_100_fails(self, valid_uuid):
        """Test that story_points > 100 raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                story_points=101,
            )

        errors = exc_info.value.errors()
        assert any("story_points" in str(e).lower() for e in errors)


class TestIssueExternalTrackerValidation:
    """Tests for Issue external tracker validation."""

    def test_valid_external_trackers(self, valid_uuid):
        """Test valid external tracker values."""
        for tracker in ["gitea", "github", "gitlab"]:
            issue = IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                external_tracker_type=tracker,
                external_issue_id="ext-123",
            )
            assert issue.external_tracker_type == tracker

    def test_invalid_external_tracker(self, valid_uuid):
        """Test that invalid external tracker raises ValidationError."""
        with pytest.raises(ValidationError):
            IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                external_tracker_type="invalid",  # type: ignore
                external_issue_id="ext-123",
            )


class TestIssueUpdateValidation:
    """Tests for IssueUpdate schema validation."""

    def test_issue_update_partial(self):
        """Test updating only some fields."""
        update = IssueUpdate(
            title="Updated Title",
        )

        assert update.title == "Updated Title"
        assert update.body is None
        assert update.status is None

    def test_issue_update_all_fields(self):
        """Test updating all fields."""
        agent_id = uuid.uuid4()
        sprint_id = uuid.uuid4()

        update = IssueUpdate(
            title="Updated Title",
            body="Updated body",
            status=IssueStatus.CLOSED,
            priority=IssuePriority.CRITICAL,
            labels=["updated"],
            assigned_agent_id=agent_id,
            human_assignee=None,
            sprint_id=sprint_id,
            story_points=8,
            sync_status=SyncStatus.PENDING,
        )

        assert update.title == "Updated Title"
        assert update.status == IssueStatus.CLOSED
        assert update.priority == IssuePriority.CRITICAL

    def test_issue_update_empty_title_fails(self):
        """Test that empty title in update raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            IssueUpdate(title="")

        errors = exc_info.value.errors()
        assert any("title" in str(e) for e in errors)

    def test_issue_update_labels_normalized(self):
        """Test that labels are normalized in updates."""
        update = IssueUpdate(
            labels=["Bug", "SECURITY"],
        )

        assert update.labels == ["bug", "security"]


class TestIssueAssignValidation:
    """Tests for IssueAssign schema validation."""

    def test_assign_to_agent(self):
        """Test assigning to an agent."""
        agent_id = uuid.uuid4()
        assign = IssueAssign(assigned_agent_id=agent_id)

        assert assign.assigned_agent_id == agent_id
        assert assign.human_assignee is None

    def test_assign_to_human(self):
        """Test assigning to a human."""
        assign = IssueAssign(human_assignee="developer@example.com")

        assert assign.human_assignee == "developer@example.com"
        assert assign.assigned_agent_id is None

    def test_unassign(self):
        """Test unassigning (both None)."""
        assign = IssueAssign()

        assert assign.assigned_agent_id is None
        assert assign.human_assignee is None

    def test_assign_both_fails(self):
        """Test that assigning to both agent and human raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            IssueAssign(
                assigned_agent_id=uuid.uuid4(),
                human_assignee="developer@example.com",
            )

        errors = exc_info.value.errors()
        # Check for the validation error message
        assert len(errors) > 0


class TestIssueEnums:
    """Tests for Issue enum validation."""

    def test_valid_issue_statuses(self, valid_uuid):
        """Test all valid issue statuses."""
        for status in IssueStatus:
            issue = IssueCreate(
                project_id=valid_uuid,
                title=f"Issue {status.value}",
                status=status,
            )
            assert issue.status == status

    def test_invalid_issue_status(self, valid_uuid):
        """Test that invalid issue status raises ValidationError."""
        with pytest.raises(ValidationError):
            IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                status="invalid",  # type: ignore
            )

    def test_valid_issue_priorities(self, valid_uuid):
        """Test all valid issue priorities."""
        for priority in IssuePriority:
            issue = IssueCreate(
                project_id=valid_uuid,
                title=f"Issue {priority.value}",
                priority=priority,
            )
            assert issue.priority == priority

    def test_invalid_issue_priority(self, valid_uuid):
        """Test that invalid issue priority raises ValidationError."""
        with pytest.raises(ValidationError):
            IssueCreate(
                project_id=valid_uuid,
                title="Test Issue",
                priority="invalid",  # type: ignore
            )

    def test_valid_sync_statuses(self):
        """Test all valid sync statuses in update."""
        for status in SyncStatus:
            update = IssueUpdate(sync_status=status)
            assert update.sync_status == status
