"""
tests/track_b/test_story_8_01.py

Story 8.01: ImmutableKernel — File Permission Locker

Black Box Tests (BB1–BB5):
    BB1  lock_kernel() → files become read-only (write attempt raises OSError)
    BB2  verify_kernel() on intact kernel → intact=True, modified_files=[]
    BB3  is_kernel_file("core/interceptors/base_interceptor.py") → True
    BB4  is_kernel_file("some/random/file.py") → False
    BB5  verify_kernel() after chmod +w on one file → intact=False, file in modified_files

White Box Tests (WB1–WB4):
    WB1  Lock uses stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH (readable, not writable)
    WB2  lock_kernel() on non-existent file → failure reported, no exception raised
    WB3  LockResult and VerifyResult are proper dataclasses with correct fields
    WB4  base_path is injectable (all tests use tmp_path)

Package Test:
    PKG  core.evolution __init__.py exports ImmutableKernel, LockResult, VerifyResult,
         KERNEL_FILES, KERNEL_DIRS
"""

from __future__ import annotations

import os
import stat
import sys

import pytest

# ---------------------------------------------------------------------------
# Path setup
# ---------------------------------------------------------------------------

GENESIS_ROOT = "/mnt/e/genesis-system"
if GENESIS_ROOT not in sys.path:
    sys.path.insert(0, GENESIS_ROOT)

# ---------------------------------------------------------------------------
# Imports under test
# ---------------------------------------------------------------------------

