"""
Tests for AutosaveManager
"""

import pytest
import json
import tempfile
import shutil
from pathlib import Path
from datetime import datetime, timedelta
from unittest.mock import Mock, patch, MagicMock

from pyPhotoAlbum.autosave_manager import AutosaveManager


class TestAutosaveManagerInit:
    """Tests for AutosaveManager initialization"""

    def test_init_creates_checkpoint_directory(self, tmp_path, monkeypatch):
        """Test that init creates the checkpoint directory"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        assert checkpoint_dir.exists()

    def test_init_with_existing_directory(self, tmp_path, monkeypatch):
        """Test init when checkpoint directory already exists"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        assert checkpoint_dir.exists()


class TestGetCheckpointPath:
    """Tests for _get_checkpoint_path method"""

    def test_get_checkpoint_path_basic(self, tmp_path, monkeypatch):
        """Test basic checkpoint path generation"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        path = manager._get_checkpoint_path("MyProject")

        assert path.parent == checkpoint_dir
        assert path.suffix == ".ppz"
        assert "checkpoint_MyProject_" in path.name

    def test_get_checkpoint_path_with_timestamp(self, tmp_path, monkeypatch):
        """Test checkpoint path with specific timestamp"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        timestamp = datetime(2024, 1, 15, 10, 30, 45)
        path = manager._get_checkpoint_path("TestProject", timestamp)

        assert "20240115_103045" in path.name

    def test_get_checkpoint_path_sanitizes_name(self, tmp_path, monkeypatch):
        """Test that special characters in project name are sanitized"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        path = manager._get_checkpoint_path("My Project!@#$%")

        # Should not contain special characters except - and _
        name_without_ext = path.stem
        for char in name_without_ext:
            assert char.isalnum() or char in "-_", f"Invalid char: {char}"


class TestCreateCheckpoint:
    """Tests for create_checkpoint method"""

    def test_create_checkpoint_success(self, tmp_path, monkeypatch):
        """Test successful checkpoint creation"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        # Mock save_to_zip - note the return value format
        with patch("pyPhotoAlbum.autosave_manager.save_to_zip") as mock_save:
            mock_save.return_value = (True, "Success")

            mock_project = Mock()
            mock_project.name = "TestProject"
            mock_project.file_path = "/path/to/project.ppz"

            success, message = manager.create_checkpoint(mock_project)

            assert success is True
            assert "Checkpoint created" in message
            mock_save.assert_called_once()

    def test_create_checkpoint_failure(self, tmp_path, monkeypatch):
        """Test checkpoint creation failure"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        with patch("pyPhotoAlbum.autosave_manager.save_to_zip") as mock_save:
            mock_save.return_value = (False, "Disk full")

            mock_project = Mock()
            mock_project.name = "TestProject"

            success, message = manager.create_checkpoint(mock_project)

            assert success is False
            assert "Checkpoint failed" in message

    def test_create_checkpoint_exception(self, tmp_path, monkeypatch):
        """Test checkpoint creation with exception"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        with patch("pyPhotoAlbum.autosave_manager.save_to_zip") as mock_save:
            mock_save.side_effect = Exception("IO Error")

            mock_project = Mock()
            mock_project.name = "TestProject"

            success, message = manager.create_checkpoint(mock_project)

            assert success is False
            assert "Checkpoint error" in message


