#
#   Imandra Inc.
#
#   speclogician/logic/lib/complement.py
#

from __future__ import annotations

import asyncio
from typing import Optional

from speclogician.modeling.domain import BaseStatus
from speclogician.modeling.complement import ScenarioComplement
from speclogician.modeling.spec import Spec

from ..api.decomp import decomp_complement_async


def _is_iml_invalid(x: object) -> bool:
    """
    Best-effort check for IML invalidity without hard-coding exact enum imports.
    Works with:
      - IMLValidity.INVALID enum
      - string/repr containing "IMLValidity.INVALID"
      - literal "invalid"
    """
    v = getattr(x, "is_iml_valid", None)
    if v is None:
        return False

    # enum-style
    try:
        if getattr(v, "name", None) == "INVALID":
            return True
    except Exception:
        pass

    # string-style
    s = str(v)
    return (s == "invalid") or ("IMLValidity.INVALID" in s)


def _spec_is_decomposable_for_complement(spec: Spec) -> tuple[bool, str]:
    dm = spec.domain_model

    # 1) base must be good
    if getattr(dm, "base_status", None) != BaseStatus.VALID:
        return False, f"base_status={getattr(dm, 'base_status', None)}"

    # 2) must have scenarios
    scs = list(getattr(spec, "scenarios", []) or [])
    if len(scs) == 0:
        return False, "no_scenarios"

    # 3) scenarios should be internally consistent (avoid wasting time)
    if any(getattr(sc, "all_preds_consistent", True) is False for sc in scs):
        return False, "has_inconsistent_scenarios"

    # 4) avoid complement if any predicate/transition is IML-invalid
    bad: list[tuple[str, str]] = []

    for p in list(getattr(dm, "state_preds", [])) + list(getattr(dm, "action_preds", [])):
        if _is_iml_invalid(p):
            bad.append(("pred", getattr(p, "name", "<unknown>")))

    for t in list(getattr(dm, "transitions", [])):
        if _is_iml_invalid(t):
            bad.append(("trans", getattr(t, "name", "<unknown>")))

    if bad:
        return False, f"iml_invalid={bad[:3]}{'...' if len(bad) > 3 else ''}"

    return True, "ok"


def maybe_update_spec_complement(spec: Spec) -> Optional[ScenarioComplement]:
    """
    Policy-level helper (SYNC):

      - decides whether it is *appropriate* to compute complement
      - if not appropriate, clears stale complement
      - if appropriate, calls ImandraX via API-layer decomp and stores result

    NOTE:
      - This is intentionally sync to fit the state pipeline.
      - It runs the async decomp using asyncio.run().
      - If called inside an existing event loop, it will gracefully skip and
        leave complement cleared (to avoid stale info).
    """
    ok, _why = _spec_is_decomposable_for_complement(spec)
    if not ok:
        # Prevent stale coverage info from hanging around.
        spec.scenario_comp = None
        return None

    try:
        comp = asyncio.run(decomp_complement_async(spec))
    except RuntimeError:
        # "asyncio.run() cannot be called from a running event loop"
        # This should be rare in CLI, but can happen in notebooks/tests.
        # Prefer "clear rather than stale".
        spec.scenario_comp = None
        return None

    spec.scenario_comp = comp
    return comp