from core.evolution.immutable_kernel import (  # noqa: E402
    ImmutableKernel,
    LockResult,
    VerifyResult,
    KERNEL_FILES,
    KERNEL_DIRS,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_kernel_files(tmp_path, content: bytes = b"# kernel file\n") -> dict[str, object]:
    """Create all KERNEL_FILES inside tmp_path with default content.

    Returns a dict mapping relative kernel path → absolute Path object.
    """
    created: dict[str, object] = {}
    for rel in KERNEL_FILES:
        full = tmp_path / rel
        full.parent.mkdir(parents=True, exist_ok=True)
        full.write_bytes(content)
        created[rel] = full
    return created


def _make_kernel(tmp_path) -> ImmutableKernel:
    """Return an ImmutableKernel pointed at tmp_path."""
    return ImmutableKernel(base_path=str(tmp_path))


# ===========================================================================
# BB Tests — Black Box
# ===========================================================================


def test_bb1_lock_kernel_makes_files_read_only(tmp_path):
    """BB1: lock_kernel() sets files to read-only; write attempt then raises OSError."""
    _make_kernel_files(tmp_path)
    kernel = _make_kernel(tmp_path)

    result = kernel.lock_kernel()

    assert isinstance(result, LockResult)
    assert len(result.locked) == len(KERNEL_FILES)
    assert result.failed == []

    # Each file should now be read-only — trying to open for write should fail
    for rel in KERNEL_FILES:
        full = tmp_path / rel
        mode = os.stat(str(full)).st_mode
        write_bits = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH
        # No write bits should remain
        assert not (mode & write_bits), f"File still writable after lock: {rel}"


def test_bb2_verify_kernel_intact(tmp_path):
    """BB2: verify_kernel() on an intact kernel → intact=True, modified_files=[]."""
    _make_kernel_files(tmp_path)
    kernel = _make_kernel(tmp_path)

    # Lock first, then verify
    kernel.lock_kernel()
    verify = kernel.verify_kernel()

    assert isinstance(verify, VerifyResult)
    assert verify.intact is True
    assert verify.modified_files == []


def test_bb3_is_kernel_file_true_for_kernel_path(tmp_path):
    """BB3: is_kernel_file returns True for each path in KERNEL_FILES."""
    kernel = _make_kernel(tmp_path)
    for rel in KERNEL_FILES:
        assert kernel.is_kernel_file(rel) is True, f"Expected True for {rel}"


def test_bb4_is_kernel_file_false_for_random_path(tmp_path):
    """BB4: is_kernel_file returns False for an unrelated path."""
    kernel = _make_kernel(tmp_path)
    assert kernel.is_kernel_file("some/random/file.py") is False
    assert kernel.is_kernel_file("core/genesis_execution_layer.py") is False
    assert kernel.is_kernel_file("") is False


def test_bb5_verify_detects_modified_file(tmp_path):
    """BB5: After chmod +w on one file, verify_kernel → intact=False, file in modified_files."""
    created = _make_kernel_files(tmp_path)
    kernel = _make_kernel(tmp_path)

    # Lock all files
    kernel.lock_kernel()

    # Manually re-enable write on the first kernel file
    first_rel = KERNEL_FILES[0]
    first_full = created[first_rel]
    os.chmod(str(first_full), stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR)

    verify = kernel.verify_kernel()

    assert verify.intact is False
    assert first_rel in verify.modified_files
    # Other files should still be intact (not in modified_files)
    for rel in KERNEL_FILES[1:]:
        assert rel not in verify.modified_files


# ===========================================================================
# WB Tests — White Box
# ===========================================================================


def test_wb1_lock_uses_read_only_mode_not_chmod_000(tmp_path):
    """WB1: Locked files use S_IREAD|S_IRGRP|S_IROTH (readable, not hidden/inaccessible)."""
    _make_kernel_files(tmp_path)
    kernel = _make_kernel(tmp_path)

    kernel.lock_kernel()

    expected_mode = stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH
    for rel in KERNEL_FILES:
        full = tmp_path / rel
        actual = stat.S_IMODE(os.stat(str(full)).st_mode)
        assert actual == expected_mode, (
            f"{rel}: expected mode {oct(expected_mode)}, got {oct(actual)}"
        )


def test_wb2_lock_nonexistent_file_reported_not_raised(tmp_path):
    """WB2: lock_kernel() on non-existent files records failures, raises no exception."""
    # Do NOT create the files — they are missing
    kernel = _make_kernel(tmp_path)

    # Should not raise — should return failures
    result = kernel.lock_kernel()

    assert isinstance(result, LockResult)
    # All files missing → all failed
    assert len(result.failed) == len(KERNEL_FILES)
    assert result.locked == []
    # Each failed entry is a (path, error_message) tuple
    for filepath, error_msg in result.failed:
        assert isinstance(filepath, str)
        assert isinstance(error_msg, str)
        assert len(error_msg) > 0


def test_wb3_lockresult_and_verifyresult_are_dataclasses(tmp_path):
    """WB3: LockResult and VerifyResult are proper dataclasses with correct field names."""
    import dataclasses

    # LockResult
    assert dataclasses.is_dataclass(LockResult)
    field_names_lr = {f.name for f in dataclasses.fields(LockResult)}
    assert "locked" in field_names_lr
    assert "failed" in field_names_lr

    # VerifyResult
    assert dataclasses.is_dataclass(VerifyResult)
    field_names_vr = {f.name for f in dataclasses.fields(VerifyResult)}
    assert "intact" in field_names_vr
    assert "modified_files" in field_names_vr

    # Default values
    lr = LockResult()
    assert lr.locked == []
    assert lr.failed == []

    vr = VerifyResult()
    assert vr.intact is True
    assert vr.modified_files == []


def test_wb4_base_path_is_injectable(tmp_path):
    """WB4: ImmutableKernel accepts an injected base_path and never touches real files."""
    kernel = _make_kernel(tmp_path)
    assert kernel.base_path == tmp_path

    # Creates and locks files in tmp_path only
    _make_kernel_files(tmp_path)
    result = kernel.lock_kernel()
    assert len(result.locked) == len(KERNEL_FILES)

    # Real genesis-system files are untouched
    real_kernel = ImmutableKernel()  # default base_path = /mnt/e/genesis-system
    assert str(real_kernel.base_path) == "/mnt/e/genesis-system"


# ===========================================================================
# Additional edge cases
# ===========================================================================


def test_verify_skips_nonexistent_files(tmp_path):
    """verify_kernel() skips files that do not exist — they're not 'modified'."""
    # Create only the first kernel file (others are absent)
    first_rel = KERNEL_FILES[0]
    first_full = tmp_path / first_rel
    first_full.parent.mkdir(parents=True, exist_ok=True)
    first_full.write_bytes(b"# kernel")

    kernel = _make_kernel(tmp_path)
    kernel.lock_kernel()  # will lock one, fail on others

    verify = kernel.verify_kernel()
    # The existing file is locked → not in modified_files
    assert first_rel not in verify.modified_files
    # Intact because no existing files are write-enabled
    assert verify.intact is True


def test_is_kernel_file_with_absolute_path(tmp_path):
    """is_kernel_file works for absolute paths that contain a kernel path as substring."""
    kernel = _make_kernel(tmp_path)
    abs_path = f"/mnt/e/genesis-system/{KERNEL_FILES[0]}"
    assert kernel.is_kernel_file(abs_path) is True


def test_lock_result_partial_success(tmp_path):
    """LockResult records partial success when only some files exist."""
    # Create only the first kernel file
    first_rel = KERNEL_FILES[0]
    full = tmp_path / first_rel
    full.parent.mkdir(parents=True, exist_ok=True)
    full.write_bytes(b"# kernel")

    kernel = _make_kernel(tmp_path)
    result = kernel.lock_kernel()

    assert first_rel in result.locked
    assert len(result.failed) == len(KERNEL_FILES) - 1


def test_kernel_files_constant_has_expected_entries():
    """KERNEL_FILES constant contains the 3 expected paths."""
    assert "core/interceptors/base_interceptor.py" in KERNEL_FILES
    assert "tests/axiomatic/test_axioms.py" in KERNEL_FILES
    assert "core/storage/shadow_router.py" in KERNEL_FILES
    assert len(KERNEL_FILES) == 3


def test_kernel_dirs_constant():
    """KERNEL_DIRS constant contains credentials/."""
    assert "credentials/" in KERNEL_DIRS


# ===========================================================================
# Package import test
# ===========================================================================


def test_pkg_init_exports():
    """PKG: core.evolution __init__.py exports ImmutableKernel, LockResult, VerifyResult,
    KERNEL_FILES, KERNEL_DIRS."""
    from core.evolution import (  # noqa: F401
        ImmutableKernel as IK,
        LockResult as LR,
        VerifyResult as VR,
        KERNEL_FILES as KF,
        KERNEL_DIRS as KD,
    )

    assert IK is ImmutableKernel
    assert LR is LockResult
    assert VR is VerifyResult
    assert KF is KERNEL_FILES
    assert KD is KERNEL_DIRS
