"""Tests for Ed25519 attestation signing and verification."""

from __future__ import annotations

import base64
import json

import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    NoEncryption,
    PrivateFormat,
    PublicFormat,
)

from complyform.core.attestation.builder import build_attestation
from complyform.core.attestation.signer import (
    build_dsse_envelope,
    sign_payload,
    verify_signature,
)
from complyform.core.errors import (
    AttestationKeyNotFoundError,
    AttestationVerificationFailedError,
)


@pytest.fixture
def ed25519_keypair() -> tuple[bytes, bytes]:
    """Generate a test Ed25519 key pair (private PEM, public PEM)."""
    private_key = Ed25519PrivateKey.generate()
    private_pem = private_key.private_bytes(
        Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()
    )
    public_pem = private_key.public_key().public_bytes(
        Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
    )
    return private_pem, public_pem


@pytest.fixture
def private_key_pem(ed25519_keypair: tuple[bytes, bytes]) -> bytes:
    return ed25519_keypair[0]


@pytest.fixture
def public_key_pem(ed25519_keypair: tuple[bytes, bytes]) -> bytes:
    return ed25519_keypair[1]


def _make_assessment_json() -> dict:
    return {
        "schema_version": 1,
        "tool": "complyform",
        "tool_version": "0.1.0",
        "command": "assess",
        "timestamp": "2025-01-01T00:00:00+00:00",
        "cloud": "gcp",
        "scan_source": "state",
        "state_hash": "sha256:abc123",
        "frameworks": ["soc2"],
        "summary": {
            "total_controls": 10,
            "passed": 7,
            "failed": 2,
            "manual_review": 1,
            "not_assessed": 0,
            "score": 77.8,
            "severity_counts": {"MEDIUM": 8, "HIGH": 2},
        },
        "findings": [],
        "metadata": {
            "resource_count": 5,
            "assessment_duration_ms": 150,
            "tier": "community",
            "cache_hit": True,
            "provider_versions": {"google": "5.0.0"},
        },
        "cloud_intelligence": None,
        "upgrade_hints": None,
    }


class TestSignAndVerify:
    def test_sign_and_verify_roundtrip(
        self, private_key_pem: bytes, public_key_pem: bytes
    ) -> None:
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
        from cryptography.hazmat.primitives.serialization import load_pem_public_key

        payload = b"test payload data"
        signature = sign_payload(payload, private_key_pem)
        assert len(signature) == 64  # Ed25519 signatures are 64 bytes

        pub_key = load_pem_public_key(public_key_pem)
        assert isinstance(pub_key, Ed25519PublicKey)
        assert verify_signature(payload, signature, pub_key) is True

    def test_verify_invalid_signature(self, public_key_pem: bytes) -> None:
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
        from cryptography.hazmat.primitives.serialization import load_pem_public_key

        payload = b"test payload data"
        bad_sig = b"\x00" * 64

        pub_key = load_pem_public_key(public_key_pem)
        assert isinstance(pub_key, Ed25519PublicKey)
        assert verify_signature(payload, bad_sig, pub_key) is False


class TestBuildDSSEEnvelope:
    def test_build_dsse_envelope_signed(self, private_key_pem: bytes) -> None:
        statement = '{"_type":"https://in-toto.io/Statement/v1","test":"data"}'
        envelope = build_dsse_envelope(statement, private_key_pem)

        assert envelope["payloadType"] == "application/vnd.in-toto+json"
        assert len(envelope["signatures"]) == 1
        assert "sig" in envelope["signatures"][0]
        assert "keyid" in envelope["signatures"][0]

        # Verify the payload round-trips
        decoded = base64.b64decode(envelope["payload"]).decode("utf-8")
        assert decoded == statement

    def test_build_dsse_envelope_unsigned(self) -> None:
        statement = '{"_type":"https://in-toto.io/Statement/v1","test":"data"}'
        envelope = build_dsse_envelope(statement, None)

        assert envelope["payloadType"] == "application/vnd.in-toto+json"
        assert envelope["signatures"] == []

        decoded = base64.b64decode(envelope["payload"]).decode("utf-8")
        assert decoded == statement


class TestBuildAttestationSigned:
    def test_build_attestation_signed(self, private_key_pem: bytes) -> None:
        assessment = _make_assessment_json()
        envelope = build_attestation(
            assessment, "./assessment.json", signing_key_pem=private_key_pem
        )

        assert len(envelope["signatures"]) == 1
        assert envelope["payloadType"] == "application/vnd.in-toto+json"

        payload_bytes = base64.b64decode(envelope["payload"])
        statement = json.loads(payload_bytes)
        assert statement["_type"] == "https://in-toto.io/Statement/v1"

    def test_build_attestation_unsigned(self) -> None:
        assessment = _make_assessment_json()
        envelope = build_attestation(assessment, "./assessment.json")

        assert envelope["signatures"] == []


