"""Unit tests for SchedulerConfig model fields and validators (Task 3)."""
import pytest
from pydantic import ValidationError

from src.server.models.config import SchedulerConfig

ALL_DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]


class TestSchedulerConfigDefaults:
    """3.1 – Default values."""

    def test_default_schedule_time(self) -> None:
        config = SchedulerConfig()
        assert config.schedule_time == "03:00"

    def test_default_schedule_days(self) -> None:
        config = SchedulerConfig()
        assert config.schedule_days == ALL_DAYS

    def test_default_auto_download(self) -> None:
        config = SchedulerConfig()
        assert config.auto_download_after_rescan is False

    def test_default_enabled(self) -> None:
        config = SchedulerConfig()
        assert config.enabled is True

    def test_default_interval_minutes(self) -> None:
        config = SchedulerConfig()
        assert config.interval_minutes == 60


class TestSchedulerConfigValidScheduleTime:
    """3.2 – Valid schedule_time values."""

    @pytest.mark.parametrize("time_val", ["00:00", "03:00", "12:30", "23:59"])
    def test_valid_times(self, time_val: str) -> None:
        config = SchedulerConfig(schedule_time=time_val)
        assert config.schedule_time == time_val


class TestSchedulerConfigInvalidScheduleTime:
    """3.3 – Invalid schedule_time values must raise ValidationError."""

    @pytest.mark.parametrize(
        "time_val",
        ["25:00", "3pm", "", "3:00pm", "24:00", "-1:00", "9:00", "1:60"],
    )
    def test_invalid_times(self, time_val: str) -> None:
        with pytest.raises(ValidationError):
            SchedulerConfig(schedule_time=time_val)


class TestSchedulerConfigValidScheduleDays:
    """3.4 – Valid schedule_days values."""

    def test_single_day(self) -> None:
        config = SchedulerConfig(schedule_days=["mon"])
        assert config.schedule_days == ["mon"]

    def test_multiple_days(self) -> None:
        config = SchedulerConfig(schedule_days=["mon", "fri"])
        assert config.schedule_days == ["mon", "fri"]

    def test_all_days(self) -> None:
        config = SchedulerConfig(schedule_days=ALL_DAYS)
        assert config.schedule_days == ALL_DAYS

    def test_empty_list(self) -> None:
        config = SchedulerConfig(schedule_days=[])
        assert config.schedule_days == []


class TestSchedulerConfigInvalidScheduleDays:
    """3.5 – Invalid schedule_days values must raise ValidationError."""

    @pytest.mark.parametrize(
        "days",
        [
            ["monday"],
            ["xyz"],
            ["Mon"],      # Case-sensitive — must be lowercase
            [""],
        ],
    )
    def test_invalid_days(self, days: list) -> None:
        with pytest.raises(ValidationError):
            SchedulerConfig(schedule_days=days)


class TestSchedulerConfigAutoDownload:
    """3.6 – auto_download_after_rescan field."""

    def test_set_true(self) -> None:
        config = SchedulerConfig(auto_download_after_rescan=True)
        assert config.auto_download_after_rescan is True

    def test_set_false(self) -> None:
        config = SchedulerConfig(auto_download_after_rescan=False)
        assert config.auto_download_after_rescan is False


class TestSchedulerConfigBackwardCompat:
    """3.7 – Backward compatibility: old fields still work."""

    def test_legacy_fields_use_defaults(self) -> None:
        config = SchedulerConfig(enabled=True, interval_minutes=30)
        assert config.schedule_time == "03:00"
        assert config.schedule_days == ALL_DAYS
        assert config.auto_download_after_rescan is False
        assert config.enabled is True
        assert config.interval_minutes == 30


class TestSchedulerConfigFolderScanEnabled:
    """3.8 – folder_scan_enabled field (Task 1.1)."""

    def test_default_folder_scan_enabled(self) -> None:
        config = SchedulerConfig()
        assert config.folder_scan_enabled is False

    def test_set_folder_scan_enabled_true(self) -> None:
        config = SchedulerConfig(folder_scan_enabled=True)
        assert config.folder_scan_enabled is True

    def test_set_folder_scan_enabled_false(self) -> None:
        config = SchedulerConfig(folder_scan_enabled=False)
        assert config.folder_scan_enabled is False

    def test_backward_compat_missing_field(self) -> None:
        """Old configs without folder_scan_enabled load successfully."""
        dumped = {
            "enabled": True,
            "interval_minutes": 60,
            "schedule_time": "03:00",
            "schedule_days": ALL_DAYS,
            "auto_download_after_rescan": False,
        }
        config = SchedulerConfig(**dumped)
        assert config.folder_scan_enabled is False


