"""Internal Cython feasibility helpers for WS-0079.

This module is intentionally internal and is not part of the package root
export contract. It provides deterministic reference kernels, optional best-
effort speedup loading, and utility helpers used by benchmark/profile tooling.
"""

from __future__ import annotations

import cProfile
import importlib
import pstats
from collections.abc import Callable
from time import perf_counter
from typing import Any


def _load_numpy_runtime() -> Any | None:
    """Return NumPy runtime module or None when unavailable."""
    try:
        import numpy as np
    except Exception:
        return None
    return np


def _load_cython_speedups(*, build: bool = False) -> Any | None:
    """Return the optional Cython speedup module when available.

    When ``build=False``, this only attempts a normal import. This is expected
    to return None unless the extension has already been built locally.
    When ``build=True``, it may attempt a best-effort ``pyximport`` build.
    Any import/build error returns None and is not raised to callers.
    """

    module_name = "icsd.adapters._cython_speedups"
    try:
        return importlib.import_module(module_name)
    except Exception:
        if not build:
            return None

    try:
        import pyximport

        pyximport.install(language_level=3)
        return importlib.import_module(module_name)
    except Exception:
        return None


def _normalise_float64_1d_contiguous(values: Any, *, np_runtime: Any) -> Any:
    """Normalise values to float64, 1D, C-contiguous NumPy array."""

    array = np_runtime.asarray(values, dtype=np_runtime.float64)
    flattened = array.ravel()
    return np_runtime.ascontiguousarray(flattened, dtype=np_runtime.float64)


def reference_all_finite(values: Any, *, np_runtime: Any) -> bool:
    """Return True when all values are finite."""

    array = _normalise_float64_1d_contiguous(values, np_runtime=np_runtime)
    return bool(np_runtime.isfinite(array).all())


def reference_min_max(values: Any, *, np_runtime: Any) -> tuple[float, float]:
    """Return observed minimum and maximum as Python floats.

    Raises ValueError when the input is empty to mirror speedup-kernel contract.
    """

    array = _normalise_float64_1d_contiguous(values, np_runtime=np_runtime)
    if int(array.size) == 0:
        msg = "values must not be empty."
        raise ValueError(msg)
    return float(array.min()), float(array.max())


def generate_deterministic_cases(
    *,
    np_runtime: Any,
    seed: int = 79079,
) -> tuple[tuple[str, Any], ...]:
    """Return deterministic benchmark/profile arrays in canonical order."""

    rng = np_runtime.random.default_rng(seed)
    finite_small = _normalise_float64_1d_contiguous(
        rng.standard_normal(128),
        np_runtime=np_runtime,
    )
    finite_large = _normalise_float64_1d_contiguous(
        rng.standard_normal(8192),
        np_runtime=np_runtime,
    )
    with_nan = finite_small.copy()
    with_nan[0] = np_runtime.nan
    with_inf = finite_small.copy()
    with_inf[1] = np_runtime.inf
    empty = _normalise_float64_1d_contiguous([], np_runtime=np_runtime)
    return (
        ("finite_small", finite_small),
        ("finite_large", finite_large),
        ("with_nan", with_nan),
        ("with_inf", with_inf),
        ("empty", empty),
    )


