import hashlib
import tempfile
from pathlib import Path

import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives import serialization

from mrp.auth import Keypair, _b64url_encode, _b64url_decode


class TestB64Url:
    def test_round_trip(self):
        data = b"hello world"
        encoded = _b64url_encode(data)
        decoded = _b64url_decode(encoded)
        assert decoded == data

    def test_no_padding(self):
        # Various lengths to ensure no '=' in output
        for i in range(1, 50):
            encoded = _b64url_encode(b"x" * i)
            assert "=" not in encoded


class TestKeypair:
    def test_generate_public_key_length(self):
        kp = Keypair.generate()
        assert len(kp.public_key_b64) == 43  # 32 bytes -> 43 chars base64url no padding

    def test_generate_public_key_raw_length(self):
        kp = Keypair.generate()
        assert len(kp.public_key) == 32

    def test_seed_length(self):
        kp = Keypair.generate()
        assert len(kp.seed) == 32

    def test_from_seed_deterministic(self):
        seed = b"\x01" * 32
        kp1 = Keypair.from_seed(seed)
        kp2 = Keypair.from_seed(seed)
        assert kp1.public_key == kp2.public_key
        assert kp1.public_key_b64 == kp2.public_key_b64

    def test_different_seeds_different_keys(self):
        kp1 = Keypair.from_seed(b"\x01" * 32)
        kp2 = Keypair.from_seed(b"\x02" * 32)
        assert kp1.public_key != kp2.public_key

    def test_sign_and_verify(self):
        kp = Keypair.generate()
        data = b"test message"
        sig = kp.sign(data)
        assert len(sig) == 64  # Ed25519 signature is 64 bytes

        # Verify using cryptography library
        pub = Ed25519PublicKey.from_public_bytes(kp.public_key)
        pub.verify(sig, data)  # Raises if invalid

    def test_sign_wrong_key_fails(self):
        kp1 = Keypair.generate()
        kp2 = Keypair.generate()
        data = b"test message"
        sig = kp1.sign(data)

        pub = Ed25519PublicKey.from_public_bytes(kp2.public_key)
        with pytest.raises(Exception):
            pub.verify(sig, data)

    def test_save_and_load(self, tmp_path):
        kp1 = Keypair.generate()
        key_path = str(tmp_path / "test.key")
        kp1.save(key_path)

        # File should be 32 bytes (seed)
        assert Path(key_path).stat().st_size == 32

        kp2 = Keypair.from_file(key_path)
        assert kp1.public_key == kp2.public_key
        assert kp1.seed == kp2.seed

    def test_from_file_missing_raises(self):
        with pytest.raises(FileNotFoundError):
            Keypair.from_file("/nonexistent/path/key.bin")

    def test_from_file_create(self, tmp_path):
        key_path = str(tmp_path / "new.key")
        kp = Keypair.from_file(key_path, create=True)
        assert len(kp.public_key_b64) == 43
        assert Path(key_path).exists()

        # Loading again should give same key
        kp2 = Keypair.from_file(key_path)
        assert kp.public_key == kp2.public_key


class TestSignRequest:
    def test_canonical_string_format(self):
        kp = Keypair.generate()
        # sign_request should produce a valid base64url signature
        sig = kp.sign_request("POST", "/v1/messages", b'{"test": true}', "2026-03-05T12:00:00Z")
        assert isinstance(sig, str)
        assert "=" not in sig  # no padding

        # Decode and verify
        sig_bytes = _b64url_decode(sig)
        assert len(sig_bytes) == 64

    def test_empty_body(self):
        kp = Keypair.generate()
        sig1 = kp.sign_request("GET", "/v1/messages", None, "2026-03-05T12:00:00Z")
        sig2 = kp.sign_request("GET", "/v1/messages", b"", "2026-03-05T12:00:00Z")
        # None and b"" should produce identical signatures (both hash empty bytes)
        assert sig1 == sig2

    def test_body_hash_known_value(self):
        # SHA-256 of empty string
        empty_hash = _b64url_encode(hashlib.sha256(b"").digest())
        assert empty_hash == "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"

    def test_verify_signature(self):
        kp = Keypair.generate()
        method = "POST"
        path = "/v1/messages"
        body = b'{"hello": "world"}'
        timestamp = "2026-03-05T12:00:00Z"

        sig_b64 = kp.sign_request(method, path, body, timestamp)

        # Reconstruct canonical string and verify
        body_hash = _b64url_encode(hashlib.sha256(body).digest())
        canonical = f"{method}\n{path}\n{timestamp}\n{body_hash}"

        sig_bytes = _b64url_decode(sig_b64)
        pub = Ed25519PublicKey.from_public_bytes(kp.public_key)
        pub.verify(sig_bytes, canonical.encode())  # Raises if invalid