class TestVerifyCmd:
    def test_verify_cmd_pass(
        self, private_key_pem: bytes, public_key_pem: bytes, tmp_path: object
    ) -> None:
        """Signed attestation with matching assessment should PASS."""
        from pathlib import Path
        from unittest.mock import patch as mock_patch

        from cryptography.hazmat.primitives.serialization import load_pem_public_key

        tmp = Path(str(tmp_path))

        assessment = _make_assessment_json()
        from complyform.core.attestation.canonical import canonical_json

        assess_bytes = canonical_json(assessment)
        assess_path = tmp / "assessment.json"
        assess_path.write_bytes(assess_bytes)

        envelope = build_attestation(
            assessment, str(assess_path), signing_key_pem=private_key_pem
        )
        att_path = tmp / "attestation.json"
        att_path.write_text(json.dumps(envelope), encoding="utf-8")

        pub_key = load_pem_public_key(public_key_pem)

        from complyform.cli.verify_cmd import _verify

        with mock_patch(
            "complyform.core.attestation.signer.load_public_key", return_value=pub_key
        ):
            result = _verify(str(att_path), str(assess_path))

        assert result["status"] == "PASS"
        assert result["signed"] is True
        assert result["signature_valid"] is True
        assert result["hash_match"] is True

    def test_verify_cmd_partial(self, tmp_path: object) -> None:
        """Unsigned attestation with matching hash should be PARTIAL."""
        from pathlib import Path

        tmp = Path(str(tmp_path))

        assessment = _make_assessment_json()
        from complyform.core.attestation.canonical import canonical_json

        assess_bytes = canonical_json(assessment)
        assess_path = tmp / "assessment.json"
        assess_path.write_bytes(assess_bytes)

        envelope = build_attestation(assessment, str(assess_path))
        att_path = tmp / "attestation.json"
        att_path.write_text(json.dumps(envelope), encoding="utf-8")

        from complyform.cli.verify_cmd import _verify

        result = _verify(str(att_path), str(assess_path))

        assert result["status"] == "PARTIAL"
        assert result["signed"] is False
        assert result["hash_match"] is True

    def test_verify_cmd_fail_signature(
        self, private_key_pem: bytes, public_key_pem: bytes, tmp_path: object
    ) -> None:
        """Signed attestation with tampered signature should FAIL."""
        from pathlib import Path
        from unittest.mock import patch as mock_patch

        from cryptography.hazmat.primitives.serialization import load_pem_public_key

        tmp = Path(str(tmp_path))

        assessment = _make_assessment_json()
        envelope = build_attestation(
            assessment, "./assessment.json", signing_key_pem=private_key_pem
        )

        # Tamper with the signature
        envelope["signatures"][0]["sig"] = base64.b64encode(b"\x00" * 64).decode()

        att_path = tmp / "attestation.json"
        att_path.write_text(json.dumps(envelope), encoding="utf-8")

        pub_key = load_pem_public_key(public_key_pem)

        from complyform.cli.verify_cmd import _verify

        with mock_patch(
            "complyform.core.attestation.signer.load_public_key", return_value=pub_key
        ):
            result = _verify(str(att_path), None)

        assert result["status"] == "FAIL"
        assert result["signed"] is True
        assert result["signature_valid"] is False

    def test_verify_cmd_fail_hash(
        self, private_key_pem: bytes, public_key_pem: bytes, tmp_path: object
    ) -> None:
        """Signed attestation with mismatched assessment hash should FAIL."""
        from pathlib import Path
        from unittest.mock import patch as mock_patch

        from cryptography.hazmat.primitives.serialization import load_pem_public_key

        tmp = Path(str(tmp_path))

        assessment = _make_assessment_json()
        envelope = build_attestation(
            assessment, "./assessment.json", signing_key_pem=private_key_pem
        )

        att_path = tmp / "attestation.json"
        att_path.write_text(json.dumps(envelope), encoding="utf-8")

        # Write a different assessment file
        assess_path = tmp / "assessment.json"
        assess_path.write_text('{"different": "content"}', encoding="utf-8")

        pub_key = load_pem_public_key(public_key_pem)

        from complyform.cli.verify_cmd import _verify

        with mock_patch(
            "complyform.core.attestation.signer.load_public_key", return_value=pub_key
        ):
            result = _verify(str(att_path), str(assess_path))

        assert result["status"] == "FAIL"
        assert result["hash_match"] is False


class TestAttestationErrors:
    def test_attestation_key_not_found_error(self) -> None:
        err = AttestationKeyNotFoundError()
        assert err.error_code == 90
        assert "key" in err.message.lower() or "signing" in err.message.lower()

    def test_attestation_verification_failed_error(self) -> None:
        err = AttestationVerificationFailedError("test reason")
        assert err.error_code == 91
        assert "test reason" in err.message
