# This is free software for the public good of a permacomputer hosted at
# permacomputer.com, an always-on computer by the people, for the people.
# One which is durable, easy to repair, & distributed like tap water
# for machine learning intelligence.
#
# The permacomputer is community-owned infrastructure optimized around
# four values:
#
#   TRUTH      First principles, math & science, open source code freely distributed
#   FREEDOM    Voluntary partnerships, freedom from tyranny & corporate control
#   HARMONY    Minimal waste, self-renewing systems with diverse thriving connections
#   LOVE       Be yourself without hurting others, cooperation through natural law
#
# This software contributes to that vision by archiving the web, preserving digital knowledge before it disappears.
# Code is seeds to sprout on any abandoned technology.

"""
Tests for filevault module (AsyncVault).

Tests async content-addressable storage with MD5 hashing.
"""

import pytest
import os
import tempfile
import shutil
import hashlib

from neopig.filevault import AsyncVault, Vault, content_hash, hash_to_path


class TestAsyncVaultBasics:
    """Test basic AsyncVault functionality."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_vault_initialization(self):
        """Test AsyncVault initializes correctly."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        assert vault._initialized is True
        assert os.path.exists(self.vault_path)

    @pytest.mark.asyncio
    async def test_vault_double_init(self):
        """Test that double initialization is safe."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()
        await vault.init()  # Should not raise

        assert vault._initialized is True


class TestAsyncVaultStorage:
    """Test AsyncVault storage operations."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_store_and_retrieve(self):
        """Test storing and retrieving data."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Hello, World! This is test image data."
        md5_hash = content_hash(data)

        # Store
        path = await vault.store(md5_hash, data, ext="jpg")
        assert path is not None

        # Retrieve
        retrieved = await vault.get(md5_hash)
        assert retrieved == data

    @pytest.mark.asyncio
    async def test_store_with_extension(self):
        """Test storing with file extension."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"PNG image data here"
        md5_hash = content_hash(data)

        path = await vault.store(md5_hash, data, ext="png")
        assert str(path).endswith(".png")

    @pytest.mark.asyncio
    async def test_store_without_extension(self):
        """Test storing without file extension."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Binary data without extension"
        md5_hash = content_hash(data)

        path = await vault.store(md5_hash, data)
        assert md5_hash in str(path)

    @pytest.mark.asyncio
    async def test_exists_true(self):
        """Test exists returns True for stored data."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Test data for exists check"
        md5_hash = content_hash(data)

        await vault.store(md5_hash, data)
        assert await vault.exists(md5_hash) is True

    @pytest.mark.asyncio
    async def test_exists_false(self):
        """Test exists returns False for missing data."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        fake_hash = "0" * 32
        assert await vault.exists(fake_hash) is False

    @pytest.mark.asyncio
    async def test_get_missing_returns_none(self):
        """Test get returns None for missing data."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        fake_hash = "0" * 32
        result = await vault.get(fake_hash)
        assert result is None


class TestAsyncVaultDeduplication:
    """Test AsyncVault deduplication behavior."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_same_data_same_path(self):
        """Test that same data stored twice goes to same path."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Duplicate test data"
        md5_hash = content_hash(data)

        path1 = await vault.store(md5_hash, data, ext="jpg")
        path2 = await vault.store(md5_hash, data, ext="jpg")

        assert path1 == path2

    @pytest.mark.asyncio
    async def test_different_data_different_path(self):
        """Test that different data goes to different paths."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data1 = b"First unique data"
        data2 = b"Second unique data"
        hash1 = content_hash(data1)
        hash2 = content_hash(data2)

        path1 = await vault.store(hash1, data1)
        path2 = await vault.store(hash2, data2)

        assert path1 != path2


class TestAsyncVaultDeletion:
    """Test AsyncVault deletion operations."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_delete_existing(self):
        """Test deleting existing file."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Data to be deleted"
        md5_hash = content_hash(data)

        await vault.store(md5_hash, data)
        assert await vault.exists(md5_hash) is True

        result = await vault.delete(md5_hash)
        assert result is True
        assert await vault.exists(md5_hash) is False

    @pytest.mark.asyncio
    async def test_delete_missing(self):
        """Test deleting non-existent file."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        fake_hash = "0" * 32
        result = await vault.delete(fake_hash)
        assert result is False


