"""
Unit tests for result formatter.
Tests validation and unfilled shift analysis.
"""
import pytest
import sys
from pathlib import Path

# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from result_formatter import validate_constraints, analyze_unfilled_shift


def test_validation_detects_twenty_four_hour_overlap_and_availability_gap():
    """Post-solve validation uses the same real dated 24h interval semantics."""
    employee = {
        "userId": 1,
        "name": "Alice",
        "role": 4,
        "hoursMax": 40.0,
        "currentPeriodHours": 0.0,
        "shiftsThisWeek": 0,
        "availability": [
            {"dayOfWeek": 1, "startTime": "08:00", "endTime": "24:00"},
            {"dayOfWeek": 2, "startTime": "10:00", "endTime": "18:00"},
        ],
        "timeOff": [],
        "allowedPositionIds": [1],
    }
    shifts = [
        {
            "shiftId": 1,
            "date": "2026-02-16",
            "dayOfWeek": 1,
            "startTime": "09:00",
            "endTime": "09:00",
            "durationHours": 24.0,
            "positionId": 1,
            "minRoleId": 4,
        },
        {
            "shiftId": 2,
            "date": "2026-02-17",
            "dayOfWeek": 2,
            "startTime": "08:00",
            "endTime": "12:00",
            "durationHours": 4.0,
            "positionId": 1,
            "minRoleId": 4,
        },
    ]

    violations = validate_constraints(
        {"employees": [employee], "shifts": shifts, "lockedShifts": []},
        {1: 1, 2: 1},
    )

    assert violations["c5_availability"] >= 1
    assert violations["c9_overlap"] == 1


def test_validation_detects_time_off_on_second_day_of_overnight_shift():
    employee = {
        "userId": 1,
        "name": "Alice",
        "role": 4,
        "hoursMax": 40.0,
        "currentPeriodHours": 0.0,
        "shiftsThisWeek": 0,
        "availability": [
            {"dayOfWeek": 1, "startTime": "00:00", "endTime": "23:59"},
            {"dayOfWeek": 2, "startTime": "00:00", "endTime": "23:59"},
        ],
        "timeOff": [{"date": "2026-02-17"}],
        "allowedPositionIds": [],
    }
    shift = {
        "shiftId": 1,
        "date": "2026-02-16",
        "dayOfWeek": 1,
        "startTime": "22:00",
        "endTime": "02:00",
        "durationHours": 4.0,
        "positionId": None,
        "minRoleId": 4,
    }

    violations = validate_constraints(
        {"employees": [employee], "shifts": [shift], "lockedShifts": []},
        {1: 1},
    )

    assert violations["c6_time_off"] == 1


def test_c12_validation_no_violations():
    """Test C12: Valid assignment where employee has the right position."""
    problem_data = {
        "employees": [
            {
                "userId": 1,
                "name": "Alice",
                "role": 4,
                "hourlyRate": 15.00,
                "hoursRequested": 32.0,
                "hoursMin": 20.0,
                "hoursMax": 40.0,
                "currentPeriodHours": 0.0,
                "shiftsThisWeek": 0,
                "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
                "timeOff": [],
                "allowedPositionIds": [1, 3]
            }
        ],
        "shifts": [
            {
                "shiftId": 1,
                "minRoleId": 4,
                "date": "2026-02-16",
                "dayOfWeek": 1,
                "startTime": "09:00",
                "endTime": "17:00",
                "durationHours": 8.0,
                "positionId": 1
            }
        ],
        "lockedShifts": []
    }

    # Assign shift 1 to employee 1 (who has position 1)
    assignments = {1: 1}

    violations = validate_constraints(problem_data, assignments)

    assert violations["c12_position_eligibility"] == 0, "No C12 violations expected"


