"""Tests for storage module."""

import os
import stat
import sys
import tempfile
from pathlib import Path

import pytest

from aqua_mcp.storage import Storage, WalletData, Config, _validate_wallet_name


@pytest.fixture
def temp_storage():
    """Create a temporary storage instance."""
    with tempfile.TemporaryDirectory() as tmpdir:
        yield Storage(Path(tmpdir))


class TestStorage:
    """Tests for Storage class."""

    def test_init_creates_directories(self, temp_storage):
        """Test that initialization creates required directories."""
        assert temp_storage.base_dir.exists()
        assert temp_storage.wallets_dir.exists()
        assert temp_storage.cache_dir.exists()

    def test_config_save_load(self, temp_storage):
        """Test saving and loading config."""
        config = Config(network="testnet", default_wallet="test")
        temp_storage.save_config(config)
        
        loaded = temp_storage.load_config()
        assert loaded.network == "testnet"
        assert loaded.default_wallet == "test"

    def test_wallet_save_load(self, temp_storage):
        """Test saving and loading wallet."""
        wallet = WalletData(
            name="test",
            network="mainnet",
            descriptor="ct(...)",
        )
        temp_storage.save_wallet(wallet)
        
        assert temp_storage.wallet_exists("test")
        
        loaded = temp_storage.load_wallet("test")
        assert loaded.name == "test"
        assert loaded.network == "mainnet"
        assert loaded.descriptor == "ct(...)"

    def test_list_wallets(self, temp_storage):
        """Test listing wallets."""
        assert temp_storage.list_wallets() == []
        
        wallet1 = WalletData(name="w1", network="mainnet", descriptor="ct1")
        wallet2 = WalletData(name="w2", network="testnet", descriptor="ct2")
        temp_storage.save_wallet(wallet1)
        temp_storage.save_wallet(wallet2)
        
        wallets = temp_storage.list_wallets()
        assert set(wallets) == {"w1", "w2"}

    def test_delete_wallet(self, temp_storage):
        """Test deleting wallet."""
        wallet = WalletData(name="todelete", network="mainnet", descriptor="ct")
        temp_storage.save_wallet(wallet)

        assert temp_storage.wallet_exists("todelete")
        assert temp_storage.delete_wallet("todelete")
        assert not temp_storage.wallet_exists("todelete")

    def test_delete_wallet_removes_cache(self, temp_storage):
        """Deleting a wallet also removes its cache directory."""
        wallet = WalletData(name="withcache", network="mainnet", descriptor="ct")
        temp_storage.save_wallet(wallet)
        cache_path = temp_storage.get_cache_path("withcache")
        # Create a dummy file inside the cache to verify rmtree
        (cache_path / "dummy.db").touch()
        assert cache_path.exists()

        temp_storage.delete_wallet("withcache")
        assert not temp_storage.wallet_exists("withcache")
        assert not cache_path.exists()

    def test_mnemonic_encryption(self, temp_storage):
        """Test mnemonic encryption/decryption."""
        mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
        passphrase = "test123"
        
        encrypted = temp_storage.encrypt_mnemonic(mnemonic, passphrase)
        assert encrypted != mnemonic
        
        decrypted = temp_storage.decrypt_mnemonic(encrypted, passphrase)
        assert decrypted == mnemonic

    def test_mnemonic_wrong_passphrase(self, temp_storage):
        """Test that wrong passphrase fails."""
        mnemonic = "test mnemonic"
        encrypted = temp_storage.encrypt_mnemonic(mnemonic, "correct")

        with pytest.raises(Exception):
            temp_storage.decrypt_mnemonic(encrypted, "wrong")


class TestWalletNameValidation:
    """Tests for wallet name validation (path traversal prevention)."""

    @pytest.mark.parametrize("name", ["default", "my-wallet", "wallet_1", "A", "a" * 64])
    def test_valid_names(self, name):
        """Valid wallet names should pass validation."""
        assert _validate_wallet_name(name) == name

    @pytest.mark.parametrize("name", [
        "../../etc/passwd",
        "../evil",
        "wallet/name",
        "wallet.json",
        "",
        "a" * 65,
        "hello world",
        "/absolute",
        "wallet\x00evil",
    ])
    def test_invalid_names(self, name):
        """Invalid wallet names should raise ValueError."""
        with pytest.raises(ValueError, match="Invalid wallet name"):
            _validate_wallet_name(name)

    def test_path_traversal_blocked_on_save(self, temp_storage):
        """Path traversal in wallet name should be blocked during save."""
        wallet = WalletData(
            name="../../etc/evil",
            network="mainnet",
            descriptor="ct(...)",
        )
        with pytest.raises(ValueError, match="Invalid wallet name"):
            temp_storage.save_wallet(wallet)

    def test_path_traversal_blocked_on_load(self, temp_storage):
        """Path traversal in wallet name should be blocked during load."""
        with pytest.raises(ValueError, match="Invalid wallet name"):
            temp_storage.load_wallet("../evil")

    def test_path_traversal_blocked_on_cache(self, temp_storage):
        """Path traversal in wallet name should be blocked on cache path."""
        with pytest.raises(ValueError, match="Invalid wallet name"):
            temp_storage.get_cache_path("../../tmp/evil")


