"""
Critical tests for storage layer.

These tests verify atomic writes, temp file cleanup, and
file permission handling.
"""

import pytest
import tempfile
import json
from pathlib import Path
from kilit.storage import FileStorage, atomic_write, set_secure_permissions
from kilit.exceptions import KilitFileError


class TestAtomicWrite:
    """Test atomic write operations."""

    def test_atomic_write_creates_file(self, tmp_path):
        """Atomic write creates file with correct content."""
        target = tmp_path / "test.txt"
        content = "test content"

        atomic_write(target, content)

        assert target.exists()
        assert target.read_text() == content

    def test_atomic_write_overwrites_existing(self, tmp_path):
        """Atomic write overwrites existing file."""
        target = tmp_path / "test.txt"
        target.write_text("old content")

        new_content = "new content"
        atomic_write(target, new_content)

        assert target.read_text() == new_content

    def test_atomic_write_cleans_temp_on_success(self, tmp_path):
        """Atomic write cleans up temp file on success."""
        target = tmp_path / "test.txt"
        content = "test"

        # Count files before
        files_before = list(tmp_path.iterdir())

        atomic_write(target, content)

        # Count files after (should only be target file)
        files_after = list(tmp_path.iterdir())
        assert len(files_after) == len(files_before) + 1
        assert target in files_after

    def test_atomic_write_preserves_old_on_error(self, tmp_path):
        """Atomic write preserves old file if write fails."""
        target = tmp_path / "test.txt"
        old_content = "old content"
        target.write_text(old_content)

        # This should work, but let's test the principle
        # In real scenario, disk full or permission error would trigger this
        try:
            atomic_write(target, "new content")
        except Exception:
            pass

        # Old content should still be there (or new content if successful)
        assert target.exists()


class TestFileStorage:
    """Test FileStorage class operations."""

    def test_save_and_load_key(self, tmp_path):
        """Save and load master key."""
        storage = FileStorage()
        key_path = tmp_path / "master.key"
        key = "test_key_base64"

        storage.save_key(key, key_path)
        loaded_key = storage.load_key(key_path)

        assert loaded_key == key

    def test_load_key_not_found_raises_error(self, tmp_path):
        """Loading non-existent key raises KilitFileError."""
        storage = FileStorage()
        key_path = tmp_path / "nonexistent.key"

        with pytest.raises(KilitFileError, match="not found"):
            storage.load_key(key_path)

    def test_save_and_load_envelope(self, tmp_path):
        """Save and load envelope."""
        storage = FileStorage()
        envelope_path = tmp_path / "creds.json.enc"
        envelope = {
            "v": 1,
            "kdf": "scrypt",
            "kdfp": {"n": 16384, "r": 8, "p": 1},
            "salt": "test_salt",
            "nonce": "test_nonce",
            "ct": "test_ciphertext",
        }

        storage.save_envelope(envelope, envelope_path)
        loaded_envelope = storage.load_envelope(envelope_path)

        assert loaded_envelope == envelope

    def test_load_envelope_not_found_raises_error(self, tmp_path):
        """Loading non-existent envelope raises KilitFileError."""
        storage = FileStorage()
        envelope_path = tmp_path / "nonexistent.json.enc"

        with pytest.raises(KilitFileError, match="not found"):
            storage.load_envelope(envelope_path)

    def test_load_envelope_invalid_json_raises_error(self, tmp_path):
        """Loading invalid JSON raises KilitFileError."""
        storage = FileStorage()
        envelope_path = tmp_path / "invalid.json.enc"
        envelope_path.write_text("not valid json{")

        with pytest.raises(KilitFileError, match="Invalid JSON"):
            storage.load_envelope(envelope_path)


class TestSecurePermissions:
    """Test secure file permissions."""

    def test_set_secure_permissions_on_posix(self, tmp_path):
        """Set secure permissions on POSIX systems."""
        import platform

        test_file = tmp_path / "test.txt"
        test_file.write_text("test")

        result = set_secure_permissions(test_file)

        if platform.system() != "Windows":
            assert result is True
            # Check permissions are 0o600
            import stat

            mode = test_file.stat().st_mode
            assert stat.S_IMODE(mode) == 0o600
        else:
            # On Windows, just check it doesn't crash
            assert result in (True, False)

    def test_set_secure_permissions_on_nonexistent_file(self, tmp_path):
        """Setting permissions on non-existent file handles error gracefully."""
        test_file = tmp_path / "nonexistent.txt"

        # Should not crash, just return False
        try:
            result = set_secure_permissions(test_file)
            assert result is False
        except (FileNotFoundError, OSError):
            # Also acceptable - some platforms raise error
            pass


class TestTempFileCleanup:
    """Test that temp files are cleaned up properly."""

    def test_no_temp_files_left_after_atomic_write(self, tmp_path):
        """No temp files left after successful atomic write."""
        target = tmp_path / "test.txt"

        atomic_write(target, "content")

        # Check no temp files left
        all_files = list(tmp_path.iterdir())
        temp_files = [f for f in all_files if "tmp" in f.name.lower()]

        assert len(temp_files) == 0
        assert target in all_files
