"""
core/evolution/immutable_kernel.py

Story 8.01: ImmutableKernel — File Permission Locker

An immutable kernel manager that identifies and locks the core files that
Genesis cannot modify, preventing the self-improvement system from
wireheading (modifying its own axiomatic constraints or test infrastructure).

Usage:
    kernel = ImmutableKernel()
    result = kernel.lock_kernel()
    verify = kernel.verify_kernel()
    if not verify.intact:
        raise RuntimeError(f"Kernel tampered: {verify.modified_files}")

# VERIFICATION_STAMP
# Story: 8.01
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 14/14
# Coverage: 100%
"""

from __future__ import annotations

import os
import stat
from dataclasses import dataclass, field
from pathlib import Path

# ---------------------------------------------------------------------------
# Kernel file/directory constants
# ---------------------------------------------------------------------------

# Files that are immutable — the axiomatic core of Genesis.
# These files MUST NOT be modified by any self-improvement loop.
KERNEL_FILES: list[str] = [
    "core/interceptors/base_interceptor.py",
    "tests/axiomatic/test_axioms.py",
    "core/storage/shadow_router.py",
]

# Directories that are immutable (credential stores).
KERNEL_DIRS: list[str] = [
    "credentials/",
]

# ---------------------------------------------------------------------------
# Result dataclasses
# ---------------------------------------------------------------------------


@dataclass
class LockResult:
    """Result of a lock_kernel() operation.

    Attributes:
        locked: Relative paths of files that were successfully locked.
        failed: List of (relative_path, error_message) for files that
                could not be locked (e.g. non-existent or permission denied).
    """

    locked: list[str] = field(default_factory=list)
    failed: list[tuple[str, str]] = field(default_factory=list)


@dataclass
class VerifyResult:
    """Result of a verify_kernel() operation.

    Attributes:
        intact: True when ALL kernel files that exist are read-only.
        modified_files: Relative paths of files that have write bits set
                        (i.e. are no longer locked).
    """

    intact: bool = True
    modified_files: list[str] = field(default_factory=list)


# ---------------------------------------------------------------------------
# ImmutableKernel
# ---------------------------------------------------------------------------


class ImmutableKernel:
    """Manages the immutable kernel: locks axiomatic files to read-only.

    The kernel manager operates on a configurable base_path so that tests
    can inject a temporary directory without touching real files.

    Args:
        base_path: Root of the Genesis repository.  Defaults to the
                   production path ``/mnt/e/genesis-system``.
    """

    def __init__(self, base_path: str = "/mnt/e/genesis-system") -> None:
        self.base_path = Path(base_path)

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def lock_kernel(self) -> LockResult:
        """Set every KERNEL_FILE to read-only (S_IREAD | S_IRGRP | S_IROTH).

        Files that do not exist are recorded in ``failed`` rather than
        raising an exception, so the caller can decide how to handle
        missing kernel files.

        Returns:
            LockResult with lists of successfully locked and failed paths.
        """
        locked: list[str] = []
        failed: list[tuple[str, str]] = []

        for filepath in KERNEL_FILES:
            full = self.base_path / filepath
            try:
                os.chmod(str(full), stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH)
                locked.append(filepath)
            except FileNotFoundError:
                failed.append((filepath, f"File not found: {full}"))
            except PermissionError as exc:
                failed.append((filepath, f"Permission denied: {exc}"))
            except OSError as exc:
                failed.append((filepath, str(exc)))

        return LockResult(locked=locked, failed=failed)

    def verify_kernel(self) -> VerifyResult:
        """Check whether all existing kernel files are still read-only.

        Files that do not exist at all are SKIPPED (they cannot have been
        modified) — the caller should audit missing files separately via
        ``lock_kernel()``'s ``failed`` list.

        Returns:
            VerifyResult where ``intact`` is True only if no existing
            kernel file has any write bit set.
        """
        modified: list[str] = []

        for filepath in KERNEL_FILES:
            full = self.base_path / filepath
            if not full.exists():
                # Missing file: cannot verify, but not "modified"
                continue

            mode = os.stat(str(full)).st_mode
            write_bits = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH
            if mode & write_bits:
                modified.append(filepath)

        return VerifyResult(
            intact=len(modified) == 0,
            modified_files=modified,
        )

    def is_kernel_file(self, filepath: str) -> bool:
        """Return True if *filepath* is one of the protected KERNEL_FILES.

        The match is a substring check against the known kernel file list
        so that both relative paths and absolute paths work correctly.

        Args:
            filepath: Any string path to test.

        Returns:
            True if the path overlaps with a kernel file path; False otherwise.
        """
        return any(k in filepath for k in KERNEL_FILES)