class TestSwapStorage:
    """Tests for swap persistence (Layer 4)."""

    def _make_swap(self, **overrides):
        from aqua_mcp.boltz import SwapInfo

        defaults = {
            "swap_id": "test_swap_123",
            "address": "lq1qqexampleaddress",
            "expected_amount": 50069,
            "claim_public_key": "03" + "ab" * 32,
            "swap_tree": {"claimLeaf": {}, "refundLeaf": {}},
            "timeout_block_height": 2500000,
            "refund_private_key": "aa" * 32,
            "refund_public_key": "03" + "cc" * 32,
            "invoice": "lnbc500u1ptest...",
            "status": "swap.created",
            "network": "mainnet",
            "created_at": "2026-03-05T12:00:00",
        }
        defaults.update(overrides)
        return SwapInfo(**defaults)

    def test_swaps_dir_created_on_init(self, temp_storage):
        """Storage init creates swaps/ directory."""
        assert temp_storage.swaps_dir.exists()

    def test_save_and_load_swap(self, temp_storage):
        """SwapInfo saved can be loaded back correctly."""
        swap = self._make_swap()
        temp_storage.save_swap(swap)

        loaded = temp_storage.load_swap("test_swap_123")
        assert loaded is not None
        assert loaded.swap_id == swap.swap_id
        assert loaded.address == swap.address
        assert loaded.expected_amount == swap.expected_amount
        assert loaded.status == swap.status
        assert loaded.refund_private_key == swap.refund_private_key

    def test_load_swap_not_found_returns_none(self, temp_storage):
        """load_swap with nonexistent ID returns None."""
        result = temp_storage.load_swap("nonexistent")
        assert result is None

    def test_list_swaps_empty(self, temp_storage):
        """list_swaps returns empty list when no swaps."""
        assert temp_storage.list_swaps() == []

    def test_list_swaps_returns_ids(self, temp_storage):
        """list_swaps returns all saved swap IDs."""
        swap1 = self._make_swap(swap_id="swap_aaa")
        swap2 = self._make_swap(swap_id="swap_bbb")
        temp_storage.save_swap(swap1)
        temp_storage.save_swap(swap2)

        ids = temp_storage.list_swaps()
        assert set(ids) == {"swap_aaa", "swap_bbb"}

    def test_save_swap_updates_existing(self, temp_storage):
        """Saving swap with same ID overwrites previous data."""
        swap = self._make_swap(status="swap.created")
        temp_storage.save_swap(swap)

        swap.status = "transaction.mempool"
        swap.lockup_txid = "dd" * 32
        temp_storage.save_swap(swap)

        loaded = temp_storage.load_swap("test_swap_123")
        assert loaded.status == "transaction.mempool"
        assert loaded.lockup_txid == "dd" * 32

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix mode bits")
    def test_swap_file_permissions(self, temp_storage):
        """Swap files are created with 0o600 permissions."""
        swap = self._make_swap()
        temp_storage.save_swap(swap)

        swap_path = temp_storage.swaps_dir / "test_swap_123.json"
        mode = stat.S_IMODE(os.stat(swap_path).st_mode)
        assert mode == 0o600, f"Expected 0600, got {oct(mode)}"


class TestFilePermissions:
    """Tests for restrictive file permissions."""

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix mode bits")
    def test_wallet_file_permissions(self, temp_storage):
        """Wallet files should be created with 0600 permissions."""
        wallet = WalletData(name="secure", network="mainnet", descriptor="ct(...)")
        temp_storage.save_wallet(wallet)

        path = temp_storage._wallet_path("secure")
        mode = stat.S_IMODE(os.stat(path).st_mode)
        assert mode == 0o600, f"Expected 0600, got {oct(mode)}"

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix mode bits")
    def test_config_file_permissions(self, temp_storage):
        """Config file should be created with 0600 permissions."""
        config = Config(network="testnet")
        temp_storage.save_config(config)

        mode = stat.S_IMODE(os.stat(temp_storage.config_path).st_mode)
        assert mode == 0o600, f"Expected 0600, got {oct(mode)}"

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix mode bits")
    def test_directory_permissions(self):
        """Directories should be created with 0700 permissions."""
        with tempfile.TemporaryDirectory() as tmpdir:
            base = Path(tmpdir) / "new_wallet_dir"
            storage = Storage(base)

            for d in [storage.base_dir, storage.wallets_dir, storage.cache_dir]:
                mode = stat.S_IMODE(os.stat(d).st_mode)
                assert mode == 0o700, f"Expected 0700 for {d}, got {oct(mode)}"


