"""
Test suite for Weekly Time Goals feature.
Tests model creation, calculations, relationships, routes, and business logic.
"""

import pytest
from datetime import datetime, timedelta, date
from app.models import WeeklyTimeGoal, TimeEntry, User, Project
from app import db
from factories import TimeEntryFactory


# ============================================================================
# WeeklyTimeGoal Model Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.models
@pytest.mark.smoke
def test_weekly_goal_creation(app, user):
    """Test basic weekly time goal creation."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        assert goal.id is not None
        assert goal.target_hours == 40.0
        assert goal.week_start_date == week_start
        assert goal.week_end_date == week_start + timedelta(days=6)
        assert goal.status == "active"
        assert goal.created_at is not None
        assert goal.updated_at is not None


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_default_week(app, user):
    """Test weekly goal creation with default week (current week)."""
    with app.app_context():
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
        db.session.add(goal)
        db.session.commit()

        # Should default to current week's Monday
        today = date.today()
        expected_week_start = today - timedelta(days=today.weekday())

        assert goal.week_start_date == expected_week_start
        assert goal.week_end_date == expected_week_start + timedelta(days=6)


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_with_notes(app, user):
    """Test weekly goal with notes."""
    with app.app_context():
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=35.0, notes="Vacation week, reduced hours")
        db.session.add(goal)
        db.session.commit()

        assert goal.notes == "Vacation week, reduced hours"


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_actual_hours_calculation(app, user, project):
    """Test calculation of actual hours worked."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        # Add time entries for the week
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=8),
            duration_seconds=8 * 3600,
        )
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start + timedelta(days=1), datetime.min.time()),
            end_time=datetime.combine(week_start + timedelta(days=1), datetime.min.time()) + timedelta(hours=7),
            duration_seconds=7 * 3600,
        )

        # Refresh goal to get calculated properties
        db.session.refresh(goal)

        assert goal.actual_hours == 15.0


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_progress_percentage(app, user, project):
    """Test progress percentage calculation."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        # Add time entry
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
            duration_seconds=20 * 3600,
        )

        db.session.refresh(goal)

        # 20 hours out of 40 = 50%
        assert goal.progress_percentage == 50.0


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_remaining_hours(app, user, project):
    """Test remaining hours calculation."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        # Add time entry
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=15),
            duration_seconds=15 * 3600,
        )

        db.session.refresh(goal)

        assert goal.remaining_hours == 25.0


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_is_completed(app, user, project):
    """Test is_completed property."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=20.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        db.session.refresh(goal)
        assert goal.is_completed is False

        # Add time entry to complete goal
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
            duration_seconds=20 * 3600,
        )

        db.session.refresh(goal)
        assert goal.is_completed is True


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_average_hours_per_day(app, user, project):
    """Test average hours per day calculation."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        # Add time entry for 10 hours
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=10),
            duration_seconds=10 * 3600,
        )

        db.session.refresh(goal)

        # Remaining: 30 hours, Days remaining: depends on current day
        if goal.days_remaining > 0:
            expected_avg = round(goal.remaining_hours / goal.days_remaining, 2)
            assert goal.average_hours_per_day == expected_avg


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_week_label(app, user):
    """Test week label generation."""
    with app.app_context():
        week_start = date(2024, 1, 1)  # A Monday
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        assert "Jan 01" in goal.week_label
        assert "Jan 07" in goal.week_label


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_status_update_completed(app, user, project):
    """Test automatic status update to completed."""
    with app.app_context():
        # Create goal for past week
        week_start = date.today() - timedelta(days=14)
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=20.0, week_start_date=week_start, status="active")
        db.session.add(goal)
        db.session.commit()

        # Add time entry to meet goal
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
            duration_seconds=20 * 3600,
        )

        goal.update_status()
        db.session.commit()

        assert goal.status == "completed"


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_status_update_failed(app, user, project):
    """Test automatic status update to failed."""
    with app.app_context():
        # Create goal for past week
        week_start = date.today() - timedelta(days=14)
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start, status="active")
        db.session.add(goal)
        db.session.commit()

        # Add time entry that doesn't meet goal
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
            duration_seconds=20 * 3600,
        )

        goal.update_status()
        db.session.commit()

        assert goal.status == "failed"


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_get_current_week(app, user):
    """Test getting current week's goal."""
    with app.app_context():
        # Create goal for current week
        today = date.today()
        week_start = today - timedelta(days=today.weekday())

        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=week_start)
        db.session.add(goal)
        db.session.commit()

        # Get current week goal
        current_goal = WeeklyTimeGoal.get_current_week_goal(user.id)

        assert current_goal is not None
        assert current_goal.id == goal.id


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_to_dict(app, user):
    """Test goal serialization to dictionary."""
    with app.app_context():
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, notes="Test notes")
        db.session.add(goal)
        db.session.commit()

        goal_dict = goal.to_dict()

        assert "id" in goal_dict
        assert "user_id" in goal_dict
        assert "target_hours" in goal_dict
        assert "actual_hours" in goal_dict
        assert "week_start_date" in goal_dict
        assert "week_end_date" in goal_dict
        assert "status" in goal_dict
        assert "notes" in goal_dict
        assert "progress_percentage" in goal_dict
        assert "remaining_hours" in goal_dict
        assert "is_completed" in goal_dict

        assert goal_dict["target_hours"] == 40.0
        assert goal_dict["notes"] == "Test notes"