def test_c12_validation_with_violation():
    """Test C12: Invalid assignment where employee lacks the position."""
    problem_data = {
        "employees": [
            {
                "userId": 1,
                "name": "Alice",
                "role": 4,
                "hourlyRate": 15.00,
                "hoursRequested": 32.0,
                "hoursMin": 20.0,
                "hoursMax": 40.0,
                "currentPeriodHours": 0.0,
                "shiftsThisWeek": 0,
                "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
                "timeOff": [],
                "allowedPositionIds": [1, 3]
            }
        ],
        "shifts": [
            {
                "shiftId": 1,
                "minRoleId": 4,
                "date": "2026-02-16",
                "dayOfWeek": 1,
                "startTime": "09:00",
                "endTime": "17:00",
                "durationHours": 8.0,
                "positionId": 2  # Employee doesn't have position 2
            }
        ],
        "lockedShifts": []
    }

    # Assign shift 1 to employee 1 (who lacks position 2)
    assignments = {1: 1}

    violations = validate_constraints(problem_data, assignments)

    assert violations["c12_position_eligibility"] == 1, "Expected 1 C12 violation"


def test_c12_validation_null_position_no_violation():
    """Test C12: No violation when shift has no position requirement."""
    problem_data = {
        "employees": [
            {
                "userId": 1,
                "name": "Alice",
                "role": 4,
                "hourlyRate": 15.00,
                "hoursRequested": 32.0,
                "hoursMin": 20.0,
                "hoursMax": 40.0,
                "currentPeriodHours": 0.0,
                "shiftsThisWeek": 0,
                "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
                "timeOff": [],
                "allowedPositionIds": [1, 3]
            }
        ],
        "shifts": [
            {
                "shiftId": 1,
                "minRoleId": 4,
                "date": "2026-02-16",
                "dayOfWeek": 1,
                "startTime": "09:00",
                "endTime": "17:00",
                "durationHours": 8.0,
                "positionId": None  # No position requirement
            }
        ],
        "lockedShifts": []
    }

    # Assign shift 1 to employee 1
    assignments = {1: 1}

    violations = validate_constraints(problem_data, assignments)

    assert violations["c12_position_eligibility"] == 0, "No C12 violations expected for null position"


def test_c12_validation_empty_positions_violation():
    """Test C12: Employee with empty positions assigned to positioned shift -> violation."""
    problem_data = {
        "employees": [
            {
                "userId": 1,
                "name": "Alice",
                "role": 4,
                "hourlyRate": 15.00,
                "hoursRequested": 32.0,
                "hoursMin": 20.0,
                "hoursMax": 40.0,
                "currentPeriodHours": 0.0,
                "shiftsThisWeek": 0,
                "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
                "timeOff": [],
                "allowedPositionIds": []  # No positions
            }
        ],
        "shifts": [
            {
                "shiftId": 1,
                "minRoleId": 4,
                "date": "2026-02-16",
                "dayOfWeek": 1,
                "startTime": "09:00",
                "endTime": "17:00",
                "durationHours": 8.0,
                "positionId": 1
            }
        ],
        "lockedShifts": []
    }

    # Assign shift 1 to employee 1 (who has no positions)
    assignments = {1: 1}

    violations = validate_constraints(problem_data, assignments)

    assert violations["c12_position_eligibility"] == 1, "Expected 1 C12 violation for empty positions"


def test_c12_unfilled_shift_position_reason():
    """Test C12: Unfilled shift analysis should mention position ineligibility."""
    shift = {
        "shiftId": 1,
        "minRoleId": 4,
        "date": "2026-02-16",
        "dayOfWeek": 1,
        "startTime": "09:00",
        "endTime": "17:00",
        "durationHours": 8.0,
        "positionId": 2,
        "positionName": "Cashier"
    }

    employees = [
        {
            "userId": 1,
            "name": "Alice",
            "role": 4,
            "hoursMax": 40.0,
            "currentPeriodHours": 0.0,
            "shiftsThisWeek": 0,
            "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
            "timeOff": [],
            "allowedPositionIds": [1, 3]  # Doesn't have position 2
        }
    ]

    hours_by_employee = {1: 0.0}
    locked_dates_by_emp = {}
    filled_shift_ids = set()
    assignments = {}
    all_shifts = [shift]

    reasons = analyze_unfilled_shift(
        shift, employees, hours_by_employee,
        locked_dates_by_emp, filled_shift_ids, assignments, all_shifts
    )

    # Should mention position ineligibility
    assert len(reasons) > 0
    assert any("not qualified for Cashier position" in reason for reason in reasons), \
        f"Expected position ineligibility in reasons: {reasons}"