class TestSaveCheckpointMetadata:
    """Tests for _save_checkpoint_metadata method"""

    def test_save_metadata(self, tmp_path, monkeypatch):
        """Test saving checkpoint metadata"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        mock_project = Mock()
        mock_project.name = "TestProject"
        mock_project.file_path = "/path/to/original.ppz"

        checkpoint_path = checkpoint_dir / "checkpoint_TestProject_20240115_103045.ppz"
        checkpoint_path.touch()

        manager._save_checkpoint_metadata(mock_project, checkpoint_path)

        metadata_path = checkpoint_path.with_suffix(".json")
        assert metadata_path.exists()

        with open(metadata_path, "r") as f:
            metadata = json.load(f)

        assert metadata["project_name"] == "TestProject"
        assert metadata["original_path"] == "/path/to/original.ppz"
        assert "timestamp" in metadata


class TestListCheckpoints:
    """Tests for list_checkpoints method"""

    def test_list_checkpoints_empty(self, tmp_path, monkeypatch):
        """Test listing checkpoints when none exist"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        checkpoints = manager.list_checkpoints()

        assert checkpoints == []

    def test_list_checkpoints_with_files(self, tmp_path, monkeypatch):
        """Test listing checkpoints with existing files"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create some checkpoint files
        cp1 = checkpoint_dir / "checkpoint_Project1_20240115_100000.ppz"
        cp2 = checkpoint_dir / "checkpoint_Project2_20240115_110000.ppz"
        cp1.touch()
        cp2.touch()

        # Create metadata for first checkpoint
        metadata1 = {"project_name": "Project1", "timestamp": "2024-01-15T10:00:00"}
        with open(cp1.with_suffix(".json"), "w") as f:
            json.dump(metadata1, f)

        manager = AutosaveManager()
        checkpoints = manager.list_checkpoints()

        assert len(checkpoints) == 2

    def test_list_checkpoints_filter_by_project(self, tmp_path, monkeypatch):
        """Test listing checkpoints filtered by project name"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create checkpoint files with metadata
        cp1 = checkpoint_dir / "checkpoint_Project1_20240115_100000.ppz"
        cp2 = checkpoint_dir / "checkpoint_Project2_20240115_110000.ppz"
        cp1.touch()
        cp2.touch()

        metadata1 = {"project_name": "Project1", "timestamp": "2024-01-15T10:00:00"}
        metadata2 = {"project_name": "Project2", "timestamp": "2024-01-15T11:00:00"}

        with open(cp1.with_suffix(".json"), "w") as f:
            json.dump(metadata1, f)
        with open(cp2.with_suffix(".json"), "w") as f:
            json.dump(metadata2, f)

        manager = AutosaveManager()
        checkpoints = manager.list_checkpoints("Project1")

        assert len(checkpoints) == 1
        assert checkpoints[0][1]["project_name"] == "Project1"

    def test_list_checkpoints_sorted_by_timestamp(self, tmp_path, monkeypatch):
        """Test that checkpoints are sorted by timestamp (newest first)"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create checkpoints with different timestamps
        cp1 = checkpoint_dir / "checkpoint_Project_20240115_080000.ppz"
        cp2 = checkpoint_dir / "checkpoint_Project_20240115_120000.ppz"
        cp3 = checkpoint_dir / "checkpoint_Project_20240115_100000.ppz"
        cp1.touch()
        cp2.touch()
        cp3.touch()

        for cp, hour in [(cp1, "08"), (cp2, "12"), (cp3, "10")]:
            metadata = {"project_name": "Project", "timestamp": f"2024-01-15T{hour}:00:00"}
            with open(cp.with_suffix(".json"), "w") as f:
                json.dump(metadata, f)

        manager = AutosaveManager()
        checkpoints = manager.list_checkpoints()

        # Should be sorted newest first: 12:00, 10:00, 08:00
        assert "12:00:00" in checkpoints[0][1]["timestamp"]
        assert "10:00:00" in checkpoints[1][1]["timestamp"]
        assert "08:00:00" in checkpoints[2][1]["timestamp"]


class TestLoadCheckpoint:
    """Tests for load_checkpoint method"""

    def test_load_checkpoint_success(self, tmp_path, monkeypatch):
        """Test successful checkpoint loading"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        with patch("pyPhotoAlbum.autosave_manager.load_from_zip") as mock_load:
            mock_project = Mock()
            mock_load.return_value = mock_project

            checkpoint_path = checkpoint_dir / "checkpoint_Test.ppz"
            success, result = manager.load_checkpoint(checkpoint_path)

            assert success is True
            assert result == mock_project

    def test_load_checkpoint_failure(self, tmp_path, monkeypatch):
        """Test checkpoint loading failure"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()

        with patch("pyPhotoAlbum.autosave_manager.load_from_zip") as mock_load:
            mock_load.side_effect = Exception("Corrupt file")

            checkpoint_path = checkpoint_dir / "checkpoint_Test.ppz"
            success, result = manager.load_checkpoint(checkpoint_path)

            assert success is False
            assert "Failed to load checkpoint" in result


class TestDeleteCheckpoint:
    """Tests for delete_checkpoint method"""

    def test_delete_checkpoint_success(self, tmp_path, monkeypatch):
        """Test successful checkpoint deletion"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create checkpoint and metadata files
        cp = checkpoint_dir / "checkpoint_Test.ppz"
        cp.touch()
        metadata = cp.with_suffix(".json")
        metadata.touch()

        manager = AutosaveManager()
        result = manager.delete_checkpoint(cp)

        assert result is True
        assert not cp.exists()
        assert not metadata.exists()

    def test_delete_checkpoint_nonexistent(self, tmp_path, monkeypatch):
        """Test deleting nonexistent checkpoint"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        cp = checkpoint_dir / "nonexistent.ppz"
        result = manager.delete_checkpoint(cp)

        assert result is True  # Should succeed even if file doesn't exist


class TestDeleteAllCheckpoints:
    """Tests for delete_all_checkpoints method"""

    def test_delete_all_checkpoints(self, tmp_path, monkeypatch):
        """Test deleting all checkpoints"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create multiple checkpoints
        for i in range(3):
            cp = checkpoint_dir / f"checkpoint_Project_{i}.ppz"
            cp.touch()
            metadata = {"project_name": "Project", "timestamp": f"2024-01-15T{i}:00:00"}
            with open(cp.with_suffix(".json"), "w") as f:
                json.dump(metadata, f)

        manager = AutosaveManager()
        manager.delete_all_checkpoints()

        remaining = list(checkpoint_dir.glob("checkpoint_*.ppz"))
        assert len(remaining) == 0

    def test_delete_all_checkpoints_filtered(self, tmp_path, monkeypatch):
        """Test deleting all checkpoints for specific project"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create checkpoints for different projects
        for name in ["ProjectA", "ProjectB", "ProjectA"]:
            cp = checkpoint_dir / f"checkpoint_{name}_{datetime.now().strftime('%Y%m%d_%H%M%S%f')}.ppz"
            cp.touch()
            metadata = {"project_name": name, "timestamp": datetime.now().isoformat()}
            with open(cp.with_suffix(".json"), "w") as f:
                json.dump(metadata, f)

        manager = AutosaveManager()
        manager.delete_all_checkpoints("ProjectA")

        # Only ProjectB should remain
        remaining = list(checkpoint_dir.glob("checkpoint_*.ppz"))
        assert len(remaining) == 1
        assert "ProjectB" in remaining[0].name


class TestCleanupOldCheckpoints:
    """Tests for cleanup_old_checkpoints method"""

    def test_cleanup_old_checkpoints_by_age(self, tmp_path, monkeypatch):
        """Test cleanup of old checkpoints by age"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create old and new checkpoints
        old_time = datetime.now() - timedelta(hours=48)
        new_time = datetime.now() - timedelta(hours=1)

        old_cp = checkpoint_dir / "checkpoint_Project_old.ppz"
        new_cp = checkpoint_dir / "checkpoint_Project_new.ppz"
        old_cp.touch()
        new_cp.touch()

        old_metadata = {"project_name": "Project", "timestamp": old_time.isoformat()}
        new_metadata = {"project_name": "Project", "timestamp": new_time.isoformat()}

        with open(old_cp.with_suffix(".json"), "w") as f:
            json.dump(old_metadata, f)
        with open(new_cp.with_suffix(".json"), "w") as f:
            json.dump(new_metadata, f)

        manager = AutosaveManager()
        manager.cleanup_old_checkpoints(max_age_hours=24)

        # Only new checkpoint should remain
        remaining = list(checkpoint_dir.glob("checkpoint_*.ppz"))
        assert len(remaining) == 1
        assert "new" in remaining[0].name

    def test_cleanup_old_checkpoints_by_count(self, tmp_path, monkeypatch):
        """Test cleanup of checkpoints by count"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create many recent checkpoints
        for i in range(5):
            timestamp = datetime.now() - timedelta(hours=i)
            cp = checkpoint_dir / f"checkpoint_Project_{i:02d}.ppz"
            cp.touch()
            metadata = {"project_name": "Project", "timestamp": timestamp.isoformat()}
            with open(cp.with_suffix(".json"), "w") as f:
                json.dump(metadata, f)

        manager = AutosaveManager()
        manager.cleanup_old_checkpoints(max_age_hours=24 * 7, max_count=3)

        # Should only keep 3 most recent
        remaining = list(checkpoint_dir.glob("checkpoint_*.ppz"))
        assert len(remaining) == 3


class TestHasCheckpoints:
    """Tests for has_checkpoints method"""

    def test_has_checkpoints_true(self, tmp_path, monkeypatch):
        """Test has_checkpoints returns True when checkpoints exist"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        cp = checkpoint_dir / "checkpoint_Test.ppz"
        cp.touch()

        manager = AutosaveManager()
        assert manager.has_checkpoints() is True

    def test_has_checkpoints_false(self, tmp_path, monkeypatch):
        """Test has_checkpoints returns False when no checkpoints"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        assert manager.has_checkpoints() is False


class TestGetLatestCheckpoint:
    """Tests for get_latest_checkpoint method"""

    def test_get_latest_checkpoint(self, tmp_path, monkeypatch):
        """Test getting the latest checkpoint"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create checkpoints with different timestamps
        for hour in [8, 10, 12]:
            cp = checkpoint_dir / f"checkpoint_Project_{hour:02d}.ppz"
            cp.touch()
            metadata = {"project_name": "Project", "timestamp": f"2024-01-15T{hour:02d}:00:00"}
            with open(cp.with_suffix(".json"), "w") as f:
                json.dump(metadata, f)

        manager = AutosaveManager()
        result = manager.get_latest_checkpoint()

        assert result is not None
        assert "12:00:00" in result[1]["timestamp"]

    def test_get_latest_checkpoint_none(self, tmp_path, monkeypatch):
        """Test getting latest checkpoint when none exist"""
        checkpoint_dir = tmp_path / "checkpoints"
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        manager = AutosaveManager()
        result = manager.get_latest_checkpoint()

        assert result is None

    def test_get_latest_checkpoint_filtered(self, tmp_path, monkeypatch):
        """Test getting latest checkpoint for specific project"""
        checkpoint_dir = tmp_path / "checkpoints"
        checkpoint_dir.mkdir(parents=True)
        monkeypatch.setattr(AutosaveManager, "CHECKPOINT_DIR", checkpoint_dir)

        # Create checkpoints for different projects
        for name, hour in [("ProjectA", 10), ("ProjectB", 12), ("ProjectA", 8)]:
            cp = checkpoint_dir / f"checkpoint_{name}_{hour:02d}.ppz"
            cp.touch()
            metadata = {"project_name": name, "timestamp": f"2024-01-15T{hour:02d}:00:00"}
            with open(cp.with_suffix(".json"), "w") as f:
                json.dump(metadata, f)

        manager = AutosaveManager()
        result = manager.get_latest_checkpoint("ProjectA")

        assert result is not None
        assert result[1]["project_name"] == "ProjectA"
        assert "10:00:00" in result[1]["timestamp"]  # Latest for ProjectA