# ============================================================================
# WeeklyTimeGoal Routes Tests
# ============================================================================


@pytest.mark.smoke
def test_weekly_goals_index_page(authenticated_client):
    """Test weekly goals index page loads."""
    response = authenticated_client.get("/goals")
    assert response.status_code == 200


@pytest.mark.smoke
def test_weekly_goals_create_page(authenticated_client):
    """Test weekly goals create page loads."""
    response = authenticated_client.get("/goals/create")
    assert response.status_code == 200


@pytest.mark.smoke
def test_create_weekly_goal_via_form(authenticated_client, app, user):
    """Test creating a weekly goal via form submission."""
    with app.app_context():
        data = {"target_hours": 40.0, "notes": "Test goal"}
        response = authenticated_client.post("/goals/create", data=data, follow_redirects=True)
        assert response.status_code == 200

        # Check goal was created
        goal = WeeklyTimeGoal.query.filter_by(user_id=user.id).first()
        assert goal is not None
        assert goal.target_hours == 40.0


@pytest.mark.smoke
def test_edit_weekly_goal(authenticated_client, app, user):
    """Test editing a weekly goal."""
    with app.app_context():
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
        db.session.add(goal)
        db.session.commit()
        goal_id = goal.id

    # Update goal (POST request happens outside app context)
    data = {"target_hours": 35.0, "notes": "Updated notes", "status": "active"}
    response = authenticated_client.post(f"/goals/{goal_id}/edit", data=data, follow_redirects=True)
    assert response.status_code == 200

    # Check goal was updated - re-query within app context
    with app.app_context():
        goal = WeeklyTimeGoal.query.get(goal_id)
        assert goal is not None, "Goal should still exist after edit"
        assert goal.target_hours == 35.0, f"Expected 35.0, got {goal.target_hours}"
        assert goal.notes == "Updated notes"


@pytest.mark.smoke
def test_delete_weekly_goal(authenticated_client, app, user):
    """Test deleting a weekly goal."""
    with app.app_context():
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
        db.session.add(goal)
        db.session.commit()
        goal_id = goal.id

    # Delete goal (POST request happens outside app context)
    response = authenticated_client.post(f"/goals/{goal_id}/delete", follow_redirects=True)
    assert response.status_code == 200

    # Check goal was deleted - re-query within app context
    with app.app_context():
        deleted_goal = WeeklyTimeGoal.query.get(goal_id)
        assert deleted_goal is None, f"Goal should be deleted but found: {deleted_goal}"


@pytest.mark.smoke
def test_view_weekly_goal(authenticated_client, app, user):
    """Test viewing a specific weekly goal."""
    # Create goal outside app context to ensure it persists
    goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
    with app.app_context():
        db.session.add(goal)
        db.session.commit()
        goal_id = goal.id

    response = authenticated_client.get(f"/goals/{goal_id}")
    assert response.status_code == 200


