"""
Tests for PerDiem and PerDiemRate models
"""

import pytest

pytestmark = [pytest.mark.unit, pytest.mark.models]

from datetime import date, datetime, time
from decimal import Decimal
from app import db
from app.models import PerDiem, PerDiemRate, User


@pytest.fixture
def user(client):
    """Create a test user"""
    user = User(username="testuser", email="test@example.com")
    user.set_password("password123")
    db.session.add(user)
    db.session.commit()
    return user


@pytest.fixture
def rate(client):
    """Create a test per diem rate"""
    rate = PerDiemRate(
        country="Germany",
        city="Berlin",
        full_day_rate=28.00,
        half_day_rate=14.00,
        breakfast_rate=5.60,
        lunch_rate=11.20,
        dinner_rate=11.20,
        incidental_rate=3.00,
        currency_code="EUR",
        effective_from=date(2024, 1, 1),
    )
    db.session.add(rate)
    db.session.commit()
    return rate


def test_create_per_diem_rate(client):
    """Test creating a per diem rate"""
    rate = PerDiemRate(
        country="France", city="Paris", full_day_rate=45.00, half_day_rate=22.50, effective_from=date(2024, 1, 1)
    )

    db.session.add(rate)
    db.session.commit()

    assert rate.id is not None
    assert rate.country == "France"
    assert rate.city == "Paris"
    assert rate.full_day_rate == Decimal("45.00")
    assert rate.half_day_rate == Decimal("22.50")
    assert rate.is_active is True


def test_get_rate_for_location(client, rate):
    """Test getting rate for a specific location"""
    found_rate = PerDiemRate.get_rate_for_location("Germany", "Berlin", date.today())

    assert found_rate is not None
    assert found_rate.id == rate.id
    assert found_rate.city == "Berlin"


def test_get_rate_falls_back_to_country(client):
    """Test that rate search falls back to country rate if city not found"""
    # Create country-level rate
    country_rate = PerDiemRate(
        country="Netherlands",
        city=None,  # Country-level rate
        full_day_rate=35.00,
        half_day_rate=17.50,
        effective_from=date(2024, 1, 1),
    )
    db.session.add(country_rate)
    db.session.commit()

    # Search for a city that doesn't have a rate
    found_rate = PerDiemRate.get_rate_for_location("Netherlands", "Amsterdam", date.today())

    assert found_rate is not None
    assert found_rate.id == country_rate.id
    assert found_rate.city is None


def test_create_per_diem_claim(client, user, rate):
    """Test creating a per diem claim"""
    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Conference",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 23),
        country="Germany",
        city="Berlin",
        full_day_rate=rate.full_day_rate,
        half_day_rate=rate.half_day_rate,
        full_days=3,
        half_days=1,
        breakfast_deduction=rate.breakfast_rate,
        currency_code="EUR",
    )

    db.session.add(per_diem)
    db.session.commit()

    assert per_diem.id is not None
    assert per_diem.trip_purpose == "Conference"
    assert per_diem.full_days == 3
    assert per_diem.half_days == 1
    assert per_diem.total_days == 3.5
    assert per_diem.status == "pending"


def test_per_diem_calculation(client, user, rate):
    """Test per diem amount calculation"""
    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Business trip",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 22),
        country="Germany",
        city="Berlin",
        full_day_rate=28,
        half_day_rate=14,
        full_days=2,
        half_days=1,
        breakfast_provided=0,
        breakfast_deduction=0,
        lunch_deduction=0,
        dinner_deduction=0,
    )

    db.session.add(per_diem)
    db.session.commit()

    # Calculation: (2 * 28) + (1 * 14) = 56 + 14 = 70
    assert per_diem.calculated_amount == Decimal("70")


def test_per_diem_with_meal_deductions(client, user, rate):
    """Test per diem with provided meals"""
    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Conference with meals",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 22),
        country="Germany",
        city="Berlin",
        full_day_rate=28,
        half_day_rate=14,
        full_days=3,
        half_days=0,
        breakfast_provided=2,
        lunch_provided=3,
        dinner_provided=2,
        breakfast_deduction=5.60,
        lunch_deduction=11.20,
        dinner_deduction=11.20,
    )

    db.session.add(per_diem)
    db.session.commit()

    # Calculation: (3 * 28) - (2 * 5.60) - (3 * 11.20) - (2 * 11.20)
    # = 84 - 11.20 - 33.60 - 22.40 = 16.80
    assert per_diem.calculated_amount == Decimal("16.80")