class TestAsyncVaultPath:
    """Test AsyncVault path operations."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_get_path_existing(self):
        """Test getting path for existing file."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Test data for path"
        md5_hash = content_hash(data)

        await vault.store(md5_hash, data, ext="bin")
        path = await vault.get_path(md5_hash)

        assert path is not None
        assert path.exists()
        assert md5_hash in str(path)

    @pytest.mark.asyncio
    async def test_get_path_missing(self):
        """Test getting path for missing file."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        fake_hash = "0" * 32
        path = await vault.get_path(fake_hash)
        assert path is None

    @pytest.mark.asyncio
    async def test_path_uses_hash_pairs(self):
        """Test that path uses 2-char hex pairs as subdirectories."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        data = b"Test data for subdir check"
        md5_hash = content_hash(data)
        # First two chars form first directory
        prefix = md5_hash[:2]

        path = await vault.store(md5_hash, data)
        path_str = str(path)

        # Path should contain the prefix as a subdirectory
        assert f"/{prefix}/" in path_str or f"\\{prefix}\\" in path_str


class TestAsyncVaultStats:
    """Test AsyncVault statistics."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_stats_empty_vault(self):
        """Test stats on empty vault."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        stats = await vault.stats()
        assert stats['count'] == 0
        assert stats['total_size'] == 0

    @pytest.mark.asyncio
    async def test_stats_with_files(self):
        """Test stats with files in vault."""
        vault = AsyncVault(vaultpath=self.vault_path)
        await vault.init()

        # Store some files
        for i in range(3):
            data = f"Test data {i}".encode()
            md5_hash = content_hash(data)
            await vault.store(md5_hash, data)

        stats = await vault.stats()
        assert stats['count'] == 3
        assert stats['total_size'] > 0


class TestSyncVault:
    """Test synchronous Vault."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.vault_path = os.path.join(self.temp_dir, "test_vault")

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    def test_sync_store_and_get(self):
        """Test sync vault store and get."""
        vault = Vault(vaultpath=self.vault_path)
        vault.init()

        data = b"Sync test data"
        md5_hash = content_hash(data)

        path = vault.store(md5_hash, data, ext="bin")
        assert path.exists()

        retrieved = vault.get(md5_hash)
        assert retrieved == data

    def test_sync_exists(self):
        """Test sync vault exists."""
        vault = Vault(vaultpath=self.vault_path)
        vault.init()

        data = b"Exists test"
        md5_hash = content_hash(data)

        assert vault.exists(md5_hash) is False
        vault.store(md5_hash, data)
        assert vault.exists(md5_hash) is True

    def test_sync_delete(self):
        """Test sync vault delete."""
        vault = Vault(vaultpath=self.vault_path)
        vault.init()

        data = b"Delete test"
        md5_hash = content_hash(data)

        vault.store(md5_hash, data)
        assert vault.delete(md5_hash) is True
        assert vault.exists(md5_hash) is False

    def test_sync_json_operations(self):
        """Test sync vault JSON operations."""
        vault = Vault(vaultpath=self.vault_path)
        vault.init()

        path = vault.create_filename("test-key", ext="json")
        data = {"key": "value", "number": 42}

        vault.write_json(path, data)
        loaded = vault.read_json(path)

        assert loaded == data

    def test_sync_text_operations(self):
        """Test sync vault text operations."""
        vault = Vault(vaultpath=self.vault_path)
        vault.init()

        path = vault.create_filename("text-key", ext="txt")
        content = "Hello, World!"

        vault.write_text(path, content)
        loaded = vault.read_text(path)

        assert loaded == content


class TestHashToPath:
    """Test hash_to_path utility function."""

    def test_basic_hash(self):
        """Test basic hash to path conversion."""
        h = "abcdef1234567890abcdef1234567890"
        path = hash_to_path(h, depth=3)
        assert str(path) == "ab/cd/ef/abcdef1234567890abcdef1234567890"

    def test_with_extension(self):
        """Test hash to path with extension."""
        h = "abcdef1234567890abcdef1234567890"
        path = hash_to_path(h, depth=3, ext="jpg")
        assert str(path) == "ab/cd/ef/abcdef1234567890abcdef1234567890.jpg"

    def test_depth_9(self):
        """Test default depth of 9."""
        h = "abcdef1234567890abcdef1234567890"
        path = hash_to_path(h, depth=9)
        expected = "ab/cd/ef/12/34/56/78/90/ab/abcdef1234567890abcdef1234567890"
        assert str(path) == expected


class TestContentHash:
    """Test content_hash utility function."""

    def test_content_hash(self):
        """Test content hash generation."""
        data = b"Hello, World!"
        h = content_hash(data)
        assert h == hashlib.md5(data).hexdigest()
        assert len(h) == 32


if __name__ == '__main__':
    pytest.main([__file__, '-v'])
