# tests/schemas/syndarix/test_sprint_schemas.py
"""
Tests for Sprint schema validation.
"""

from datetime import date, timedelta

import pytest
from pydantic import ValidationError

from app.schemas.syndarix import (
    SprintCreate,
    SprintStatus,
    SprintUpdate,
)


class TestSprintCreateValidation:
    """Tests for SprintCreate schema validation."""

    def test_valid_sprint_create(self, valid_sprint_data):
        """Test creating sprint with valid data."""
        sprint = SprintCreate(**valid_sprint_data)

        assert sprint.name == "Sprint 1"
        assert sprint.number == 1
        assert sprint.start_date is not None
        assert sprint.end_date is not None

    def test_sprint_create_defaults(self, valid_sprint_data):
        """Test that defaults are applied correctly."""
        sprint = SprintCreate(**valid_sprint_data)

        assert sprint.status == SprintStatus.PLANNED
        assert sprint.goal is None
        assert sprint.planned_points is None
        assert sprint.velocity is None

    def test_sprint_create_with_all_fields(self, valid_uuid):
        """Test creating sprint with all optional fields."""
        today = date.today()

        sprint = SprintCreate(
            project_id=valid_uuid,
            name="Full Sprint",
            number=5,
            goal="Complete all features",
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.PLANNED,
            planned_points=21,
            velocity=0,
        )

        assert sprint.name == "Full Sprint"
        assert sprint.number == 5
        assert sprint.goal == "Complete all features"
        assert sprint.planned_points == 21

    def test_sprint_create_name_empty_fails(self, valid_uuid):
        """Test that empty name raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
            )

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

    def test_sprint_create_name_whitespace_only_fails(self, valid_uuid):
        """Test that whitespace-only name raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="   ",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
            )

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

    def test_sprint_create_name_stripped(self, valid_uuid):
        """Test that name is stripped."""
        today = date.today()

        sprint = SprintCreate(
            project_id=valid_uuid,
            name="  Padded Sprint Name  ",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
        )

        assert sprint.name == "Padded Sprint Name"

    def test_sprint_create_project_id_required(self):
        """Test that project_id is required."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                name="Sprint 1",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
            )

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


class TestSprintNumberValidation:
    """Tests for Sprint number validation."""

    def test_sprint_number_valid(self, valid_uuid):
        """Test valid sprint numbers."""
        today = date.today()

        for number in [1, 10, 100]:
            sprint = SprintCreate(
                project_id=valid_uuid,
                name=f"Sprint {number}",
                number=number,
                start_date=today,
                end_date=today + timedelta(days=14),
            )
            assert sprint.number == number

    def test_sprint_number_zero_fails(self, valid_uuid):
        """Test that sprint number 0 raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="Sprint Zero",
                number=0,
                start_date=today,
                end_date=today + timedelta(days=14),
            )

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

    def test_sprint_number_negative_fails(self, valid_uuid):
        """Test that negative sprint number raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="Negative Sprint",
                number=-1,
                start_date=today,
                end_date=today + timedelta(days=14),
            )

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


class TestSprintDateValidation:
    """Tests for Sprint date validation."""

    def test_valid_date_range(self, valid_uuid):
        """Test valid date range (end > start)."""
        today = date.today()

        sprint = SprintCreate(
            project_id=valid_uuid,
            name="Sprint 1",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
        )

        assert sprint.end_date > sprint.start_date

    def test_same_day_sprint(self, valid_uuid):
        """Test that same day sprint is valid."""
        today = date.today()

        sprint = SprintCreate(
            project_id=valid_uuid,
            name="One Day Sprint",
            number=1,
            start_date=today,
            end_date=today,  # Same day is allowed
        )

        assert sprint.start_date == sprint.end_date

    def test_end_before_start_fails(self, valid_uuid):
        """Test that end date before start date raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="Invalid Sprint",
                number=1,
                start_date=today,
                end_date=today - timedelta(days=1),  # Before start
            )

        errors = exc_info.value.errors()
        assert len(errors) > 0


class TestSprintPointsValidation:
    """Tests for Sprint points validation."""

    def test_valid_planned_points(self, valid_uuid):
        """Test valid planned_points values."""
        today = date.today()

        for points in [0, 1, 21, 100]:
            sprint = SprintCreate(
                project_id=valid_uuid,
                name=f"Sprint {points}",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
                planned_points=points,
            )
            assert sprint.planned_points == points

    def test_planned_points_negative_fails(self, valid_uuid):
        """Test that negative planned_points raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="Negative Points Sprint",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
                planned_points=-1,
            )

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

    def test_valid_velocity(self, valid_uuid):
        """Test valid velocity values."""
        today = date.today()

        for points in [0, 5, 21]:
            sprint = SprintCreate(
                project_id=valid_uuid,
                name=f"Sprint {points}",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
                velocity=points,
            )
            assert sprint.velocity == points

    def test_velocity_negative_fails(self, valid_uuid):
        """Test that negative velocity raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError) as exc_info:
            SprintCreate(
                project_id=valid_uuid,
                name="Negative Velocity Sprint",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
                velocity=-1,
            )

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


class TestSprintUpdateValidation:
    """Tests for SprintUpdate schema validation."""

    def test_sprint_update_partial(self):
        """Test updating only some fields."""
        update = SprintUpdate(
            name="Updated Name",
        )

        assert update.name == "Updated Name"
        assert update.goal is None
        assert update.start_date is None
        assert update.end_date is None

    def test_sprint_update_all_fields(self):
        """Test updating all fields."""
        today = date.today()

        update = SprintUpdate(
            name="Updated Name",
            goal="Updated goal",
            start_date=today,
            end_date=today + timedelta(days=21),
            status=SprintStatus.ACTIVE,
            planned_points=34,
            velocity=20,
        )

        assert update.name == "Updated Name"
        assert update.goal == "Updated goal"
        assert update.status == SprintStatus.ACTIVE
        assert update.planned_points == 34

    def test_sprint_update_empty_name_fails(self):
        """Test that empty name in update raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            SprintUpdate(name="")

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

    def test_sprint_update_name_stripped(self):
        """Test that name is stripped in updates."""
        update = SprintUpdate(name="  Updated  ")

        assert update.name == "Updated"


class TestSprintStatusEnum:
    """Tests for SprintStatus enum validation."""

    def test_valid_sprint_statuses(self, valid_uuid):
        """Test all valid sprint statuses."""
        today = date.today()

        for status in SprintStatus:
            sprint = SprintCreate(
                project_id=valid_uuid,
                name=f"Sprint {status.value}",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
                status=status,
            )
            assert sprint.status == status

    def test_invalid_sprint_status(self, valid_uuid):
        """Test that invalid sprint status raises ValidationError."""
        today = date.today()

        with pytest.raises(ValidationError):
            SprintCreate(
                project_id=valid_uuid,
                name="Invalid Status Sprint",
                number=1,
                start_date=today,
                end_date=today + timedelta(days=14),
                status="invalid",  # type: ignore
            )