def test_calculate_days_from_dates_single_day(client):
    """Test calculating days for a single day trip"""
    result = PerDiem.calculate_days_from_dates(
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 20),
        departure_time=time(8, 0),
        return_time=time(18, 0),  # 10 hours
    )

    assert result["full_days"] == 1
    assert result["half_days"] == 0


def test_calculate_days_from_dates_multi_day(client):
    """Test calculating days for multi-day trip"""
    result = PerDiem.calculate_days_from_dates(
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 23),
        departure_time=time(8, 0),  # Before noon = full day
        return_time=time(14, 0),  # After noon = full day
    )

    # Day 1: departure before 12:00 = full day
    # Day 2-3: middle days = 2 full days
    # Day 4: return after 12:00 = full day
    # Total: 4 full days
    assert result["full_days"] == 4
    assert result["half_days"] == 0


def test_calculate_days_with_half_days(client):
    """Test calculating days with half days"""
    result = PerDiem.calculate_days_from_dates(
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 22),
        departure_time=time(14, 0),  # After noon = half day
        return_time=time(10, 0),  # Before noon = half day
    )

    # Day 1: departure after 12:00 = half day
    # Day 2: middle day = full day
    # Day 3: return before 12:00 = half day
    # Total: 1 full day, 2 half days
    assert result["full_days"] == 1
    assert result["half_days"] == 2


def test_per_diem_approval(client, user):
    """Test per diem approval workflow"""
    admin = User(username="admin", email="admin@example.com", role="admin")
    admin.set_password("admin123")
    db.session.add(admin)
    db.session.commit()

    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Business trip",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 22),
        country="Germany",
        full_day_rate=28,
        half_day_rate=14,
        full_days=2,
        half_days=1,
    )

    db.session.add(per_diem)
    db.session.commit()

    # Approve
    per_diem.approve(admin.id, notes="Approved")
    db.session.commit()

    assert per_diem.status == "approved"
    assert per_diem.approved_by == admin.id
    assert per_diem.approved_at is not None


def test_per_diem_to_dict(client, user, rate):
    """Test converting per diem to dictionary"""
    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Test trip",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 22),
        country="Germany",
        city="Berlin",
        full_day_rate=28,
        half_day_rate=14,
        full_days=2,
        half_days=1,
    )

    db.session.add(per_diem)
    db.session.commit()

    data = per_diem.to_dict()

    assert data["id"] == per_diem.id
    assert data["user_id"] == user.id
    assert data["trip_purpose"] == "Test trip"
    assert data["country"] == "Germany"
    assert data["city"] == "Berlin"
    assert data["full_days"] == 2
    assert data["half_days"] == 1
    assert data["total_days"] == 2.5


def test_per_diem_recalculate(client, user):
    """Test recalculating per diem amount"""
    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Trip",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 22),
        country="Germany",
        full_day_rate=28,
        half_day_rate=14,
        full_days=2,
        half_days=0,
    )

    db.session.add(per_diem)
    db.session.commit()

    initial_amount = per_diem.calculated_amount
    assert initial_amount == Decimal("56")

    # Change days
    per_diem.full_days = 3
    new_amount = per_diem.recalculate_amount()

    assert new_amount == Decimal("84")
    assert per_diem.calculated_amount == Decimal("84")


def test_per_diem_create_expense(client, user):
    """Test creating expense from per diem claim"""
    per_diem = PerDiem(
        user_id=user.id,
        trip_purpose="Conference",
        start_date=date(2025, 10, 20),
        end_date=date(2025, 10, 23),
        country="Germany",
        city="Berlin",
        full_day_rate=28,
        half_day_rate=14,
        full_days=3,
        half_days=1,
    )

    db.session.add(per_diem)
    db.session.commit()

    # Create expense
    expense = per_diem.create_expense()

    assert expense is not None
    assert expense.user_id == user.id
    assert expense.category == "meals"
    assert expense.amount == per_diem.calculated_amount
    assert "Berlin, Germany" in expense.title
