"""
Tests for Issue #50: Define metaschema for JSON schema structure

This test module defines comprehensive tests for the MarkiTect metaschema that extends
standard JSON Schema with MarkiTect-specific features like heading text capture,
content field instructions, and outline structure representation.

Following TDD8 methodology - these tests are written before implementation.
"""

import json
import pytest
from pathlib import Path
from typing import Dict, Any

from markitect.metaschema import MetaschemaValidator, MARKITECT_METASCHEMA_PATH


class TestIssue50MetaschemaDefinition:
    """Test suite for MarkiTect metaschema definition and validation."""

    def setup_method(self):
        """Set up test fixtures."""
        self.metaschema_validator = MetaschemaValidator()

    def test_metaschema_file_exists_and_is_valid_json(self):
        """Test that the metaschema JSON file exists and contains valid JSON."""
        # Arrange & Act
        metaschema_path = Path(MARKITECT_METASCHEMA_PATH)

        # Assert
        assert metaschema_path.exists(), f"Metaschema file should exist at {MARKITECT_METASCHEMA_PATH}"

        with open(metaschema_path) as f:
            metaschema = json.load(f)

        assert isinstance(metaschema, dict), "Metaschema should be a valid JSON object"
        assert "$schema" in metaschema, "Metaschema should have $schema property"
        assert "type" in metaschema, "Metaschema should have type property"

    def test_metaschema_extends_json_schema_draft_07(self):
        """Test that metaschema properly extends JSON Schema Draft 07."""
        # Arrange & Act
        metaschema = self.metaschema_validator.get_metaschema()

        # Assert
        assert metaschema["$schema"] == "http://json-schema.org/draft-07/schema#"
        assert metaschema["type"] == "object"
        assert "allOf" in metaschema, "Should extend base JSON Schema using allOf"

        # Should reference standard JSON Schema
        found_json_schema_ref = False
        for schema_ref in metaschema["allOf"]:
            if "$ref" in schema_ref and "json-schema.org" in schema_ref["$ref"]:
                found_json_schema_ref = True
                break
        assert found_json_schema_ref, "Should reference standard JSON Schema Draft 07"

    def test_metaschema_supports_heading_text_capture(self):
        """Test that metaschema supports heading text capture extensions."""
        # Arrange & Act
        metaschema = self.metaschema_validator.get_metaschema()

        # Assert - Check for MarkiTect-specific heading text properties
        markitect_properties = self._get_markitect_extensions(metaschema)

        assert "x-markitect-heading-text" in markitect_properties, \
            "Should support x-markitect-heading-text property"

        heading_text_schema = markitect_properties["x-markitect-heading-text"]
        assert heading_text_schema["type"] == "string"
        assert "description" in heading_text_schema
        assert "preserve actual heading text" in heading_text_schema["description"].lower()

    def test_metaschema_supports_content_field_instructions(self):
        """Test that metaschema supports content field instruction capabilities."""
        # Arrange & Act
        metaschema = self.metaschema_validator.get_metaschema()

        # Assert - Check for content instruction properties
        markitect_properties = self._get_markitect_extensions(metaschema)

        assert "x-markitect-content-instructions" in markitect_properties, \
            "Should support x-markitect-content-instructions property"

        instructions_schema = markitect_properties["x-markitect-content-instructions"]
        assert instructions_schema["type"] == "string"
        assert "description" in instructions_schema
        assert "content author" in instructions_schema["description"].lower()

    def test_metaschema_supports_outline_structure_representation(self):
        """Test that metaschema supports outline structure representation."""
        # Arrange & Act
        metaschema = self.metaschema_validator.get_metaschema()

        # Assert - Check for outline structure properties
        markitect_properties = self._get_markitect_extensions(metaschema)

        assert "x-markitect-outline-mode" in markitect_properties, \
            "Should support x-markitect-outline-mode property"

        outline_schema = markitect_properties["x-markitect-outline-mode"]
        assert outline_schema["type"] == "boolean"
        assert "description" in outline_schema

        assert "x-markitect-outline-depth" in markitect_properties, \
            "Should support x-markitect-outline-depth property"

        depth_schema = markitect_properties["x-markitect-outline-depth"]
        assert depth_schema["type"] == "integer"
        assert depth_schema["minimum"] == 1

    def test_metaschema_validates_standard_json_schema(self):
        """Test that metaschema accepts standard JSON Schema documents (backward compatibility)."""
        # Arrange
        standard_schema = {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "title": "Standard Schema",
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer", "minimum": 0}
            },
            "required": ["name"]
        }

        # Act & Assert
        is_valid = self.metaschema_validator.validate_schema(standard_schema)
        assert is_valid, "Standard JSON Schema should be valid against metaschema"

    def test_metaschema_validates_markitect_extended_schema(self):
        """Test that metaschema accepts MarkiTect extended schemas."""
        # Arrange
        markitect_schema = {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "title": "MarkiTect Extended Schema",
            "x-markitect-outline-mode": True,
            "x-markitect-outline-depth": 3,
            "properties": {
                "headings": {
                    "type": "object",
                    "properties": {
                        "level_1": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "content": {"type": "string"},
                                    "x-markitect-heading-text": "Introduction",
                                    "x-markitect-content-instructions": "Provide overview of the document"
                                }
                            }
                        }
                    }
                }
            }
        }

        # Act & Assert
        is_valid = self.metaschema_validator.validate_schema(markitect_schema)
        assert is_valid, "MarkiTect extended schema should be valid against metaschema"

    def test_metaschema_rejects_invalid_markitect_extensions(self):
        """Test that metaschema rejects invalid MarkiTect extension values."""
        # Arrange
        invalid_schemas = [
            # Invalid outline depth (negative)
            {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "x-markitect-outline-depth": -1
            },
            # Invalid outline mode (not boolean)
            {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "x-markitect-outline-mode": "true"
            },
            # Invalid heading text (not string)
            {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "properties": {
                    "heading": {
                        "x-markitect-heading-text": 123
                    }
                }
            }
        ]

        # Act & Assert
        for invalid_schema in invalid_schemas:
            is_valid = self.metaschema_validator.validate_schema(invalid_schema)
            assert not is_valid, f"Invalid schema should be rejected: {invalid_schema}"

    def test_metaschema_validation_provides_detailed_errors(self):
        """Test that metaschema validation provides detailed error messages."""
        # Arrange
        invalid_schema = {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "x-markitect-outline-depth": "not-a-number"
        }

        # Act
        validation_result = self.metaschema_validator.validate_schema_with_errors(invalid_schema)

        # Assert
        assert not validation_result.is_valid
        assert len(validation_result.errors) > 0

        error_messages = [error.message for error in validation_result.errors]
        assert any("x-markitect-outline-depth" in msg for msg in error_messages), \
            "Error should mention the problematic MarkiTect extension"

    def test_metaschema_supports_content_instruction_types(self):
        """Test that metaschema supports different types of content instructions."""
        # Arrange & Act
        metaschema = self.metaschema_validator.get_metaschema()
        markitect_properties = self._get_markitect_extensions(metaschema)

        # Assert - Check for instruction type support
        assert "x-markitect-instruction-type" in markitect_properties, \
            "Should support x-markitect-instruction-type property"

        instruction_type_schema = markitect_properties["x-markitect-instruction-type"]
        assert instruction_type_schema["type"] == "string"
        assert "enum" in instruction_type_schema

        expected_types = ["description", "example", "constraint", "template"]
        for instruction_type in expected_types:
            assert instruction_type in instruction_type_schema["enum"], \
                f"Should support {instruction_type} instruction type"

    def test_metaschema_supports_schema_metadata(self):
        """Test that metaschema supports MarkiTect-specific schema metadata."""
        # Arrange & Act
        metaschema = self.metaschema_validator.get_metaschema()
        markitect_properties = self._get_markitect_extensions(metaschema)

        # Assert - Check for metadata properties
        assert "x-markitect-generated-from" in markitect_properties, \
            "Should support x-markitect-generated-from property"

        assert "x-markitect-generation-mode" in markitect_properties, \
            "Should support x-markitect-generation-mode property"

        generation_mode = markitect_properties["x-markitect-generation-mode"]
        assert "enum" in generation_mode
        assert "outline" in generation_mode["enum"]
        assert "full" in generation_mode["enum"]

    def test_existing_schemas_validate_against_metaschema(self):
        """Test that all existing MarkiTect schemas validate against the new metaschema."""
        # Arrange - Get sample existing schema structure
        existing_schema = {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "title": "Schema for example.md",
            "description": "JSON schema describing the structure of example.md",
            "properties": {
                "headings": {
                    "type": "object",
                    "properties": {
                        "level_1": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "content": {"type": "string"},
                                    "level": {"type": "integer"},
                                    "position": {"type": "integer"}
                                },
                                "required": ["content", "level"]
                            }
                        }
                    }
                },
                "metadata": {
                    "type": "object",
                    "properties": {
                        "total_elements": {"type": "integer"},
                        "structure_types": {
                            "type": "array",
                            "items": {"type": "string"}
                        }
                    }
                }
            }
        }

        # Act & Assert
        is_valid = self.metaschema_validator.validate_schema(existing_schema)
        assert is_valid, "Existing MarkiTect schemas should remain valid (backward compatibility)"

    def _get_markitect_extensions(self, metaschema: Dict[str, Any]) -> Dict[str, Any]:
        """Helper to extract MarkiTect extension properties from metaschema."""
        # Look for MarkiTect extensions in the allOf extension
        for extension in metaschema.get("allOf", []):
            if "properties" in extension:
                markitect_props = {}
                for prop_name, prop_schema in extension["properties"].items():
                    if prop_name.startswith("x-markitect-"):
                        markitect_props[prop_name] = prop_schema
                if markitect_props:
                    return markitect_props

        # Fallback: look in patternProperties for x-markitect- patterns
        pattern_props = metaschema.get("patternProperties", {})
        for pattern, schema in pattern_props.items():
            if "x-markitect" in pattern:
                return {pattern: schema}

        return {}


class TestMetaschemaValidator:
    """Test the MetaschemaValidator utility class."""

    def test_metaschema_validator_can_be_created(self):
        """Test that MetaschemaValidator can be instantiated."""
        # Act & Assert
        validator = MetaschemaValidator()
        assert validator is not None

    def test_metaschema_validator_loads_metaschema(self):
        """Test that MetaschemaValidator properly loads the metaschema."""
        # Arrange & Act
        validator = MetaschemaValidator()
        metaschema = validator.get_metaschema()

        # Assert
        assert isinstance(metaschema, dict)
        assert "$schema" in metaschema
        assert metaschema["$schema"] == "http://json-schema.org/draft-07/schema#"

    def test_metaschema_validator_caches_metaschema(self):
        """Test that MetaschemaValidator caches the loaded metaschema."""
        # Arrange & Act
        validator = MetaschemaValidator()
        metaschema1 = validator.get_metaschema()
        metaschema2 = validator.get_metaschema()

        # Assert
        assert metaschema1 is metaschema2, "Should cache metaschema instance"