def run_parity_case(
    values: Any,
    *,
    build_speedup: bool = False,
    np_runtime: Any | None = None,
    speedups: Any | None = None,
) -> dict[str, Any]:
    """Return deterministic parity envelope for one array-like input."""

    resolved_numpy = np_runtime if np_runtime is not None else _load_numpy_runtime()
    if resolved_numpy is None:
        return _parity_payload(
            status="numpy_unavailable",
            speedup_available=False,
            matched=None,
            reference=None,
            speedup=None,
            error_type="ModuleNotFoundError",
        )

    array = _normalise_float64_1d_contiguous(values, np_runtime=resolved_numpy)
    reference_payload = _kernel_payload_from_reference(array, np_runtime=resolved_numpy)

    resolved_speedups = (
        speedups if speedups is not None else _load_cython_speedups(build=build_speedup)
    )
    if resolved_speedups is None:
        return _parity_payload(
            status="speedup_unavailable",
            speedup_available=False,
            matched=None,
            reference=reference_payload,
            speedup=None,
            error_type=None,
        )

    try:
        speedup_payload = _kernel_payload_from_speedup(array, speedups=resolved_speedups)
    except Exception as exc:
        return _parity_payload(
            status="speedup_unavailable",
            speedup_available=False,
            matched=None,
            reference=reference_payload,
            speedup=None,
            error_type=type(exc).__name__,
        )

    matched = reference_payload == speedup_payload
    status = "ok" if matched else "mismatch"
    return _parity_payload(
        status=status,
        speedup_available=True,
        matched=matched,
        reference=reference_payload,
        speedup=speedup_payload,
        error_type=None,
    )


def time_reference_case(values: Any, *, np_runtime: Any, iterations: int) -> float:
    """Benchmark reference kernels for one case."""

    array = _normalise_float64_1d_contiguous(values, np_runtime=np_runtime)
    start = perf_counter()
    for _ in range(iterations):
        reference_all_finite(array, np_runtime=np_runtime)
        try:
            reference_min_max(array, np_runtime=np_runtime)
        except ValueError:
            pass
    return perf_counter() - start


def time_speedup_case(values: Any, *, speedups: Any, iterations: int) -> float:
    """Benchmark speedup kernels for one case.

    Raises any speedup invocation exception so callers can downgrade to an
    unavailable status in a deterministic way.
    """

    start = perf_counter()
    for _ in range(iterations):
        speedups.all_finite_double(values)
        try:
            speedups.min_max_double(values)
        except ValueError:
            pass
    return perf_counter() - start


def profile_callable(
    callback: Callable[[], None],
    *,
    repeats: int,
    top_n: int,
) -> list[dict[str, Any]]:
    """Profile one callback and return deterministically sorted top rows."""

    profiler = cProfile.Profile()
    profiler.enable()
    for _ in range(repeats):
        callback()
    profiler.disable()

    stats = pstats.Stats(profiler)
    rows: list[dict[str, Any]] = []
    for (filename, line_number, function_name), metrics in stats.stats.items():
        _primitive_calls, total_calls, total_time, cumulative_time, _callers = metrics
        function_id = f"{filename}:{line_number}:{function_name}"
        rows.append(
            {
                "function": function_id,
                "call_count": int(total_calls),
                "total_time": float(total_time),
                "cumulative_time": float(cumulative_time),
            }
        )

    rows.sort(key=lambda item: (-item["cumulative_time"], item["function"]))
    return rows[:top_n]


def _kernel_payload_from_reference(array: Any, *, np_runtime: Any) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "all_finite": bool(np_runtime.isfinite(array).all()),
        "min_max": None,
        "min_max_error_type": None,
    }
    try:
        minimum, maximum = reference_min_max(array, np_runtime=np_runtime)
    except Exception as exc:
        payload["min_max_error_type"] = type(exc).__name__
    else:
        payload["min_max"] = [minimum, maximum]
    return payload


def _kernel_payload_from_speedup(array: Any, *, speedups: Any) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "all_finite": bool(speedups.all_finite_double(array)),
        "min_max": None,
        "min_max_error_type": None,
    }
    try:
        minimum, maximum = speedups.min_max_double(array)
    except Exception as exc:
        payload["min_max_error_type"] = type(exc).__name__
    else:
        payload["min_max"] = [float(minimum), float(maximum)]
    return payload


def _parity_payload(
    *,
    status: str,
    speedup_available: bool,
    matched: bool | None,
    reference: dict[str, Any] | None,
    speedup: dict[str, Any] | None,
    error_type: str | None,
) -> dict[str, Any]:
    return {
        "status": status,
        "speedup_available": speedup_available,
        "matched": matched,
        "reference": reference,
        "speedup": speedup,
        "error_type": error_type,
    }
