# tests/models/syndarix/test_sprint.py
"""
Unit tests for the Sprint model.
"""

import uuid
from datetime import date, datetime, timedelta

import pytest

from app.models.syndarix import (
    Project,
    Sprint,
    SprintStatus,
)


class TestSprintModel:
    """Tests for Sprint model creation and fields."""

    def test_create_sprint_with_required_fields(self, db_session):
        """Test creating a sprint with only required fields."""
        project = Project(
            id=uuid.uuid4(),
            name="Sprint Project",
            slug="sprint-project",
        )
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Sprint 1",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Sprint 1").first()

        assert retrieved is not None
        assert retrieved.name == "Sprint 1"
        assert retrieved.number == 1
        assert retrieved.start_date == today
        assert retrieved.end_date == today + timedelta(days=14)
        assert retrieved.status == SprintStatus.PLANNED  # Default
        assert retrieved.goal is None
        assert retrieved.planned_points is None
        assert retrieved.velocity is None

    def test_create_sprint_with_all_fields(self, db_session):
        """Test creating a sprint with all optional fields."""
        project = Project(
            id=uuid.uuid4(),
            name="Full Sprint Project",
            slug="full-sprint-project",
        )
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint_id = uuid.uuid4()

        sprint = Sprint(
            id=sprint_id,
            project_id=project.id,
            name="Full Sprint",
            number=5,
            goal="Complete all authentication features",
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.ACTIVE,
            planned_points=34,
            velocity=21,
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(id=sprint_id).first()

        assert retrieved.name == "Full Sprint"
        assert retrieved.number == 5
        assert retrieved.goal == "Complete all authentication features"
        assert retrieved.status == SprintStatus.ACTIVE
        assert retrieved.planned_points == 34
        assert retrieved.velocity == 21

    def test_sprint_timestamps(self, db_session):
        """Test that timestamps are automatically set."""
        project = Project(id=uuid.uuid4(), name="Timestamp Sprint Project", slug="timestamp-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Timestamp Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
        )
        db_session.add(sprint)
        db_session.commit()

        assert isinstance(sprint.created_at, datetime)
        assert isinstance(sprint.updated_at, datetime)

    def test_sprint_string_representation(self, db_session):
        """Test the string representation of a sprint."""
        project = Project(id=uuid.uuid4(), name="Repr Sprint Project", slug="repr-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Sprint Alpha",
            number=3,
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.ACTIVE,
        )

        repr_str = repr(sprint)
        assert "Sprint Alpha" in repr_str
        assert "#3" in repr_str
        assert str(project.id) in repr_str
        assert "active" in repr_str


class TestSprintStatus:
    """Tests for Sprint status field."""

    def test_all_sprint_statuses(self, db_session):
        """Test that all sprint statuses can be stored."""
        project = Project(id=uuid.uuid4(), name="Status Sprint Project", slug="status-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        for idx, status in enumerate(SprintStatus):
            sprint = Sprint(
                id=uuid.uuid4(),
                project_id=project.id,
                name=f"Sprint {status.value}",
                number=idx + 1,
                start_date=today,
                end_date=today + timedelta(days=14),
                status=status,
            )
            db_session.add(sprint)
            db_session.commit()

            retrieved = db_session.query(Sprint).filter_by(id=sprint.id).first()
            assert retrieved.status == status


class TestSprintLifecycle:
    """Tests for Sprint lifecycle operations."""

    def test_start_sprint(self, db_session):
        """Test starting a planned sprint."""
        project = Project(id=uuid.uuid4(), name="Start Sprint Project", slug="start-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Sprint to Start",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.PLANNED,
        )
        db_session.add(sprint)
        db_session.commit()

        # Start the sprint
        sprint.status = SprintStatus.ACTIVE
        sprint.planned_points = 21
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Sprint to Start").first()
        assert retrieved.status == SprintStatus.ACTIVE
        assert retrieved.planned_points == 21

    def test_complete_sprint(self, db_session):
        """Test completing an active sprint."""
        project = Project(id=uuid.uuid4(), name="Complete Sprint Project", slug="complete-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Sprint to Complete",
            number=1,
            start_date=today - timedelta(days=14),
            end_date=today,
            status=SprintStatus.ACTIVE,
            planned_points=21,
        )
        db_session.add(sprint)
        db_session.commit()

        # Complete the sprint
        sprint.status = SprintStatus.COMPLETED
        sprint.velocity = 18
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Sprint to Complete").first()
        assert retrieved.status == SprintStatus.COMPLETED
        assert retrieved.velocity == 18

    def test_cancel_sprint(self, db_session):
        """Test cancelling a sprint."""
        project = Project(id=uuid.uuid4(), name="Cancel Sprint Project", slug="cancel-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Sprint to Cancel",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.ACTIVE,
            planned_points=21,
        )
        db_session.add(sprint)
        db_session.commit()

        # Cancel the sprint
        sprint.status = SprintStatus.CANCELLED
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Sprint to Cancel").first()
        assert retrieved.status == SprintStatus.CANCELLED


class TestSprintDates:
    """Tests for Sprint date fields."""

    def test_sprint_date_range(self, db_session):
        """Test storing sprint date range."""
        project = Project(id=uuid.uuid4(), name="Date Range Project", slug="date-range-project")
        db_session.add(project)
        db_session.commit()

        start = date(2024, 1, 1)
        end = date(2024, 1, 14)

        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Date Range Sprint",
            number=1,
            start_date=start,
            end_date=end,
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Date Range Sprint").first()
        assert retrieved.start_date == start
        assert retrieved.end_date == end

    def test_one_day_sprint(self, db_session):
        """Test creating a one-day sprint."""
        project = Project(id=uuid.uuid4(), name="One Day Project", slug="one-day-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="One Day Sprint",
            number=1,
            start_date=today,
            end_date=today,  # Same day
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="One Day Sprint").first()
        assert retrieved.start_date == retrieved.end_date

    def test_long_sprint(self, db_session):
        """Test creating a long sprint (e.g., 4 weeks)."""
        project = Project(id=uuid.uuid4(), name="Long Sprint Project", slug="long-sprint-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Long Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=28),  # 4 weeks
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Long Sprint").first()
        delta = retrieved.end_date - retrieved.start_date
        assert delta.days == 28


class TestSprintPoints:
    """Tests for Sprint story points fields."""

    def test_sprint_with_zero_points(self, db_session):
        """Test sprint with zero planned points."""
        project = Project(id=uuid.uuid4(), name="Zero Points Project", slug="zero-points-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Zero Points Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
            planned_points=0,
            velocity=0,
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Zero Points Sprint").first()
        assert retrieved.planned_points == 0
        assert retrieved.velocity == 0

    def test_sprint_velocity_calculation(self, db_session):
        """Test that we can calculate velocity from points."""
        project = Project(id=uuid.uuid4(), name="Velocity Project", slug="velocity-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Velocity Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.COMPLETED,
            planned_points=21,
            velocity=18,
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Velocity Sprint").first()

        # Calculate completion ratio from velocity
        completion_ratio = retrieved.velocity / retrieved.planned_points
        assert completion_ratio == pytest.approx(18 / 21, rel=0.01)

    def test_sprint_overdelivery(self, db_session):
        """Test sprint where completed > planned (stretch goals)."""
        project = Project(id=uuid.uuid4(), name="Overdelivery Project", slug="overdelivery-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Overdelivery Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
            status=SprintStatus.COMPLETED,
            planned_points=20,
            velocity=25,  # Completed more than planned
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Overdelivery Sprint").first()
        assert retrieved.velocity > retrieved.planned_points


class TestSprintNumber:
    """Tests for Sprint number field."""

    def test_sequential_sprint_numbers(self, db_session):
        """Test creating sprints with sequential numbers."""
        project = Project(id=uuid.uuid4(), name="Sequential Project", slug="sequential-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        for i in range(1, 6):
            sprint = Sprint(
                id=uuid.uuid4(),
                project_id=project.id,
                name=f"Sprint {i}",
                number=i,
                start_date=today + timedelta(days=(i - 1) * 14),
                end_date=today + timedelta(days=i * 14 - 1),
            )
            db_session.add(sprint)
            db_session.commit()

        sprints = db_session.query(Sprint).filter_by(project_id=project.id).order_by(Sprint.number).all()
        assert len(sprints) == 5
        for i, sprint in enumerate(sprints, 1):
            assert sprint.number == i

    def test_large_sprint_number(self, db_session):
        """Test sprint with large number (e.g., long-running project)."""
        project = Project(id=uuid.uuid4(), name="Large Number Project", slug="large-number-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Sprint 100",
            number=100,
            start_date=today,
            end_date=today + timedelta(days=14),
        )
        db_session.add(sprint)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Sprint 100").first()
        assert retrieved.number == 100


class TestSprintUpdate:
    """Tests for Sprint update operations."""

    def test_update_sprint_goal(self, db_session):
        """Test updating sprint goal."""
        project = Project(id=uuid.uuid4(), name="Update Goal Project", slug="update-goal-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Update Goal Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
            goal="Original goal",
        )
        db_session.add(sprint)
        db_session.commit()

        original_created_at = sprint.created_at

        sprint.goal = "Updated goal with more detail"
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Update Goal Sprint").first()
        assert retrieved.goal == "Updated goal with more detail"
        assert retrieved.created_at == original_created_at
        assert retrieved.updated_at > original_created_at

    def test_update_sprint_dates(self, db_session):
        """Test updating sprint dates."""
        project = Project(id=uuid.uuid4(), name="Update Dates Project", slug="update-dates-project")
        db_session.add(project)
        db_session.commit()

        today = date.today()
        sprint = Sprint(
            id=uuid.uuid4(),
            project_id=project.id,
            name="Update Dates Sprint",
            number=1,
            start_date=today,
            end_date=today + timedelta(days=14),
        )
        db_session.add(sprint)
        db_session.commit()

        # Extend sprint by a week
        sprint.end_date = today + timedelta(days=21)
        db_session.commit()

        retrieved = db_session.query(Sprint).filter_by(name="Update Dates Sprint").first()
        delta = retrieved.end_date - retrieved.start_date
        assert delta.days == 21
