"""Native tests for EduBridge utility helpers."""

from __future__ import annotations

from datetime import UTC, datetime

import pytest
from pydantic import BaseModel, ConfigDict, Field

from timeback_common import InputValidationError
from timeback_edubridge import (
    AggregatedMetrics,
    aggregate_activity_metrics,
    normalize_boolean,
    normalize_date,
    normalize_end_date,
    normalize_start_date,
    normalize_user,
)
from timeback_edubridge.types.analytics import (
    ActivityMetricsData,
    SubjectMetrics,
    TimeSpentMetricsData,
)


def _subject_metrics() -> SubjectMetrics:
    return SubjectMetrics(
        activityMetrics=ActivityMetricsData(
            xpEarned=100,
            totalQuestions=10,
            correctQuestions=8,
            masteredUnits=2,
        ),
        timeSpentMetrics=TimeSpentMetricsData(
            activeSeconds=3600, inactiveSeconds=600, wasteSeconds=120
        ),
        apps=["TestApp"],
    )


def test_normalize_boolean() -> None:
    assert normalize_boolean(True) is True
    assert normalize_boolean(False) is False
    assert normalize_boolean("true") is True
    assert normalize_boolean("false") is False


def test_normalize_date() -> None:
    assert normalize_date("2025-12-25") == "2025-12-25T00:00:00.000Z"
    assert normalize_date("2025-12-25T10:30:00+00:00") == "2025-12-25T10:30:00Z"
    dt = datetime(2025, 12, 25, 10, 30, 0, tzinfo=UTC)
    assert normalize_date(dt) is not None


def test_normalize_user() -> None:
    class MockUser(BaseModel):
        enabled_user: str = Field(alias="enabledUser")
        model_config = ConfigDict(populate_by_name=True)

    user = MockUser(enabledUser="true")
    normalized = normalize_user(user)  # type: ignore[type-var]
    assert normalized.enabled_user is True  # type: ignore[comparison-overlap]


# ═══════════════════════════════════════════════════════════════════════════════
# normalize_start_date
# ═══════════════════════════════════════════════════════════════════════════════


def test_normalize_start_date_passes_through_full_iso() -> None:
    assert normalize_start_date("2025-01-15T10:30:00Z") == "2025-01-15T10:30:00Z"


def test_normalize_start_date_normalizes_offset_suffix_to_z() -> None:
    assert normalize_start_date("2025-01-15T10:30:00+00:00") == "2025-01-15T10:30:00Z"


def test_normalize_start_date_passes_through_full_iso_even_with_timezone() -> None:
    assert (
        normalize_start_date("2025-01-15T10:30:00Z", "America/New_York") == "2025-01-15T10:30:00Z"
    )


def test_normalize_start_date_bare_date_without_timezone() -> None:
    assert normalize_start_date("2025-01-15") == "2025-01-15T00:00:00.000Z"


def test_normalize_start_date_new_york_winter() -> None:
    # Midnight EST = 05:00 UTC
    assert normalize_start_date("2025-01-15", "America/New_York") == "2025-01-15T05:00:00.000Z"


def test_normalize_start_date_los_angeles_winter() -> None:
    # Midnight PST = 08:00 UTC
    assert normalize_start_date("2025-01-15", "America/Los_Angeles") == "2025-01-15T08:00:00.000Z"


def test_normalize_start_date_new_york_summer() -> None:
    # Midnight EDT = 04:00 UTC
    assert normalize_start_date("2025-07-15", "America/New_York") == "2025-07-15T04:00:00.000Z"


def test_normalize_start_date_utc_timezone() -> None:
    assert normalize_start_date("2025-01-15", "UTC") == "2025-01-15T00:00:00.000Z"


def test_normalize_start_date_dst_spring_forward() -> None:
    # Clocks spring forward at 2 AM EST -> 3 AM EDT, but midnight is still EST (UTC-5)
    assert normalize_start_date("2024-03-10", "America/New_York") == "2024-03-10T05:00:00.000Z"


def test_normalize_start_date_invalid_timezone() -> None:
    with pytest.raises(InputValidationError):
        normalize_start_date("2025-01-15", "Fake/Zone")


# ═══════════════════════════════════════════════════════════════════════════════
# normalize_end_date
# ═══════════════════════════════════════════════════════════════════════════════


def test_normalize_end_date_passes_through_full_iso() -> None:
    assert normalize_end_date("2025-01-15T18:00:00Z") == "2025-01-15T18:00:00Z"


def test_normalize_end_date_normalizes_offset_suffix_to_z() -> None:
    assert normalize_end_date("2025-01-15T18:00:00+00:00") == "2025-01-15T18:00:00Z"


def test_normalize_end_date_bare_date_without_timezone() -> None:
    assert normalize_end_date("2025-01-15") == "2025-01-15T23:59:59.999Z"


def test_normalize_end_date_new_york_winter() -> None:
    # 23:59:59.999 EST = 04:59:59.999 UTC next day
    assert normalize_end_date("2025-01-15", "America/New_York") == "2025-01-16T04:59:59.999Z"


def test_normalize_end_date_los_angeles_winter() -> None:
    # 23:59:59.999 PST = 07:59:59.999 UTC next day
    assert normalize_end_date("2025-01-15", "America/Los_Angeles") == "2025-01-16T07:59:59.999Z"


def test_normalize_end_date_utc_timezone() -> None:
    assert normalize_end_date("2025-01-15", "UTC") == "2025-01-15T23:59:59.999Z"


def test_normalize_end_date_dst_spring_forward() -> None:
    # By 23:59 the clock has sprung forward, so we're in EDT (UTC-4)
    assert normalize_end_date("2024-03-10", "America/New_York") == "2024-03-11T03:59:59.999Z"


def test_normalize_end_date_dst_fall_back() -> None:
    # By 23:59 the clock has fallen back, so we're in EST (UTC-5)
    assert normalize_end_date("2024-11-03", "America/New_York") == "2024-11-04T04:59:59.999Z"


# ═══════════════════════════════════════════════════════════════════════════════
# aggregate_activity_metrics
# ═══════════════════════════════════════════════════════════════════════════════


def test_aggregate_activity_metrics() -> None:
    result = aggregate_activity_metrics({"2025-01-01": {"Math": _subject_metrics()}})
    assert result.total_xp == 100
    assert result.mastered_units == 2
    assert result.day_count == 1


def test_aggregated_metrics_model() -> None:
    metrics = AggregatedMetrics(
        totalXp=100.5,
        totalQuestions=50,
        correctQuestions=45,
        masteredUnits=3,
        activeSeconds=7200,
        inactiveSeconds=600,
        wasteSeconds=120,
        dayCount=5,
        subjectCount=10,
    )
    assert metrics.total_xp == 100.5