class TestSchedulerConfigLegacyAliases:
    """3.10 – Legacy config key aliases (auto_download, folder_scan)."""

    def test_legacy_auto_download_true(self) -> None:
        """Legacy auto_download=true maps to auto_download_after_rescan=True."""
        config = SchedulerConfig(auto_download=True)
        assert config.auto_download_after_rescan is True
        assert config.folder_scan_enabled is False

    def test_legacy_auto_download_false(self) -> None:
        config = SchedulerConfig(auto_download=False)
        assert config.auto_download_after_rescan is False

    def test_legacy_folder_scan_true(self) -> None:
        """Legacy folder_scan=true maps to folder_scan_enabled=True."""
        config = SchedulerConfig(folder_scan=True)
        assert config.folder_scan_enabled is True
        assert config.auto_download_after_rescan is False

    def test_legacy_folder_scan_false(self) -> None:
        config = SchedulerConfig(folder_scan=False)
        assert config.folder_scan_enabled is False

    def test_legacy_both_set(self) -> None:
        """Both legacy keys can be set simultaneously."""
        config = SchedulerConfig(auto_download=True, folder_scan=True)
        assert config.auto_download_after_rescan is True
        assert config.folder_scan_enabled is True

    def test_explicit_primary_overrides_legacy(self) -> None:
        """Primary field explicitly set to False still wins over legacy True.
        
        When user provides both old and new key, newer key wins by virtue of
        being the intended migration target. Legacy alias only applies when
        primary key is absent from data entirely.
        """
        config = SchedulerConfig(
            auto_download=True,
            auto_download_after_rescan=True,
            folder_scan=True,
            folder_scan_enabled=True,
        )
        # Both set to True — no conflict possible when both agree
        assert config.auto_download_after_rescan is True
        assert config.folder_scan_enabled is True

    def test_explicit_primary_false_wins_over_legacy_true(self) -> None:
        """Primary=False explicitly set wins over legacy=True.
        
        User has migrated config to new keys but old key still present.
        Explicit primary value must be respected.
        """
        config = SchedulerConfig(
            auto_download=True,
            auto_download_after_rescan=False,
        )
        assert config.auto_download_after_rescan is False

    def test_explicit_primary_true_wins_over_legacy_false(self) -> None:
        """Primary=True explicitly set wins over legacy=False."""
        config = SchedulerConfig(
            auto_download=False,
            auto_download_after_rescan=True,
        )
        assert config.auto_download_after_rescan is True

    def test_legacy_in_json_dict(self) -> None:
        """Simulate config.json with legacy auto_download key."""
        data = {
            "enabled": True,
            "schedule_time": "03:00",
            "schedule_days": ALL_DAYS,
            "auto_download": True,
            "folder_scan": True,
        }
        config = SchedulerConfig(**data)
        assert config.auto_download_after_rescan is True
        assert config.folder_scan_enabled is True


class TestSchedulerConfigSerialisation:
    """3.9 – Serialisation roundtrip."""

    def test_roundtrip(self) -> None:
        original = SchedulerConfig(
            enabled=True,
            interval_minutes=120,
            schedule_time="04:30",
            schedule_days=["mon", "wed", "fri"],
            auto_download_after_rescan=True,
            folder_scan_enabled=True,
        )
        dumped = original.model_dump()
        restored = SchedulerConfig(**dumped)
        assert restored == original

    def test_roundtrip_excludes_none_alias_fields(self) -> None:
        """model_dump must not emit null auto_download/folder_scan keys.

        Previously these null keys were written to config.json on save.
        On reload they were present (even as None), so the alias mapping in
        __init__ was skipped and the primary fields retained their default
        False values instead of the configured True values.
        """
        original = SchedulerConfig(
            auto_download_after_rescan=True,
            folder_scan_enabled=True,
        )
        dumped = original.model_dump()
        # Alias fields must not appear when None
        assert "auto_download" not in dumped
        assert "folder_scan" not in dumped
        # Primary fields roundtrip correctly
        restored = SchedulerConfig(**dumped)
        assert restored.auto_download_after_rescan is True
        assert restored.folder_scan_enabled is True