# ============================================================================
# API Endpoints Tests
# ============================================================================


@pytest.mark.smoke
def test_api_get_current_goal(authenticated_client, app, user):
    """Test API endpoint for getting current week's goal."""
    # Create goal outside app context to ensure it persists
    goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
    with app.app_context():
        db.session.add(goal)
        db.session.commit()

    response = authenticated_client.get("/api/goals/current")
    assert response.status_code == 200

    data = response.get_json()
    assert "target_hours" in data
    assert data["target_hours"] == 40.0


@pytest.mark.smoke
def test_api_list_goals(authenticated_client, app, user):
    """Test API endpoint for listing goals."""
    # Create multiple goals
    goals = []
    for i in range(3):
        goal = WeeklyTimeGoal(
            user_id=user.id,
            target_hours=40.0,
            week_start_date=date.today() - timedelta(weeks=i, days=date.today().weekday()),
        )
        goals.append(goal)

    with app.app_context():
        for goal in goals:
            db.session.add(goal)
        db.session.commit()

    response = authenticated_client.get("/api/goals")
    assert response.status_code == 200

    data = response.get_json()
    assert isinstance(data, list)
    assert len(data) == 3


@pytest.mark.smoke
def test_api_get_goal_stats(authenticated_client, app, user, project):
    """Test API endpoint for goal statistics."""
    # Create a few goals (their actual status will be determined by update_status)
    # Goal 1: Completed in the past with enough hours
    past_week_start = date.today() - timedelta(days=14)
    goal1 = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=past_week_start)

    # Goal 2: Active week
    current_week_start = date.today() - timedelta(days=date.today().weekday())
    goal2 = WeeklyTimeGoal(user_id=user.id, target_hours=40.0, week_start_date=current_week_start)

    with app.app_context():
        db.session.add(goal1)
        db.session.add(goal2)
        db.session.commit()

    response = authenticated_client.get("/api/goals/stats")
    assert response.status_code == 200

    data = response.get_json()
    # Verify the structure is correct
    assert "total_goals" in data
    assert "completed" in data
    assert "failed" in data
    assert "active" in data
    assert "completion_rate" in data
    assert data["total_goals"] == 2
    # Verify counts are consistent (completed + failed + active + cancelled should equal total)
    assert (
        data.get("completed", 0) + data.get("failed", 0) + data.get("active", 0) + data.get("cancelled", 0)
    ) == data["total_goals"]


@pytest.mark.unit
def test_weekly_goal_user_relationship(app, user):
    """Test weekly goal user relationship."""
    with app.app_context():
        goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
        db.session.add(goal)
        db.session.commit()

        db.session.refresh(goal)
        assert goal.user is not None
        assert goal.user.id == user.id


@pytest.mark.unit
def test_user_has_weekly_goals_relationship(app, user):
    """Test that user has weekly_goals relationship."""
    with app.app_context():
        # Re-query the user to ensure it's in the current session
        from app.models import User

        user_obj = User.query.get(user.id)

        goal1 = WeeklyTimeGoal(user_id=user_obj.id, target_hours=40.0)
        goal2 = WeeklyTimeGoal(
            user_id=user_obj.id,
            target_hours=35.0,
            week_start_date=date.today() - timedelta(weeks=1, days=date.today().weekday()),
        )
        db.session.add_all([goal1, goal2])
        db.session.commit()

        db.session.refresh(user_obj)
        assert user_obj.weekly_goals.count() >= 2


