"""
holografik.constraints — ai_assert constraint factories for the Topoloji + Holografik Lens.

Each factory returns an ``ai_assert.Constraint`` instance whose
``check_fn`` accepts a JSON string and returns ``(passed, score, message)``.

Constraints enforce:
  AX37:     Holographic Structure — parts mirror the whole
  KV₆:     Holographic Seed Omnipresence — every module has seed > 0
  T14:      Ne Ayn Ne Gayr — 0 < fidelity < 1
  N-2/AX59: Celâl Inclusion — B21–B22 not excluded
  AX63:     Tesanüd/İnfirâd asymmetry — composition correctness
  AX52:     Multiplicative gate
  KV₄/T6:  Convergence bound (0 < C < 1)
  KV₇:     Independence — units independently defined
  AX57:     Transparency — model state must be inspectable

KV₇ compliance: This module imports ONLY from ai_assert and
                 holografik.types / holografik.verification,
                 plus the standard library.
"""

from __future__ import annotations

import json
from typing import Tuple

from ai_assert import Constraint

from holografik.types import (
    ALL_DIMENSIONS,
    BesmeleBoyut,
    CELALI_DIMENSIONS,
    CompositionMode,
    DimensionType,
    FidelityPair,
    HolographicModel,
    HolographicUnit,
    IsomorphismResult,
    NUM_DIMENSIONS,
    SeedVector,
    clamp_score,
)
from holografik.verification import (
    check_celal_inclusion,
    check_convergence_bound,
    check_fidelity_bounds,
    check_holographic_isomorphism,
    check_independence,
    check_seed_omnipresence,
    check_seed_structure,
    check_tesanud_infirad,
    check_transparency,
    yakinlasma,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _parse_holographic(output: str) -> Tuple[bool, "HolographicModel | None", str]:
    """Parse a JSON string into a :class:`HolographicModel`.

    Expected JSON schema::

        {
            "name": str,
            "units": [
                {
                    "name": str,
                    "level": str,
                    "description": str,
                    "seed": [float, ...] (22 elements)
                }, ...
            ],
            "fidelity_pairs": [
                {
                    "source": str, "derivative": str, "fidelity": float
                }, ...
            ],
            "isomorphism_results": [
                {
                    "unit_a": str, "unit_b": str,
                    "similarity": float, "shared_dimensions": int,
                    "is_isomorphic": bool
                }, ...
            ],
            "composition_mode": str  // "tesanud" | "infirad" | "independent"
        }

    Returns:
        (success, model_or_None, message)
    """
    try:
        data = json.loads(output)
    except (json.JSONDecodeError, TypeError) as exc:
        return False, None, f"JSON parse error: {exc}"

    if not isinstance(data, dict):
        return False, None, "Expected a JSON object"

    try:
        model = HolographicModel(name=data.get("name", ""))

        # ---- units ----------------------------------------------------
        for u in data.get("units", []):
            raw_seed = u.get("seed", [])
            if isinstance(raw_seed, dict):
                # Support dict form {B01: val, B02: val, ...}
                seed_vals = []
                for dim in ALL_DIMENSIONS:
                    seed_vals.append(float(raw_seed.get(dim.name, 0.0)))
                seed = SeedVector(values=tuple(seed_vals))
            else:
                # List form [v1, v2, ..., v22]
                if len(raw_seed) != NUM_DIMENSIONS:
                    return False, None, (
                        f"Unit '{u.get('name', '?')}' seed has "
                        f"{len(raw_seed)} components, expected {NUM_DIMENSIONS}"
                    )
                seed = SeedVector(values=tuple(float(v) for v in raw_seed))

            model.add_unit(HolographicUnit(
                name=u["name"],
                seed=seed,
                level=u.get("level", ""),
                description=u.get("description", ""),
            ))

        # ---- fidelity pairs -------------------------------------------
        for f in data.get("fidelity_pairs", []):
            model.add_fidelity_pair(FidelityPair(
                source_name=f["source"],
                derivative_name=f["derivative"],
                fidelity=float(f["fidelity"]),
            ))

        # ---- isomorphism results --------------------------------------
        for r in data.get("isomorphism_results", []):
            model.add_isomorphism(IsomorphismResult(
                unit_a_name=r["unit_a"],
                unit_b_name=r["unit_b"],
                similarity=float(r["similarity"]),
                shared_dimensions=int(r["shared_dimensions"]),
                is_isomorphic=bool(r.get("is_isomorphic", False)),
            ))

        # ---- composition mode -----------------------------------------
        mode_str = data.get("composition_mode", "independent")
        model.composition_mode = CompositionMode(mode_str)

        return True, model, "OK"

    except (KeyError, ValueError, TypeError) as exc:
        return False, None, f"Model construction error: {exc}"


# ---------------------------------------------------------------------------
# Constraint factories
# ---------------------------------------------------------------------------

def seed_structure() -> Constraint:
    """All seed vectors must have valid 22-dimensional structure."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"seed_structure: {msg}"
        passed, detail = check_seed_structure(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"seed_structure: {detail}"

    return Constraint(name="seed_structure", check_fn=check)


def holographic_isomorphism() -> Constraint:
    """AX37: Parts must mirror the structure of the whole."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"holographic_isomorphism: {msg}"
        passed, detail = check_holographic_isomorphism(model)
        if passed and model.isomorphism_results:
            # Bonus for high mean similarity
            mean_sim = model.mean_similarity
            score = clamp_score(0.7 + 0.25 * mean_sim)
        elif passed:
            score = clamp_score(0.95)
        else:
            score = 0.1
        return passed, score, f"holographic_isomorphism: {detail}"

    return Constraint(name="holographic_isomorphism", check_fn=check)


def seed_omnipresence() -> Constraint:
    """KV₆: Every module must have a detectable holographic seed."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"seed_omnipresence: {msg}"
        passed, detail = check_seed_omnipresence(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"seed_omnipresence: {detail}"

    return Constraint(name="seed_omnipresence", check_fn=check)


def fidelity_bounds() -> Constraint:
    """T14/KV₄: All fidelity values must be strictly between 0 and 1."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"fidelity_bounds: {msg}"
        passed, detail = check_fidelity_bounds(model)
        score = clamp_score(0.95) if passed else 0.1
        return passed, score, f"fidelity_bounds: {detail}"

    return Constraint(name="fidelity_bounds", check_fn=check)


def celal_inclusion() -> Constraint:
    """N-2/AX59: Celâlî dimensions (B21–B22) must not be excluded."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"celal_inclusion: {msg}"
        passed, detail = check_celal_inclusion(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"celal_inclusion: {detail}"

    return Constraint(name="celal_inclusion", check_fn=check)


def holographic_convergence_bound() -> Constraint:
    """KV₄/T6: No convergence score may reach 1.0."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"holographic_convergence_bound: {msg}"
        passed, detail = check_convergence_bound(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"holographic_convergence_bound: {detail}"

    return Constraint(name="holographic_convergence_bound", check_fn=check)


def tesanud_infirad_asymmetry() -> Constraint:
    """AX63: Composition mode must be correctly classified."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"tesanud_infirad: {msg}"
        passed, detail = check_tesanud_infirad(model)
        score = clamp_score(0.95) if passed else 0.1
        return passed, score, f"tesanud_infirad: {detail}"

    return Constraint(name="tesanud_infirad_asymmetry", check_fn=check)


def holographic_independence() -> Constraint:
    """KV₇: Units must have independently defined seed vectors."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"holographic_independence: {msg}"
        passed, detail = check_independence(model)
        score = clamp_score(0.95) if passed else 0.1
        return passed, score, f"holographic_independence: {detail}"

    return Constraint(name="holographic_independence", check_fn=check)


def holographic_transparency() -> Constraint:
    """AX57: Model state must be fully inspectable."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"holographic_transparency: {msg}"
        passed, detail = check_transparency(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"holographic_transparency: {detail}"

    return Constraint(name="holographic_transparency", check_fn=check)


def holographic_convergence_score() -> Constraint:
    """KV₄/T6: Overall convergence score in [0, 1) — flag if ≥ 1.0."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_holographic(output)
        if not ok:
            return False, 0.0, f"holographic_convergence: {msg}"
        score = yakinlasma(model)
        if score >= 1.0:
            return False, score, (
                f"KV4/T6 VIOLATION: yakinlasma = {score}"
            )
        return True, score, f"holographic_convergence: yakinlasma = {score:.4f}"

    return Constraint(name="holographic_convergence_score", check_fn=check)


def valid_holographic_entry() -> Constraint:
    """Composite constraint — all individual checks must pass.

    Runs all 9 sub-constraints and reports a combined result.
    """
    sub_constraints = [
        seed_structure(),
        holographic_isomorphism(),
        seed_omnipresence(),
        fidelity_bounds(),
        celal_inclusion(),
        holographic_convergence_bound(),
        tesanud_infirad_asymmetry(),
        holographic_independence(),
        holographic_transparency(),
    ]

    def check(output: str) -> tuple[bool, float, str]:
        results = [c.check(output) for c in sub_constraints]
        all_passed = all(r.passed for r in results)

        if all_passed:
            score = clamp_score(0.95)
            return True, score, "valid_holographic_entry: All checks passed"

        failures = [r.message for r in results if not r.passed]
        avg = sum(r.score for r in results) / len(results) if results else 0.0
        score = clamp_score(avg)
        return False, score, f"valid_holographic_entry: {'; '.join(failures)}"

    return Constraint(name="valid_holographic_entry", check_fn=check)
