"""Additional targeted tests for coverage gaps not covered by v1.

Fixes and supplements tests from test_coverage_gaps.py:
- enterprise/auth.py (lines 203-204): non-ASCII signature (v1 test didn't trigger exception)
- intelligence/validation.py (lines 87, 90): 5-variable DAG for inner break coverage
- kernel/world_dag.py (line 42): remove_entity early return
- kernel/world_dag.py (line 85): BFS visited-node continue
"""

from __future__ import annotations

import random

from orxaq.enterprise.auth import TokenProvider
from orxaq.intelligence.validation import DSeparationTester
from orxaq.kernel import CausalEdge, CausalRole, Entity, VariableType, WorldDAG

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


def _build_5var_dag() -> WorldDAG:
    """Build a sparse 5-variable DAG: A -> B only.

    With 5 vars and 1 edge there are many (x, y) pairs.
    Conditioning sets grow to size 1, 2, 3 — enough to hit inner breaks.
    """
    dag = WorldDAG()
    for name in ["A", "B", "C", "D", "E"]:
        dag.add_entity(
            Entity(
                entity_id=name,
                name=name,
                causal_role=CausalRole.EXOGENOUS,
                variable_type=VariableType.STATIC,
            )
        )
    dag.add_edge(CausalEdge(source="A", target="B", mechanism="direct"))
    return dag


def _make_5var_data(n: int = 50, seed: int = 42) -> list[dict]:
    rng = random.Random(seed)
    return [{v: rng.gauss(0, 1) for v in ["A", "B", "C", "D", "E"]} for _ in range(n)]


# ---------------------------------------------------------------------------
# enterprise/auth.py — line 203-204 fix
# ---------------------------------------------------------------------------


class TestAuthNonAsciiSignature:
    """Cover lines 203-204: non-ASCII chars in signature trigger exception.

    The v1 test used '!!!not-valid-base64!!!' which base64.urlsafe_b64decode
    silently decodes (with validate=False). Non-ASCII chars like U+00DC
    cause ValueError in _bytes_from_decode_data's .encode('ascii') call.
    """

    def test_non_ascii_signature_returns_none(self):
        tp = TokenProvider(secret="test-secret")
        token = tp.issue("user")
        parts = token.split(".")
        parts[2] = "\u00dc\u00ee\u00f6"
        assert tp.validate(".".join(parts)) is None


# ---------------------------------------------------------------------------
# intelligence/validation.py — lines 87, 90
# ---------------------------------------------------------------------------


class TestValidationInnerBreaks:
    """Cover lines 87, 90: inner break inside conditioning loops.

    With 3 variables, max_tests hits the OUTER break (line 76).
    With 5 variables, conditioning sets grow large enough for
    max_tests to be hit inside the size-loop (line 87) and
    the combo-loop (line 90).
    """

    def test_max_tests_size_loop_break(self):
        """Hit line 87: break at start of conditioning size loop."""
        dag = _build_5var_dag()
        tester = DSeparationTester(dag)
        data = _make_5var_data()
        report = tester.test_all_implications(data, max_tests=4)
        assert report.total_implications == 4

    def test_max_tests_combo_loop_break(self):
        """Hit line 90: break inside combinations loop."""
        dag = _build_5var_dag()
        tester = DSeparationTester(dag)
        data = _make_5var_data()
        report = tester.test_all_implications(data, max_tests=6)
        assert report.total_implications == 6


# ---------------------------------------------------------------------------
# kernel/world_dag.py — lines 42, 85
# ---------------------------------------------------------------------------


class TestWorldDagRemoveNonexistent:
    """Cover line 42: remove_entity early return for missing ID."""

    def test_remove_missing_entity_is_noop(self):
        dag = WorldDAG()
        dag.add_entity(
            Entity(
                entity_id="A",
                name="A",
                causal_role=CausalRole.EXOGENOUS,
                variable_type=VariableType.STATIC,
            )
        )
        dag.remove_entity("NONEXISTENT")
        assert dag.get_entity("A") is not None


class TestWorldDagBfsVisitedContinue:
    """Cover line 85: BFS 'continue' for already-visited node.

    Diamond A->B, A->C, B->D, C->D: when adding edge E->A,
    _would_create_cycle BFS from A traverses children. D is
    reachable via both B and C, so it's queued twice — second
    dequeue hits the 'if node in visited: continue' at line 85.
    """

    def test_diamond_triggers_visited_continue(self):
        dag = WorldDAG()
        for name in ["A", "B", "C", "D", "E"]:
            dag.add_entity(
                Entity(
                    entity_id=name,
                    name=name,
                    causal_role=CausalRole.EXOGENOUS,
                    variable_type=VariableType.STATIC,
                )
            )
        dag.add_edge(CausalEdge(source="A", target="B", mechanism="direct"))
        dag.add_edge(CausalEdge(source="A", target="C", mechanism="direct"))
        dag.add_edge(CausalEdge(source="B", target="D", mechanism="direct"))
        dag.add_edge(CausalEdge(source="C", target="D", mechanism="direct"))
        # Adding E->A triggers _would_create_cycle(E, A) which BFS from A.
        # A's children are B,C. B's child is D, C's child is D.
        # D gets queued twice; second dequeue hits visited continue.
        dag.add_edge(CausalEdge(source="E", target="A", mechanism="direct"))
        # If we got here without CycleError, the BFS worked correctly
        assert dag.get_entity("E") is not None
