import json
from unittest.mock import MagicMock, patch

import pytest

from nebula_cert_manager.pki import PKI, PKIError


@pytest.fixture
def pki():
    return PKI("nebula-cert")


def test_create_ca(pki, tmp_path):
    """Test create_ca calls nebula-cert ca with correct args."""
    with (
        patch("nebula_cert_manager.pki.subprocess.run") as mock_run,
        patch("nebula_cert_manager.pki.tempfile.TemporaryDirectory") as mock_tmpdir,
    ):
        mock_tmpdir.return_value.__enter__ = MagicMock(return_value=str(tmp_path))
        mock_tmpdir.return_value.__exit__ = MagicMock(return_value=False)

        (tmp_path / "ca.crt").write_text("CERT")
        (tmp_path / "ca.key").write_text("KEY")

        mock_run.return_value = MagicMock(returncode=0)

        cert, key = pki.create_ca("test-ca")

        assert cert == "CERT"
        assert key == "KEY"
        cmd = mock_run.call_args[0][0]
        assert cmd[0] == "nebula-cert"
        assert cmd[1] == "ca"
        assert "-name" in cmd
        assert "test-ca" in cmd


def test_create_ca_with_duration(pki, tmp_path):
    with (
        patch("nebula_cert_manager.pki.subprocess.run") as mock_run,
        patch("nebula_cert_manager.pki.tempfile.TemporaryDirectory") as mock_tmpdir,
    ):
        mock_tmpdir.return_value.__enter__ = MagicMock(return_value=str(tmp_path))
        mock_tmpdir.return_value.__exit__ = MagicMock(return_value=False)
        (tmp_path / "ca.crt").write_text("CERT")
        (tmp_path / "ca.key").write_text("KEY")
        mock_run.return_value = MagicMock(returncode=0)

        pki.create_ca("test-ca", duration="8760h")

        cmd = mock_run.call_args[0][0]
        assert "-duration" in cmd
        assert "8760h" in cmd


def test_sign(pki, tmp_path):
    with (
        patch("nebula_cert_manager.pki.subprocess.run") as mock_run,
        patch("nebula_cert_manager.pki.tempfile.TemporaryDirectory") as mock_tmpdir,
    ):
        mock_tmpdir.return_value.__enter__ = MagicMock(return_value=str(tmp_path))
        mock_tmpdir.return_value.__exit__ = MagicMock(return_value=False)
        (tmp_path / "client.crt").write_text("CLIENT_CERT")
        (tmp_path / "client.key").write_text("CLIENT_KEY")
        mock_run.return_value = MagicMock(returncode=0)

        cert, key = pki.sign("ca-cert", "ca-key", "laptop", "10.43.1.1/16", ["user"])

        assert cert == "CLIENT_CERT"
        assert key == "CLIENT_KEY"
        cmd = mock_run.call_args[0][0]
        assert "sign" in cmd
        assert "laptop" in cmd


def test_print_cert(pki, tmp_path):
    cert_data = {"fingerprint": "abc123", "details": {"name": "test"}}
    with (
        patch("nebula_cert_manager.pki.subprocess.run") as mock_run,
        patch("nebula_cert_manager.pki.tempfile.TemporaryDirectory") as mock_tmpdir,
    ):
        mock_tmpdir.return_value.__enter__ = MagicMock(return_value=str(tmp_path))
        mock_tmpdir.return_value.__exit__ = MagicMock(return_value=False)
        # nebula-cert print -json returns a JSON array
        mock_run.return_value = MagicMock(returncode=0, stdout=json.dumps([cert_data]))

        result = pki.print_cert("some-cert-pem")

        assert result == cert_data


def test_pki_error_is_cert_manager_error():
    from nebula_cert_manager.exceptions import CertManagerError

    assert issubclass(PKIError, CertManagerError)


def test_sign_and_build_client_info(pki, tmp_path):
    cert_data = {
        "fingerprint": "fp123",
        "details": {"notAfter": "2030-01-01T00:00:00Z"},
    }
    with (
        patch("nebula_cert_manager.pki.subprocess.run") as mock_run,
        patch("nebula_cert_manager.pki.tempfile.TemporaryDirectory") as mock_tmpdir,
    ):
        mock_tmpdir.return_value.__enter__ = MagicMock(return_value=str(tmp_path))
        mock_tmpdir.return_value.__exit__ = MagicMock(return_value=False)
        (tmp_path / "client.crt").write_text("CERT_PEM")
        (tmp_path / "client.key").write_text("KEY_PEM")

        # First call: sign, second call: print_cert
        mock_run.return_value = MagicMock(returncode=0, stdout=json.dumps([cert_data]))

        client = pki.sign_and_build_client_info(
            ca_cert="ca-cert",
            ca_key="ca-key",
            name="laptop",
            ip="10.0.0.1/24",
            groups=["web"],
        )

        assert client.fingerprint == "fp123"
        assert client.cert == "CERT_PEM"
        assert client.key == "KEY_PEM"
        assert client.expires_at.year == 2030


def test_pki_error_on_failure(pki):
    import subprocess

    with patch("nebula_cert_manager.pki.subprocess.run") as mock_run:
        mock_run.side_effect = subprocess.CalledProcessError(
            1, "nebula-cert", stderr="error msg"
        )
        with pytest.raises(PKIError, match="nebula-cert failed"):
            pki.create_ca("test")


def test_pki_error_binary_not_found(pki):
    with patch("nebula_cert_manager.pki.subprocess.run") as mock_run:
        mock_run.side_effect = FileNotFoundError()
        with pytest.raises(PKIError, match="not found"):
            pki.create_ca("test")
