"""Tests for cde_onboard.progress.ProgressManager."""

from __future__ import annotations

import json
from pathlib import Path

from cde_onboard.models import ProgressData
from cde_onboard.progress import ProgressManager


class TestProgressManagerLoad:
    """Tests for loading progress data."""

    def test_load_fresh_start(self, progress_manager: ProgressManager) -> None:
        """A fresh manager (no file on disk) returns empty ProgressData."""
        assert progress_manager.data.total_completed == 0
        assert progress_manager.data.completed_lessons == []

    def test_load_existing_file(self, progress_path: Path) -> None:
        """Loading reads previously saved data."""
        # Write a valid progress file manually
        data = ProgressData()
        data.mark_completed("tasks")
        progress_path.parent.mkdir(parents=True, exist_ok=True)
        progress_path.write_text(data.model_dump_json(indent=2), encoding="utf-8")

        mgr = ProgressManager(path=progress_path)
        assert mgr.data.total_completed == 1
        assert mgr.is_completed("tasks")

    def test_load_corrupt_json(self, progress_path: Path) -> None:
        """Corrupt JSON gracefully falls back to empty progress."""
        progress_path.parent.mkdir(parents=True, exist_ok=True)
        progress_path.write_text("{invalid json!!!", encoding="utf-8")

        mgr = ProgressManager(path=progress_path)
        assert mgr.data.total_completed == 0

    def test_load_invalid_schema(self, progress_path: Path) -> None:
        """Valid JSON with wrong schema falls back to empty progress."""
        progress_path.parent.mkdir(parents=True, exist_ok=True)
        progress_path.write_text(
            json.dumps({"completed_lessons": "not-a-list"}),
            encoding="utf-8",
        )

        mgr = ProgressManager(path=progress_path)
        assert mgr.data.total_completed == 0

    def test_load_empty_file(self, progress_path: Path) -> None:
        """An empty file is treated as corrupt and falls back to empty progress."""
        progress_path.parent.mkdir(parents=True, exist_ok=True)
        progress_path.write_text("", encoding="utf-8")

        mgr = ProgressManager(path=progress_path)
        assert mgr.data.total_completed == 0

    def test_lazy_load(self, progress_manager: ProgressManager) -> None:
        """Data is only loaded when first accessed (lazy property)."""
        # The internal _data should be None before access
        assert progress_manager._data is None
        # Access triggers load
        _ = progress_manager.data
        assert progress_manager._data is not None


class TestProgressManagerSave:
    """Tests for saving progress data."""

    def test_save_creates_parent_dirs(self, tmp_path: Path) -> None:
        """save() creates parent directories if they don't exist."""
        nested_path = tmp_path / "a" / "b" / "progress.json"
        mgr = ProgressManager(path=nested_path)
        mgr.data.mark_completed("tasks")
        mgr.save()
        assert nested_path.exists()

    def test_save_writes_valid_json(
        self, progress_manager: ProgressManager, progress_path: Path
    ) -> None:
        """Saved file contains valid JSON that can be deserialized."""
        progress_manager.data.mark_completed("devcontainer-basics")
        progress_manager.save()

        raw = json.loads(progress_path.read_text(encoding="utf-8"))
        assert "devcontainer-basics" in raw["completed_lessons"]

    def test_save_is_human_readable(
        self, progress_manager: ProgressManager, progress_path: Path
    ) -> None:
        """Saved JSON is indented for readability."""
        progress_manager.data.mark_completed("tasks")
        progress_manager.save()

        text = progress_path.read_text(encoding="utf-8")
        # Indented JSON has newlines and spaces
        assert "\n" in text
        assert "  " in text


class TestProgressManagerMarkCompleted:
    """Tests for the mark_completed shortcut."""

    def test_mark_completed_saves_automatically(
        self, progress_manager: ProgressManager, progress_path: Path
    ) -> None:
        """mark_completed() persists to disk immediately."""
        progress_manager.mark_completed("services")
        assert progress_path.exists()

        # Load in a new manager to verify persistence
        mgr2 = ProgressManager(path=progress_path)
        assert mgr2.is_completed("services")

    def test_mark_completed_multiple(self, progress_manager: ProgressManager) -> None:
        """Multiple lessons can be marked completed sequentially."""
        progress_manager.mark_completed("devcontainer-basics")
        progress_manager.mark_completed("first-environment")
        progress_manager.mark_completed("tasks")
        assert progress_manager.data.total_completed == 3


class TestProgressManagerIsCompleted:
    """Tests for the is_completed shortcut."""

    def test_is_completed_true(self, progress_manager: ProgressManager) -> None:
        """Returns True for completed lessons."""
        progress_manager.mark_completed("dotfiles")
        assert progress_manager.is_completed("dotfiles")

    def test_is_completed_false(self, progress_manager: ProgressManager) -> None:
        """Returns False for lessons not yet completed."""
        assert not progress_manager.is_completed("multi-repo")


class TestProgressManagerReset:
    """Tests for the reset functionality."""

    def test_reset_clears_data(self, progress_manager: ProgressManager) -> None:
        """reset() clears all progress in memory."""
        progress_manager.mark_completed("tasks")
        progress_manager.mark_completed("services")
        progress_manager.reset()
        assert progress_manager.data.total_completed == 0
        assert not progress_manager.is_completed("tasks")

    def test_reset_removes_file(
        self, progress_manager: ProgressManager, progress_path: Path
    ) -> None:
        """reset() deletes the progress file from disk."""
        progress_manager.mark_completed("tasks")
        assert progress_path.exists()
        progress_manager.reset()
        assert not progress_path.exists()

    def test_reset_no_file_is_safe(self, progress_manager: ProgressManager) -> None:
        """reset() doesn't error when there's no file to delete."""
        progress_manager.reset()  # Should not raise
        assert progress_manager.data.total_completed == 0


class TestProgressManagerPersistence:
    """Tests for cross-instance persistence."""

    def test_persistence_across_instances(self, progress_path: Path) -> None:
        """Data saved by one manager is loaded by another."""
        mgr1 = ProgressManager(path=progress_path)
        mgr1.mark_completed("devcontainer-basics")
        mgr1.mark_completed("first-environment")

        mgr2 = ProgressManager(path=progress_path)
        assert mgr2.data.total_completed == 2
        assert mgr2.is_completed("devcontainer-basics")
        assert mgr2.is_completed("first-environment")

    def test_timestamps_persist(self, progress_path: Path) -> None:
        """Timestamps are preserved across save/load cycles."""
        mgr1 = ProgressManager(path=progress_path)
        mgr1.mark_completed("tasks")
        started = mgr1.data.started_at

        mgr2 = ProgressManager(path=progress_path)
        assert mgr2.data.started_at == started

    def test_default_path_uses_home(self) -> None:
        """When no path is given, the default PROGRESS_FILE is used."""
        from cde_onboard.constants import PROGRESS_FILE

        mgr = ProgressManager()
        assert mgr._path == PROGRESS_FILE