# ============================================================================
# WeeklyTimeGoal Exclude Weekends Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_exclude_weekends_creation(app, user):
    """Test weekly goal creation with exclude_weekends=True."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(
            user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
        )
        db.session.add(goal)
        db.session.commit()

        assert goal.exclude_weekends is True
        assert goal.week_start_date == week_start
        # Week should end on Friday (4 days after Monday)
        assert goal.week_end_date == week_start + timedelta(days=4)


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_days_remaining_exclude_weekends_monday(app, user):
    """Test days_remaining calculation with exclude_weekends=True starting on Monday."""
    with app.app_context():
        # Create a goal starting on Monday
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(
            user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
        )
        db.session.add(goal)
        db.session.commit()

        # If today is Monday, there should be 5 days remaining (Mon-Fri)
        # If today is Tuesday, there should be 4 days remaining (Tue-Fri)
        # etc.
        today = date.today()
        if today >= week_start and today <= goal.week_end_date:
            # Calculate expected days remaining (weekdays only)
            expected_days = 0
            current_date = today
            while current_date <= goal.week_end_date:
                if current_date.weekday() < 5:  # Monday through Friday
                    expected_days += 1
                current_date += timedelta(days=1)
            assert goal.days_remaining == expected_days
            # Should be between 1 and 5 days
            assert 1 <= goal.days_remaining <= 5


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_average_hours_per_day_exclude_weekends(app, user):
    """Test average_hours_per_day calculation with exclude_weekends=True."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(
            user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
        )
        db.session.add(goal)
        db.session.commit()

        # With 30 hours target and 5 days (Mon-Fri), should be 6 hours per day
        # But this depends on how many days are remaining
        today = date.today()
        if today >= week_start and today <= goal.week_end_date and goal.days_remaining > 0:
            # If we're at the start of the week with 5 days remaining
            # and 30 hours target, should be 6 hours per day
            if goal.days_remaining == 5:
                # No hours worked yet, so remaining = 30
                assert goal.average_hours_per_day == 6.0
            # Verify the calculation is correct
            expected_avg = round(goal.remaining_hours / goal.days_remaining, 2)
            assert goal.average_hours_per_day == expected_avg


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_days_remaining_excludes_weekends(app, user):
    """Test that days_remaining excludes weekends when exclude_weekends=True."""
    with app.app_context():
        # Create a goal for a specific week (Monday to Friday)
        # Use a known Monday date
        week_start = date(2024, 1, 1)  # Monday, January 1, 2024
        goal = WeeklyTimeGoal(
            user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
        )
        db.session.add(goal)
        db.session.commit()

        # Verify week_end_date is Friday (Jan 5, 2024)
        assert goal.week_end_date == date(2024, 1, 5)

        # Test with different "today" dates
        # If today is Monday (Jan 1), days_remaining should be 5 (Mon-Fri)
        # If today is Wednesday (Jan 3), days_remaining should be 3 (Wed-Fri)
        # If today is Saturday (Jan 6), days_remaining should be 0 (past week_end_date)

        # Mock different dates by temporarily modifying the goal's week_end_date
        # Actually, we can't easily mock local_now() in this test, so we'll test the logic
        # by verifying the property works correctly for the current date
        today = date.today()
        if today >= week_start and today <= goal.week_end_date:
            # Count weekdays manually
            expected_weekdays = 0
            current_date = today
            while current_date <= goal.week_end_date:
                if current_date.weekday() < 5:
                    expected_weekdays += 1
                current_date += timedelta(days=1)
            assert goal.days_remaining == expected_weekdays


@pytest.mark.unit
@pytest.mark.models
def test_weekly_goal_actual_hours_excludes_weekends(app, user, project):
    """Test that actual_hours calculation excludes weekends when exclude_weekends=True."""
    with app.app_context():
        week_start = date.today() - timedelta(days=date.today().weekday())
        goal = WeeklyTimeGoal(
            user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
        )
        db.session.add(goal)
        db.session.commit()

        # Add time entry on Monday (weekday)
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(week_start, datetime.min.time()),
            end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=8),
            duration_seconds=8 * 3600,
        )

        # Add time entry on Saturday (weekend) - should be excluded
        saturday = week_start + timedelta(days=5)
        TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            start_time=datetime.combine(saturday, datetime.min.time()),
            end_time=datetime.combine(saturday, datetime.min.time()) + timedelta(hours=5),
            duration_seconds=5 * 3600,
        )

        db.session.refresh(goal)

        # Should only count Monday's 8 hours, not Saturday's 5 hours
        assert goal.actual_hours == 8.0
