import sys
import os
import json
import pytest
import subprocess
from pathlib import Path

# Add src to path at the very top
TEST_DIR = Path(__file__).parent
SRC_DIR = TEST_DIR.parent / "src"
sys.path.insert(0, str(SRC_DIR))

import pyhdwallet
from bip_utils import Bip39SeedGenerator

DATA_DIR = TEST_DIR / "data"
VECTORS_FILE = TEST_DIR / "vectors.json"

@pytest.fixture
def vectors():
    if not VECTORS_FILE.exists():
        pytest.fail("tests/vectors.json missing. Run tests/bootstrap_vectors.py first.")
    with open(VECTORS_FILE, "r") as f:
        return json.load(f)

@pytest.fixture
def recipient_key_content():
    path = DATA_DIR / "recipient.asc"
    if not path.exists():
        pytest.fail("tests/data/recipient.asc missing.")
    with open(path, "r", encoding="utf-8") as f:
        return f.read()

def test_pgp_fingerprint_calculation(vectors, recipient_key_content):
    """
    Verifies that pgp_fingerprint computes the expected fingerprint 
    for the stored recipient key.
    """
    expected = vectors["pgp"]["expected_fingerprint"]
    actual = pyhdwallet.pgp_fingerprint(recipient_key_content)
    assert actual == expected, "Fingerprint calculation logic has changed!"

def test_pgp_require_fingerprint_match(vectors):
    """
    Verifies the safety check require_fingerprint_match enforces exact matches.
    """
    expected = vectors["pgp"]["expected_fingerprint"]
    
    # Should not raise
    pyhdwallet.require_fingerprint_match(expected, expected, "test")
    
    # Should raise on mismatch
    wrong = expected.replace("A", "B") if "A" in expected else expected.replace("0", "1")
    with pytest.raises(ValueError, match="test: PGP fingerprint mismatch"):
        pyhdwallet.require_fingerprint_match(wrong, expected, "test")

def test_bip39_seed_derivation(vectors):
    """
    Verifies that mnemonics convert to seeds exactly as they did at bootstrap time.
    """
    for case in vectors["bip39"]:
        mnemonic = case["mnemonic"]
        passphrase = case["passphrase"]
        expected_hex = case["expected_seed_hex"]
        
        # Verify internal Bip39SeedGenerator usage matches expected hex
        actual_bytes = Bip39SeedGenerator(mnemonic).Generate(passphrase)
        assert actual_bytes.hex() == expected_hex

def test_address_derivation_integrity(vectors):
    """
    Verifies derive_all produces the exact same addresses for supported chains.
    """
    for case in vectors["bip39"]:
        seed_hex = case["expected_seed_hex"]
        seed_bytes = bytes.fromhex(seed_hex)
        
        for profile, expected_addresses in case["derived_addresses"].items():
            # Infer count from ethereum addresses in the expected data
            # (Ethereum always generates `count` addresses, unlike Solana which depends on profile)
            count = len(expected_addresses.get("ethereum", []))
            
            result = pyhdwallet.derive_all(
                seed_bytes, 
                chains=["ethereum", "bitcoin", "solana"], 
                count=count,
                sol_profile=profile,
                export_private=False
            )
            assert result["addresses"] == expected_addresses, f"Mismatch for profile {profile}"

def test_bip85_derivation(vectors):
    """
    Verifies BIP85 child mnemonic derivation matches expected test vectors.
    
    Tests:
    - Path construction
    - Entropy64 derivation (HMAC-SHA512)
    - Entropy truncation
    - Child mnemonic generation
    """
    from bip_utils import Bip39SeedGenerator
    
    for case in vectors["bip85"]:
        master_mnemonic = case["master_mnemonic"]
        master_passphrase = case["master_passphrase"]
        child_words = case["child_words"]
        index = case["index"]
        
        # Generate master seed
        master_seed = Bip39SeedGenerator(master_mnemonic).Generate(master_passphrase)
        
        # Derive child using BIP85
        path, child_mnemonic, entropy64, truncated_entropy = pyhdwallet.bip85_derive_child_mnemonic(
            master_seed,
            child_words,
            index
        )
        
        # Assert path
        assert path == case["bip85_path"], f"Path mismatch: {path} != {case['bip85_path']}"
        
        # Assert entropy64 (full 64-byte HMAC output)
        assert entropy64.hex() == case["expected_entropy64_hex"], \
            f"Entropy64 mismatch for {case['description']}"
        
        # Assert truncated entropy
        assert truncated_entropy.hex() == case["expected_entropy_truncated_hex"], \
            f"Truncated entropy mismatch for {case['description']}"
        
        # Assert child mnemonic
        assert child_mnemonic == case["expected_child_mnemonic"], \
            f"Child mnemonic mismatch for {case['description']}"


def test_cli_gen_child_smoke(tmp_path, vectors):
    """
    CLI smoke test for gen-child command.
    
    Verifies:
    - Command runs without error
    - ZIP file is created
    - Off-screen mode works
    - File output works
    """
    vector = vectors["bip85"][0]  # Use first BIP85 test case
    master_mnemonic = vector["master_mnemonic"]
    
    cmd = [
        sys.executable, "src/pyhdwallet.py", "gen-child",
        "--mnemonic-stdin",
        "--index", "0",
        "--words", "12",
        "--off-screen",
        "--file",
        "--zip-password-mode", "auto",
        "--wallet-location", str(tmp_path)
    ]
    
    result = subprocess.run(
        cmd,
        input=master_mnemonic.encode("utf-8"),
        capture_output=True
    )
    
    assert result.returncode == 0, f"CLI gen-child failed: {result.stderr.decode()}"
    
    # Check ZIP file created
    zips = list(tmp_path.glob("*.zip"))
    assert len(zips) == 1, f"Expected exactly one zip file, found {len(zips)}"
    
    # Check filename pattern
    zip_name = zips[0].name
    assert zip_name.startswith("bip85_child_"), f"Unexpected zip name: {zip_name}"


def test_cli_recover_smoke(tmp_path, vectors):
    """
    Runs the CLI in a subprocess to verify end-to-end wiring 
    without network (recover mode).
    """
    vector = vectors["bip39"][0]
    mnemonic = vector["mnemonic"]
    expected_fp = vectors["pgp"]["expected_fingerprint"]
    recipient_file = DATA_DIR / "recipient.asc"
    
    # 1. Successful Recovery
    cmd = [
        sys.executable, "src/pyhdwallet.py", "recover",
        "--mnemonic-stdin",
        "--pgp-pubkey-file", str(recipient_file),
        "--expected-fingerprint", expected_fp,
        "--file", 
        "--wallet-location", str(tmp_path),
        "--off-screen",
        "--zip-password-mode", "auto"
    ]
    
    result = subprocess.run(
        cmd,
        input=mnemonic.encode("utf-8"),
        capture_output=True
    )
    
    assert result.returncode == 0, f"CLI failed: {result.stderr.decode()}"
    zips = list(tmp_path.glob("*.zip"))
    assert len(zips) == 1, "Expected exactly one zip file output"
    
    # 2. Failed Recovery (Wrong Fingerprint)
    wrong_fp = expected_fp.replace("A", "B") 
    cmd_fail = list(cmd)
    cmd_fail[cmd_fail.index(expected_fp)] = wrong_fp
    
    result_fail = subprocess.run(
        cmd_fail,
        input=mnemonic.encode("utf-8"),
        capture_output=True
    )
    
    assert result_fail.returncode != 0
    assert b"fingerprint mismatch" in result_fail.stderr or b"fingerprint mismatch" in result_fail.stdout
