"""MITRE ATT&CK Terms of Use compliance: LICENSE.txt is fetched, verified, and required.

Bundle and license live side-by-side in the cache dir. The bundle is
fail-closed on hash mismatch (drift = mistagging risk); the license
is logged-and-refreshed on hash mismatch (drift = MITRE updated the
text, not a security event), but its *presence* is mandatory.
"""
from __future__ import annotations

import hashlib
from pathlib import Path

import pytest

from decnet.ttp import attack_stix
from decnet.ttp.attack_version import (
    ATTACK_LICENSE_FILENAME,
    ATTACK_LICENSE_SHA256,
)

_REPO_BUNDLE = Path(__file__).resolve().parents[2] / "enterprise-attack-19.0.json"


@pytest.fixture(autouse=True)
def _reset_loader_state() -> None:
    attack_stix._data = None
    attack_stix._loaded_path = None
    attack_stix._attack_pattern_by_id.cache_clear()
    attack_stix._tactic_by_id.cache_clear()
    attack_stix._tactic_by_short_name.cache_clear()


def _write_dummy_license(path: Path) -> str:
    text = "placeholder license content for tests"
    path.write_text(text, encoding="utf-8")
    return hashlib.sha256(text.encode()).hexdigest()


def test_license_filename_constant() -> None:
    assert ATTACK_LICENSE_FILENAME == "LICENSE.txt"


def test_resolve_bundle_path_with_override_and_sibling_license(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Operator points DECNET_ATTACK_BUNDLE at a file with LICENSE.txt next to it — happy path."""
    bundle = tmp_path / "enterprise-attack-19.0.json"
    bundle.write_bytes(_REPO_BUNDLE.read_bytes())
    license_path = tmp_path / ATTACK_LICENSE_FILENAME
    _write_dummy_license(license_path)

    monkeypatch.setenv("DECNET_ATTACK_BUNDLE", str(bundle))
    monkeypatch.delenv("DECNET_ATTACK_LICENSE", raising=False)
    # Empty cache dir so override-mode resolves license from sibling.
    monkeypatch.setenv("DECNET_ATTACK_CACHE_DIR", str(tmp_path / "cache"))

    resolved = attack_stix.resolve_bundle_path()
    assert resolved == bundle
    assert attack_stix.loaded_license_path() == license_path


def test_resolve_bundle_path_via_decnet_attack_license_env(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """DECNET_ATTACK_LICENSE points to an arbitrary path — accepted."""
    bundle = tmp_path / "bundle" / "enterprise-attack-19.0.json"
    bundle.parent.mkdir()
    bundle.write_bytes(_REPO_BUNDLE.read_bytes())
    explicit_license = tmp_path / "license_elsewhere.txt"
    _write_dummy_license(explicit_license)

    monkeypatch.setenv("DECNET_ATTACK_BUNDLE", str(bundle))
    monkeypatch.setenv("DECNET_ATTACK_LICENSE", str(explicit_license))
    monkeypatch.setenv("DECNET_ATTACK_CACHE_DIR", str(tmp_path / "cache"))

    resolved = attack_stix.resolve_bundle_path()
    assert resolved == bundle
    assert attack_stix.loaded_license_path() == explicit_license


def test_decnet_attack_license_pointing_to_missing_file_raises(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    bundle = tmp_path / "enterprise-attack-19.0.json"
    bundle.write_bytes(_REPO_BUNDLE.read_bytes())
    monkeypatch.setenv("DECNET_ATTACK_BUNDLE", str(bundle))
    monkeypatch.setenv("DECNET_ATTACK_LICENSE", str(tmp_path / "nope.txt"))
    monkeypatch.setenv("DECNET_ATTACK_CACHE_DIR", str(tmp_path / "cache"))

    with pytest.raises(attack_stix.AttackBundleError) as exc:
        attack_stix.resolve_bundle_path()
    assert "Terms of Use" in str(exc.value)


def test_loaded_license_path_returns_none_when_absent(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.delenv("DECNET_ATTACK_BUNDLE", raising=False)
    monkeypatch.delenv("DECNET_ATTACK_LICENSE", raising=False)
    monkeypatch.setenv("DECNET_ATTACK_CACHE_DIR", str(tmp_path))
    assert attack_stix.loaded_license_path() is None


def test_pinned_license_sha_matches_repo_committed_text() -> None:
    """The pinned hash in attack_version.py is a 64-char lowercase hex sha256."""
    assert len(ATTACK_LICENSE_SHA256) == 64
    assert all(c in "0123456789abcdef" for c in ATTACK_LICENSE_SHA256)


def test_cli_license_subcommand_prints_cached_license(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    license_path = tmp_path / ATTACK_LICENSE_FILENAME
    license_path.write_text("MITRE Corporation grants you a license\n", encoding="utf-8")
    monkeypatch.setenv("DECNET_ATTACK_LICENSE", str(license_path))

    rc = attack_stix.main(["license"])
    assert rc == 0
    out = capsys.readouterr().out
    assert "MITRE Corporation" in out


def test_cli_license_returns_nonzero_when_not_present(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.delenv("DECNET_ATTACK_LICENSE", raising=False)
    monkeypatch.delenv("DECNET_ATTACK_BUNDLE", raising=False)
    monkeypatch.setenv("DECNET_ATTACK_CACHE_DIR", str(tmp_path / "empty"))

    rc = attack_stix.main(["license"])
    assert rc == 1
