"""Tests for certificate management."""
import pytest

try:
    from cryptography import x509
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False

from greymatter_core.cluster.certs import (
    CertificateError,
    generate_ca,
    is_revoked,
    issue_node_cert,
    revoke_cert,
    save_cert_files,
    verify_cert,
)

pytestmark = pytest.mark.skipif(
    not CRYPTO_AVAILABLE,
    reason="cryptography library not installed",
)


class TestGenerateCA:
    def test_generates_pem_bytes(self):
        cert_pem, key_pem = generate_ca()
        assert cert_pem.startswith(b"-----BEGIN CERTIFICATE-----")
        assert key_pem.startswith(b"-----BEGIN PRIVATE KEY-----")

    def test_ca_has_correct_cn(self):
        cert_pem, _ = generate_ca(common_name="Test CA")
        cert = x509.load_pem_x509_certificate(cert_pem)
        cn = cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)
        assert cn[0].value == "Test CA"

    def test_ca_is_ca(self):
        cert_pem, _ = generate_ca()
        cert = x509.load_pem_x509_certificate(cert_pem)
        bc = cert.extensions.get_extension_for_class(x509.BasicConstraints)
        assert bc.value.ca is True


class TestIssueNodeCert:
    def test_issues_signed_cert(self):
        ca_cert, ca_key = generate_ca()
        node_cert, node_key = issue_node_cert(ca_cert, ca_key, "node-1")
        assert node_cert.startswith(b"-----BEGIN CERTIFICATE-----")
        assert node_key.startswith(b"-----BEGIN PRIVATE KEY-----")

    def test_node_cert_has_correct_cn(self):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(ca_cert, ca_key, "node-42")
        cert = x509.load_pem_x509_certificate(node_cert)
        cn = cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)
        assert cn[0].value == "node-42"

    def test_node_cert_is_not_ca(self):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(ca_cert, ca_key, "node-1")
        cert = x509.load_pem_x509_certificate(node_cert)
        bc = cert.extensions.get_extension_for_class(x509.BasicConstraints)
        assert bc.value.ca is False

    def test_node_cert_with_ip_san(self):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(
            ca_cert, ca_key, "node-1", addresses=["100.81.247.14"]
        )
        cert = x509.load_pem_x509_certificate(node_cert)
        san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
        ips = san.value.get_values_for_type(x509.IPAddress)
        assert len(ips) >= 1


class TestVerifyCert:
    def test_valid_cert(self):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(ca_cert, ca_key, "node-1")
        result = verify_cert(node_cert, ca_cert)
        assert result["valid"] is True
        assert result["common_name"] == "node-1"

    def test_wrong_ca_fails(self):
        ca_cert1, ca_key1 = generate_ca(common_name="CA-1")
        ca_cert2, ca_key2 = generate_ca(common_name="CA-2")
        node_cert, _ = issue_node_cert(ca_cert1, ca_key1, "node-1")
        result = verify_cert(node_cert, ca_cert2)
        assert result["valid"] is False

    def test_invalid_pem_returns_error(self):
        ca_cert, _ = generate_ca()
        result = verify_cert(b"not a cert", ca_cert)
        assert result["valid"] is False
        assert "error" in result


class TestSaveCertFiles:
    def test_saves_files(self, tmp_path):
        ca_cert, ca_key = generate_ca()
        node_cert, node_key = issue_node_cert(ca_cert, ca_key, "node-1")
        paths = save_cert_files(tmp_path / "certs", ca_cert, node_cert, node_key)
        assert "ca.crt" in paths
        assert "node.crt" in paths
        assert "node.key" in paths
        # Verify files exist
        assert (tmp_path / "certs" / "ca.crt").exists()
        assert (tmp_path / "certs" / "node.key").stat().st_mode & 0o777 == 0o600


class TestRevokeCert:
    def test_revoke_records_serial(self, tmp_path):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(ca_cert, ca_key, "node-1")
        store = tmp_path / "revoked.txt"
        result = revoke_cert(node_cert, ca_cert, ca_key, revoked_store=store)
        assert result["revoked"] is True
        assert result["common_name"] == "node-1"
        assert store.exists()
        assert result["serial_number"] in store.read_text()

    def test_is_revoked_detects_revoked_cert(self, tmp_path):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(ca_cert, ca_key, "node-rev")
        store = tmp_path / "revoked.txt"
        revoke_cert(node_cert, ca_cert, ca_key, revoked_store=store)
        assert is_revoked(node_cert, revoked_store=store) is True

    def test_is_revoked_returns_false_for_valid_cert(self, tmp_path):
        ca_cert, ca_key = generate_ca()
        node_cert, _ = issue_node_cert(ca_cert, ca_key, "node-ok")
        store = tmp_path / "revoked.txt"
        assert is_revoked(node_cert, revoked_store=store) is False

    def test_multiple_revocations(self, tmp_path):
        ca_cert, ca_key = generate_ca()
        cert1, _ = issue_node_cert(ca_cert, ca_key, "node-1")
        cert2, _ = issue_node_cert(ca_cert, ca_key, "node-2")
        store = tmp_path / "revoked.txt"
        revoke_cert(cert1, ca_cert, ca_key, revoked_store=store)
        revoke_cert(cert2, ca_cert, ca_key, revoked_store=store)
        assert is_revoked(cert1, revoked_store=store) is True
        assert is_revoked(cert2, revoked_store=store) is True