class TestLightningSwapStorage:
    """Tests for Lightning swap storage."""

    def test_lightning_swaps_dir_created(self, temp_storage):
        """Lightning swaps directory is created on init."""
        assert temp_storage.lightning_swaps_dir.exists()
        assert temp_storage.lightning_swaps_dir.is_dir()

    def test_save_load_lightning_swap_receive(self, temp_storage):
        """Test saving and loading a receive Lightning swap."""
        from aqua_mcp.lightning import LightningSwap
        from datetime import datetime, UTC

        swap = LightningSwap(
            swap_id="ankara_uuid_123",
            swap_type="receive",
            provider="ankara",
            invoice="lnbc...",
            amount=100000,
            wallet_name="default",
            status="pending",
            network="mainnet",
            created_at=datetime.now(UTC).isoformat(),
            receive_address="lq1...",
        )
        temp_storage.save_lightning_swap(swap)

        loaded = temp_storage.load_lightning_swap("ankara_uuid_123")
        assert loaded is not None
        assert loaded.swap_id == "ankara_uuid_123"
        assert loaded.swap_type == "receive"
        assert loaded.provider == "ankara"
        assert loaded.receive_address == "lq1..."

    def test_save_load_lightning_swap_send(self, temp_storage):
        """Test saving and loading a send Lightning swap."""
        from aqua_mcp.lightning import LightningSwap
        from datetime import datetime, UTC

        swap = LightningSwap(
            swap_id="boltz_swap_456",
            swap_type="send",
            provider="boltz",
            invoice="lnbc...",
            amount=50069,
            wallet_name="default",
            status="processing",
            network="mainnet",
            created_at=datetime.now(UTC).isoformat(),
            lockup_txid="abc123",
            timeout_block_height=2500000,
            refund_private_key="secret_key",
        )
        temp_storage.save_lightning_swap(swap)

        loaded = temp_storage.load_lightning_swap("boltz_swap_456")
        assert loaded is not None
        assert loaded.swap_type == "send"
        assert loaded.lockup_txid == "abc123"
        assert loaded.refund_private_key == "secret_key"

    def test_load_lightning_swap_not_found(self, temp_storage):
        """Loading non-existent swap returns None."""
        loaded = temp_storage.load_lightning_swap("nonexistent")
        assert loaded is None

    def test_list_lightning_swaps(self, temp_storage):
        """Test listing all Lightning swap IDs."""
        from aqua_mcp.lightning import LightningSwap
        from datetime import datetime, UTC

        assert temp_storage.list_lightning_swaps() == []

        swap1 = LightningSwap(
            swap_id="swap_1",
            swap_type="receive",
            provider="ankara",
            invoice="lnbc...",
            amount=100000,
            wallet_name="default",
            status="pending",
            network="mainnet",
            created_at=datetime.now(UTC).isoformat(),
        )
        swap2 = LightningSwap(
            swap_id="swap_2",
            swap_type="send",
            provider="boltz",
            invoice="lnbc...",
            amount=100000,
            wallet_name="default",
            status="processing",
            network="mainnet",
            created_at=datetime.now(UTC).isoformat(),
        )
        temp_storage.save_lightning_swap(swap1)
        temp_storage.save_lightning_swap(swap2)

        swaps = temp_storage.list_lightning_swaps()
        assert set(swaps) == {"swap_1", "swap_2"}

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix mode bits")
    def test_lightning_swap_file_permissions(self, temp_storage):
        """Lightning swap files should be created with 0600 permissions."""
        from aqua_mcp.lightning import LightningSwap
        from datetime import datetime, UTC

        swap = LightningSwap(
            swap_id="secure_swap",
            swap_type="send",
            provider="boltz",
            invoice="lnbc...",
            amount=100000,
            wallet_name="default",
            status="pending",
            network="mainnet",
            created_at=datetime.now(UTC).isoformat(),
            refund_private_key="secret",
        )
        temp_storage.save_lightning_swap(swap)

        path = temp_storage._lightning_swap_path("secure_swap")
        mode = stat.S_IMODE(os.stat(path).st_mode)
        assert mode == 0o600, f"Expected 0600, got {oct(mode)}"
