"""Chaos: Spec-Level Failure.

Proving: "Corrupted intent is always rejected - within the validator's scope."

NOTE: The current Rust validator checks api_version, identity_version, entity,
sources, decision, threshold ordering, weight bounds, duplicate rule names,
and similarity rule completeness (algorithm + threshold required). It is
permissive about missing rules, blocking, and unknown rule types. Tests
document both validated and unvalidated boundaries.
"""
from __future__ import annotations

import pytest
import yaml

from tests.contract.conftest import MINIMAL_VALID_SPEC, make_spec, requires_native

pytestmark = requires_native

from kanoniv.validate import validate


# ---------------------------------------------------------------------------
# Structural corruption — test what IS and IS NOT validated
# ---------------------------------------------------------------------------


class TestStructuralCorruption:
    def test_drop_entity_fails(self):
        """Dropping entity → validation error (entity IS required)."""
        parsed = yaml.safe_load(MINIMAL_VALID_SPEC)
        parsed.pop("entity", None)
        broken_yaml = yaml.dump(parsed, default_flow_style=False)
        spec = make_spec(broken_yaml)
        result = validate(spec)
        assert not result.valid

    def test_drop_api_version_fails(self):
        """Dropping api_version → validation error."""
        parsed = yaml.safe_load(MINIMAL_VALID_SPEC)
        parsed.pop("api_version", None)
        broken_yaml = yaml.dump(parsed, default_flow_style=False)
        spec = make_spec(broken_yaml)
        result = validate(spec)
        assert not result.valid

    def test_drop_identity_version_fails(self):
        """Dropping identity_version → validation error."""
        parsed = yaml.safe_load(MINIMAL_VALID_SPEC)
        parsed.pop("identity_version", None)
        broken_yaml = yaml.dump(parsed, default_flow_style=False)
        spec = make_spec(broken_yaml)
        result = validate(spec)
        assert not result.valid

    @pytest.mark.parametrize("key", ["rules", "blocking"])
    def test_drop_optional_key_accepted(self, key):
        """Dropping rules/blocking -> currently accepted (permissive)."""
        parsed = yaml.safe_load(MINIMAL_VALID_SPEC)
        parsed.pop(key, None)
        broken_yaml = yaml.dump(parsed, default_flow_style=False)
        spec = make_spec(broken_yaml)
        result = validate(spec)
        # Document: validator is permissive about these sections
        assert result.valid

    @pytest.mark.parametrize("key", ["sources", "decision"])
    def test_drop_required_key_rejected(self, key):
        """Dropping sources/decision -> validation error (required fields)."""
        parsed = yaml.safe_load(MINIMAL_VALID_SPEC)
        parsed.pop(key, None)
        broken_yaml = yaml.dump(parsed, default_flow_style=False)
        spec = make_spec(broken_yaml)
        result = validate(spec)
        assert not result.valid


# ---------------------------------------------------------------------------
# Type confusion
# ---------------------------------------------------------------------------


class TestTypeConfusion:
    def test_wrong_type_for_threshold_accepted(self):
        """match: 'high' (string) → currently accepted (validator doesn't type-check thresholds strictly)."""
        modified = MINIMAL_VALID_SPEC.replace("match: 0.7", 'match: "high"')
        spec = make_spec(modified)
        result = validate(spec)
        # Document behavior: string thresholds are currently accepted
        assert isinstance(result.valid, bool)

    def test_wrong_type_for_weight_accepted(self):
        """weight: true → currently accepted (validator doesn't type-check weight types strictly)."""
        modified = MINIMAL_VALID_SPEC.replace("weight: 1.0", "weight: true")
        spec = make_spec(modified)
        result = validate(spec)
        assert isinstance(result.valid, bool)

    def test_extra_unknown_keys_accepted_or_rejected(self):
        """Spec with unknown keys → consistent behavior (not crash)."""
        modified = MINIMAL_VALID_SPEC + "unknown_key: some_value\n"
        spec = make_spec(modified)
        result = validate(spec)
        assert isinstance(result.valid, bool)


# ---------------------------------------------------------------------------
# Incomplete rules — what IS and IS NOT validated
# ---------------------------------------------------------------------------


class TestIncompleteRules:
    def test_rule_missing_name(self):
        """Rule without name: → rejected (names required for dedup check)."""
        yaml_str = """\
api_version: kanoniv/v2
identity_version: test_v1.0
entity:
  name: customer
sources:
  - name: src_a
    system: csv
    table: src_a
    id: id
    attributes:
      email: email
blocking:
  strategy: composite
  keys:
    - [email]
rules:
  - type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.7
    review: 0.4
  conflict_strategy: prefer_high_confidence
"""
        spec = make_spec(yaml_str)
        result = validate(spec)
        assert not result.valid

    def test_rule_missing_type(self):
        """Rule without type: → rejected."""
        yaml_str = """\
api_version: kanoniv/v2
identity_version: test_v1.0
entity:
  name: customer
sources:
  - name: src_a
    system: csv
    table: src_a
    id: id
    attributes:
      email: email
blocking:
  strategy: composite
  keys:
    - [email]
rules:
  - name: email_match
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.7
    review: 0.4
  conflict_strategy: prefer_high_confidence
"""
        spec = make_spec(yaml_str)
        result = validate(spec)
        assert not result.valid

    def test_similarity_rule_without_algorithm_rejected(self):
        """Similarity rule without algorithm -> rejected (algorithm is required)."""
        yaml_str = """\
api_version: kanoniv/v2
identity_version: test_v1.0
entity:
  name: customer
sources:
  - name: src_a
    system: csv
    table: src_a
    id: id
    attributes:
      name: name
blocking:
  strategy: composite
  keys:
    - [name]
rules:
  - name: name_sim
    type: similarity
    field: name
    threshold: 0.8
    weight: 1.0
decision:
  thresholds:
    match: 0.7
    review: 0.4
  conflict_strategy: prefer_high_confidence
"""
        spec = make_spec(yaml_str)
        result = validate(spec)
        assert not result.valid

    def test_similarity_rule_without_threshold_rejected(self):
        """Similarity rule without threshold -> rejected (threshold is required)."""
        yaml_str = """\
api_version: kanoniv/v2
identity_version: test_v1.0
entity:
  name: customer
sources:
  - name: src_a
    system: csv
    table: src_a
    id: id
    attributes:
      name: name
blocking:
  strategy: composite
  keys:
    - [name]
rules:
  - name: name_sim
    type: similarity
    field: name
    algorithm: jaro_winkler
    weight: 1.0
decision:
  thresholds:
    match: 0.7
    review: 0.4
  conflict_strategy: prefer_high_confidence
"""
        spec = make_spec(yaml_str)
        result = validate(spec)
        assert not result.valid
