"""Tests for failure taxonomy SDK — create and validate failure records."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime

import pytest

from abi_control_core.sdk.failure_taxonomy import (
    FailureClass,
    FailureSeverity,
    create_failure_record,
    validate_failure_record,
)
from abi_control_core.sdk.validate import SchemaValidationError

# ── Valid fixture ───────────────────────────────────────────────────────

VALID_FAILURE_RECORD = {
    "failure_id": "550e8400-e29b-41d4-a716-446655440000",
    "run_id": "abc12345",
    "task_id": "task-001",
    "failure_class": "hallucinated_path",
    "severity": "high",
    "agent": "claude_code",
    "task_category": "code_generation",
    "repo": "abi-core",
    "failure_summary": "Agent referenced non-existent module path",
    "evidence": {
        "file_paths": ["src/foo/bar.py"],
        "line_ranges": [{"file": "src/foo/bar.py", "start": 10, "end": 15}],
        "error_output": "ModuleNotFoundError: No module named 'nonexistent'",
    },
    "mitigation": {
        "suggested_eval": "eval-file-existence",
        "suggested_gate": "static-analysis-gate",
        "suggested_hil": "escalate-on-import-error",
    },
    "timestamp": "2026-03-07T10:00:00Z",
    "notes": "This occurred during refactoring task.",
}


# ── Schema validation tests ─────────────────────────────────────────────


def test_valid_failure_record_passes_validation() -> None:
    """Test that a valid failure record passes schema validation."""
    validate_failure_record(VALID_FAILURE_RECORD)
    assert True  # No exception raised


def test_minimal_failure_record_passes_validation() -> None:
    """Test that a minimal failure record (no optional fields) passes validation."""
    minimal = {
        "failure_id": "550e8400-e29b-41d4-a716-446655440000",
        "run_id": "run-001",
        "task_id": "task-001",
        "failure_class": "other",
        "severity": "low",
        "agent": "human",
        "task_category": "documentation",
        "repo": "test-repo",
        "failure_summary": "Test failure",
        "evidence": {"file_paths": ["test.txt"]},
        "timestamp": "2026-03-07T10:00:00Z",
    }
    validate_failure_record(minimal)
    assert True


def test_missing_required_field_fails_validation() -> None:
    """Test that missing required fields cause validation to fail."""
    invalid = VALID_FAILURE_RECORD.copy()
    del invalid["run_id"]

    with pytest.raises(SchemaValidationError) as exc_info:
        validate_failure_record(invalid)
    assert "run_id" in str(exc_info.value).lower() or "required" in str(exc_info.value).lower()


def test_invalid_failure_class_fails_validation() -> None:
    """Test that an invalid failure_class enum value fails validation."""
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["failure_class"] = "not_a_real_failure_class"

    with pytest.raises(SchemaValidationError) as exc_info:
        validate_failure_record(invalid)
    assert "failure_class" in str(exc_info.value).lower() or "enum" in str(exc_info.value).lower()


def test_invalid_severity_fails_validation() -> None:
    """Test that an invalid severity enum value fails validation."""
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["severity"] = "ultra_critical"

    with pytest.raises(SchemaValidationError) as exc_info:
        validate_failure_record(invalid)
    assert "severity" in str(exc_info.value).lower() or "enum" in str(exc_info.value).lower()


def test_missing_file_paths_in_evidence_fails_validation() -> None:
    """Test that evidence without file_paths fails validation."""
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["evidence"] = {"error_output": "some error"}

    with pytest.raises(SchemaValidationError) as exc_info:
        validate_failure_record(invalid)
    assert "file_paths" in str(exc_info.value).lower() or "required" in str(exc_info.value).lower()


def test_empty_file_paths_fails_validation() -> None:
    """Test that empty file_paths array fails validation (minItems: 1)."""
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["evidence"] = {"file_paths": []}

    with pytest.raises(SchemaValidationError) as exc_info:
        validate_failure_record(invalid)
    # Schema enforces minItems: 1
    assert "file_paths" in str(exc_info.value).lower() or "minItems" in str(exc_info.value).lower()


def test_invalid_line_range_fails_validation() -> None:
    """Test that invalid line_range structure fails validation."""
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["evidence"] = {
        "file_paths": ["test.py"],
        "line_ranges": [{"file": "test.py", "start": 0, "end": 5}],  # start must be >= 1
    }

    with pytest.raises(SchemaValidationError) as exc_info:
        validate_failure_record(invalid)
    assert "minimum" in str(exc_info.value).lower() or "start" in str(exc_info.value).lower()


def test_invalid_uuid_format_warning() -> None:
    """Test that invalid UUID format is accepted (format validation not enforced).

    Note: The jsonschema validator does not enforce format validation by default.
    This test documents the current behavior. UUID format is advisory in the schema.
    """
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["failure_id"] = "not-a-uuid"

    # Currently passes - format validation is not enforced
    validate_failure_record(invalid)
    assert True


def test_invalid_timestamp_format_warning() -> None:
    """Test that invalid timestamp format is accepted (format validation not enforced).

    Note: The jsonschema validator does not enforce format validation by default.
    This test documents the current behavior. Timestamp format is advisory in the schema.
    """
    invalid = VALID_FAILURE_RECORD.copy()
    invalid["timestamp"] = "not-a-datetime"

    # Currently passes - format validation is not enforced
    validate_failure_record(invalid)
    assert True


# ── FailureClass enum tests ─────────────────────────────────────────────


def test_failure_class_enum_values() -> None:
    """Test that all expected failure_class values exist in the enum."""
    expected_classes = [
        "scope_expansion",
        "schema_drift",
        "invariant_violation",
        "hallucinated_path",
        "tests_pass_behavior_wrong",
        "accessibility_violation",
        "partial_refactor_break",
        "import_chain_break",
        "config_naming_drift",
        "event_payload_mismatch",
        "stub_not_wired",
        "dependency_version_conflict",
        "other",
    ]

    for class_name in expected_classes:
        # Check the enum has this value
        assert any(fc.value == class_name for fc in FailureClass)


def test_failure_class_enum_to_string() -> None:
    """Test that FailureClass enum converts to correct string value."""
    assert FailureClass.HALLUCINATED_PATH.value == "hallucinated_path"
    assert FailureClass.SCOPE_EXPANSION.value == "scope_expansion"
    assert FailureClass.OTHER.value == "other"


# ── FailureSeverity enum tests ──────────────────────────────────────────


def test_failure_severity_enum_values() -> None:
    """Test that all expected severity values exist in the enum."""
    expected_severities = ["critical", "high", "medium", "low"]

    for severity_name in expected_severities:
        assert any(fs.value == severity_name for fs in FailureSeverity)


def test_failure_severity_enum_to_string() -> None:
    """Test that FailureSeverity enum converts to correct string value."""
    assert FailureSeverity.CRITICAL.value == "critical"
    assert FailureSeverity.HIGH.value == "high"
    assert FailureSeverity.MEDIUM.value == "medium"
    assert FailureSeverity.LOW.value == "low"


# ── create_failure_record tests ─────────────────────────────────────────


def test_create_failure_record_with_enums() -> None:
    """Test that create_failure_record works with enum inputs."""
    record = create_failure_record(
        run_id="run-001",
        task_id="task-001",
        failure_class=FailureClass.HALLUCINATED_PATH,
        severity=FailureSeverity.HIGH,
        agent="claude_code",
        task_category="code_generation",
        repo="abi-core",
        summary="Test failure",
        evidence={"file_paths": ["src/test.py"]},
    )

    assert record["run_id"] == "run-001"
    assert record["task_id"] == "task-001"
    assert record["failure_class"] == "hallucinated_path"
    assert record["severity"] == "high"
    assert record["agent"] == "claude_code"
    assert "failure_id" in record
    assert "timestamp" in record
    # Should validate successfully
    validate_failure_record(record)


def test_create_failure_record_with_strings() -> None:
    """Test that create_failure_record works with string inputs."""
    record = create_failure_record(
        run_id="run-002",
        task_id="task-002",
        failure_class="schema_drift",
        severity="medium",
        agent="codex",
        task_category="test_generation",
        repo="abi-vec",
        summary="Schema validation failed",
        evidence={"file_paths": ["tests/test_api.py"]},
    )

    assert record["failure_class"] == "schema_drift"
    assert record["severity"] == "medium"
    validate_failure_record(record)


def test_create_failure_record_generates_uuid() -> None:
    """Test that create_failure_record generates a valid UUID for failure_id."""
    record = create_failure_record(
        run_id="run-003",
        task_id="task-003",
        failure_class=FailureClass.OTHER,
        severity=FailureSeverity.LOW,
        agent="human",
        task_category="documentation",
        repo="test-repo",
        summary="Test",
        evidence={"file_paths": ["readme.md"]},
    )

    # Should be a valid UUID
    parsed_uuid = uuid.UUID(record["failure_id"])
    assert isinstance(parsed_uuid, uuid.UUID)


def test_create_failure_record_generates_timestamp() -> None:
    """Test that create_failure_record generates an ISO 8601 timestamp."""
    before = datetime.now(UTC)
    record = create_failure_record(
        run_id="run-004",
        task_id="task-004",
        failure_class=FailureClass.OTHER,
        severity=FailureSeverity.LOW,
        agent="human",
        task_category="documentation",
        repo="test-repo",
        summary="Test",
        evidence={"file_paths": ["readme.md"]},
    )
    after = datetime.now(UTC)

    # Parse the timestamp
    ts = datetime.fromisoformat(record["timestamp"].replace("Z", "+00:00"))
    assert before <= ts <= after


def test_create_failure_record_with_mitigation() -> None:
    """Test that create_failure_record includes optional mitigation field."""
    mitigation = {
        "suggested_eval": "eval-test",
        "suggested_gate": "gate-test",
        "suggested_hil": "hil-test",
    }

    record = create_failure_record(
        run_id="run-005",
        task_id="task-005",
        failure_class=FailureClass.INVARIANT_VIOLATION,
        severity=FailureSeverity.CRITICAL,
        agent="qwen",
        task_category="code_generation",
        repo="abi-core",
        summary="Invariant broken",
        evidence={"file_paths": ["src/core.py"]},
        mitigation=mitigation,
    )

    assert "mitigation" in record
    assert record["mitigation"]["suggested_eval"] == "eval-test"
    validate_failure_record(record)


def test_create_failure_record_with_full_evidence() -> None:
    """Test that create_failure_record handles full evidence structure."""
    evidence = {
        "file_paths": ["src/foo.py", "src/bar.py"],
        "line_ranges": [
            {"file": "src/foo.py", "start": 10, "end": 20},
            {"file": "src/bar.py", "start": 5, "end": 8},
        ],
        "error_output": "ImportError: cannot import name 'Baz'",
    }

    record = create_failure_record(
        run_id="run-006",
        task_id="task-006",
        failure_class=FailureClass.IMPORT_CHAIN_BREAK,
        severity=FailureSeverity.HIGH,
        agent="claude_code",
        task_category="code_generation",
        repo="abi-vec",
        summary="Import chain broken during refactoring",
        evidence=evidence,
    )

    assert len(record["evidence"]["file_paths"]) == 2
    assert len(record["evidence"]["line_ranges"]) == 2
    assert "error_output" in record["evidence"]
    validate_failure_record(record)


def test_create_failure_record_validates_before_return() -> None:
    """Test that create_failure_record validates and raises if invalid."""
    # Pass invalid failure_class (not in enum)
    with pytest.raises(SchemaValidationError):
        create_failure_record(
            run_id="run-007",
            task_id="task-007",
            failure_class="invalid_class_not_in_enum",
            severity=FailureSeverity.LOW,
            agent="human",
            task_category="documentation",
            repo="test-repo",
            summary="Test",
            evidence={"file_paths": ["test.md"]},
        )
