"""Tests for air-gapped license validation with real Ed25519 keypairs."""

from __future__ import annotations

import base64
import json
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING

import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
    Ed25519PrivateKey,
)

if TYPE_CHECKING:
    from pathlib import Path

from complyform.core.auth.license import LicenseManager
from complyform.core.errors import LicenseInvalidError


def _build_license_file(
    private_key: Ed25519PrivateKey,
    payload: dict[str, object],
    *,
    alg: str = "ed25519",
    add_pem_headers: bool = True,
    tamper_sig: bool = False,
) -> str:
    """Build a license file string for testing."""
    enc = base64.b64encode(json.dumps(payload).encode("utf-8")).decode("ascii")

    sig_bytes = private_key.sign(enc.encode("utf-8"))
    if tamper_sig:
        sig_bytes = b"\x00" * len(sig_bytes)

    sig = base64.b64encode(sig_bytes).decode("ascii")

    outer = json.dumps({"enc": enc, "sig": sig, "alg": alg})
    b64_outer = base64.b64encode(outer.encode("utf-8")).decode("ascii")

    if add_pem_headers:
        return (
            "-----BEGIN COMPLYFORM LICENSE-----\n"
            f"{b64_outer}\n"
            "-----END COMPLYFORM LICENSE-----\n"
        )
    return b64_outer


@pytest.fixture
def ed25519_keypair() -> tuple[Ed25519PrivateKey, bytes]:
    """Generate a test Ed25519 keypair."""
    private_key = Ed25519PrivateKey.generate()
    public_bytes = private_key.public_key().public_bytes_raw()
    return private_key, public_bytes


@pytest.fixture
def license_mgr(
    tmp_path: Path, ed25519_keypair: tuple[Ed25519PrivateKey, bytes]
) -> LicenseManager:
    """LicenseManager with test public key."""
    _, public_bytes = ed25519_keypair
    mgr = LicenseManager(config_dir=tmp_path)
    # Write test public key
    mgr.public_key_path.parent.mkdir(parents=True, exist_ok=True)
    # We need to override the public_key_path — monkeypatch via writing
    # to a custom location
    key_path = tmp_path / "ed25519_public.key"
    key_path.write_bytes(public_bytes)
    # Monkeypatch the property
    mgr.__class__ = type(
        "TestLicenseManager",
        (LicenseManager,),
        {"public_key_path": property(lambda self: key_path)},
    )
    return mgr


def _valid_payload() -> dict[str, object]:
    exp = (datetime.now(UTC) + timedelta(days=365)).isoformat()
    return {
        "tier": "pro",
        "assess_frameworks": ["all"],
        "remediate_frameworks": ["soc2", "hipaa"],
        "features": ["html_report"],
        "project_limit": 10,
        "expires_at": exp,
        "batch_limit": 10,
        "discover_limit": 10,
        "scan_sources": ["local_state", "gcs"],
    }


class TestValidLicense:
    def test_valid_license_returns_cache(
        self,
        tmp_path: Path,
        ed25519_keypair: tuple[Ed25519PrivateKey, bytes],
        license_mgr: LicenseManager,
    ) -> None:
        priv, _ = ed25519_keypair
        payload = _valid_payload()
        content = _build_license_file(priv, payload)
        lic_path = tmp_path / "test.complyform-license"
        lic_path.write_text(content)

        cache = license_mgr.validate_airgap_license(lic_path)
        assert cache.tier == "pro"
        assert cache.key_hash.startswith("sha256:")


class TestExpiredLicense:
    def test_expired_raises(
        self,
        tmp_path: Path,
        ed25519_keypair: tuple[Ed25519PrivateKey, bytes],
        license_mgr: LicenseManager,
    ) -> None:
        priv, _ = ed25519_keypair
        payload = _valid_payload()
        payload["expires_at"] = (datetime.now(UTC) - timedelta(days=1)).isoformat()
        content = _build_license_file(priv, payload)
        lic_path = tmp_path / "expired.complyform-license"
        lic_path.write_text(content)

        with pytest.raises(LicenseInvalidError, match="expired"):
            license_mgr.validate_airgap_license(lic_path)


class TestTamperedSignature:
    def test_tampered_sig_raises(
        self,
        tmp_path: Path,
        ed25519_keypair: tuple[Ed25519PrivateKey, bytes],
        license_mgr: LicenseManager,
    ) -> None:
        priv, _ = ed25519_keypair
        payload = _valid_payload()
        content = _build_license_file(priv, payload, tamper_sig=True)
        lic_path = tmp_path / "tampered.complyform-license"
        lic_path.write_text(content)

        with pytest.raises(LicenseInvalidError, match="Signature"):
            license_mgr.validate_airgap_license(lic_path)


class TestWrongAlgorithm:
    def test_wrong_alg_raises(
        self,
        tmp_path: Path,
        ed25519_keypair: tuple[Ed25519PrivateKey, bytes],
        license_mgr: LicenseManager,
    ) -> None:
        priv, _ = ed25519_keypair
        payload = _valid_payload()
        content = _build_license_file(priv, payload, alg="rsa")
        lic_path = tmp_path / "wrong_alg.complyform-license"
        lic_path.write_text(content)

        with pytest.raises(LicenseInvalidError, match="Unsupported algorithm"):
            license_mgr.validate_airgap_license(lic_path)


class TestMissingPEMHeaders:
    def test_no_headers_raises(
        self,
        tmp_path: Path,
        ed25519_keypair: tuple[Ed25519PrivateKey, bytes],
        license_mgr: LicenseManager,
    ) -> None:
        priv, _ = ed25519_keypair
        payload = _valid_payload()
        content = _build_license_file(priv, payload, add_pem_headers=False)
        lic_path = tmp_path / "no_pem.complyform-license"
        lic_path.write_text(content)

        with pytest.raises(LicenseInvalidError, match="PEM"):
            license_mgr.validate_airgap_license(lic_path)