def test_c12_unfilled_shift_no_position_requirement():
    """Test C12: Unfilled shift with no position requirement doesn't blame positions."""
    shift = {
        "shiftId": 1,
        "minRoleId": 4,
        "date": "2026-02-16",
        "dayOfWeek": 1,
        "startTime": "09:00",
        "endTime": "17:00",
        "durationHours": 8.0,
        "positionId": None  # No position requirement
    }

    employees = [
        {
            "userId": 1,
            "name": "Alice",
            "role": 4,
            "hoursMax": 7.0,  # Max hours too low (7.0 < 8.0 shift)
            "currentPeriodHours": 0.0,
            "shiftsThisWeek": 0,
            "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
            "timeOff": [],
            "allowedPositionIds": [1, 3]
        }
    ]

    hours_by_employee = {1: 0.0}
    locked_dates_by_emp = {}
    filled_shift_ids = set()
    assignments = {}
    all_shifts = [shift]

    reasons = analyze_unfilled_shift(
        shift, employees, hours_by_employee,
        locked_dates_by_emp, filled_shift_ids, assignments, all_shifts
    )

    # Should mention hours max, NOT position
    assert len(reasons) > 0
    assert any("would exceed max hours" in reason for reason in reasons), \
        f"Expected hours max reason: {reasons}"
    assert not any("position" in reason.lower() for reason in reasons), \
        f"Should not mention position for null position shift: {reasons}"


def test_c12_unfilled_shift_multiple_employees_all_position_blocked():
    """Test C12: All employees blocked by position eligibility."""
    shift = {
        "shiftId": 1,
        "minRoleId": 4,
        "date": "2026-02-16",
        "dayOfWeek": 1,
        "startTime": "09:00",
        "endTime": "17:00",
        "durationHours": 8.0,
        "positionId": 2,
        "positionName": "Cashier"
    }

    employees = [
        {
            "userId": 1,
            "name": "Alice",
            "role": 4,
            "hoursMax": 40.0,
            "currentPeriodHours": 0.0,
            "shiftsThisWeek": 0,
            "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
            "timeOff": [],
            "allowedPositionIds": [1, 3]
        },
        {
            "userId": 2,
            "name": "Bob",
            "role": 4,
            "hoursMax": 40.0,
            "currentPeriodHours": 0.0,
            "shiftsThisWeek": 0,
            "availability": [{"dayOfWeek": 1, "startTime": "08:00", "endTime": "22:00"}],
            "timeOff": [],
            "allowedPositionIds": [1]
        }
    ]

    hours_by_employee = {1: 0.0, 2: 0.0}
    locked_dates_by_emp = {}
    filled_shift_ids = set()
    assignments = {}
    all_shifts = [shift]

    reasons = analyze_unfilled_shift(
        shift, employees, hours_by_employee,
        locked_dates_by_emp, filled_shift_ids, assignments, all_shifts
    )

    # Should have "All X employees blocked" message
    assert len(reasons) > 0
    assert any("All 2 employees blocked" in reason for reason in reasons), \
        f"Expected 'All employees blocked' message: {reasons}"
    # Both employees should be mentioned
    assert any("Alice" in reason and "Cashier" in reason for reason in reasons)
    assert any("Bob" in reason and "Cashier" in reason for reason in reasons)
