"""
Unit tests for schema_refiner module (Phase 2 schema refinement).
"""

import pytest
import json
import copy
from markitect.schema_refiner import (
    SchemaRefiner,
    RefinementResult,
    RefinementAction
)
from markitect.schema_analyzer import IssueType


class TestSchemaRefiner:
    """Tests for SchemaRefiner class."""

    def test_refine_exact_count_array(self):
        """Test refinement of exact array counts."""
        schema = {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 5,
                    "maxItems": 5
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        assert len(result.actions_taken) > 0

        # Check that the array range was loosened
        refined_items = result.refined_schema["properties"]["items"]
        assert refined_items["minItems"] < 5
        assert refined_items["maxItems"] > 5

    def test_refine_const_value(self):
        """Test refinement of const constraints."""
        schema = {
            "type": "object",
            "properties": {
                "level": {
                    "type": "integer",
                    "const": 1
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        assert len(result.actions_taken) > 0

        # const should be removed and replaced with a range
        refined_level = result.refined_schema["properties"]["level"]
        assert "const" not in refined_level
        assert "minimum" in refined_level
        assert "maximum" in refined_level

    def test_refine_overly_specific_number(self):
        """Test rounding of overly specific numbers."""
        schema = {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 73
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, round_numbers=True)

        assert result.success

        # Should round to 70
        if len(result.actions_taken) > 0:
            refined_items = result.refined_schema["properties"]["items"]
            assert refined_items["minItems"] == 70

    def test_refine_narrow_range(self):
        """Test widening of narrow integer ranges."""
        schema = {
            "type": "object",
            "properties": {
                "score": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 6
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success

        # Range should be widened
        if len(result.actions_taken) > 0:
            refined_score = result.refined_schema["properties"]["score"]
            range_size = refined_score["maximum"] - refined_score["minimum"]
            assert range_size > 1

    def test_refine_nested_properties(self):
        """Test refinement of nested property structures."""
        schema = {
            "type": "object",
            "properties": {
                "outer": {
                    "type": "object",
                    "properties": {
                        "inner": {
                            "type": "array",
                            "minItems": 3,
                            "maxItems": 3
                        }
                    }
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        assert len(result.actions_taken) > 0

        # Check nested property was refined
        refined_inner = result.refined_schema["properties"]["outer"]["properties"]["inner"]
        assert refined_inner["minItems"] < 3
        assert refined_inner["maxItems"] > 3

    def test_refine_array_items_with_const(self):
        """Test refinement of array items with const properties."""
        schema = {
            "type": "object",
            "properties": {
                "headings": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "level": {
                                "type": "integer",
                                "const": 1
                            }
                        }
                    }
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        assert len(result.actions_taken) > 0

        # const in items should be refined
        refined_level = result.refined_schema["properties"]["headings"]["items"]["properties"]["level"]
        assert "const" not in refined_level

    def test_refine_no_changes_needed(self):
        """Test refinement of already flexible schema."""
        schema = {
            "type": "object",
            "x-markitect-sections": {
                "INTRO": {"classification": "required"}
            },
            "x-markitect-content-control": {
                "intro": {"content_quality": {"min_words": 50}}
            },
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 5,
                    "maxItems": 50  # Good range
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        # May have some minor improvements but should be mostly unchanged
        assert len(result.actions_taken) < 3

    def test_refine_with_disabled_options(self):
        """Test refinement with options disabled."""
        schema = {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 5,
                    "maxItems": 5
                },
                "count": {
                    "type": "integer",
                    "const": 73
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(
            schema,
            loosen_counts=False,  # Disabled
            round_numbers=False
        )

        assert result.success
        # No changes should be made since options are disabled
        assert len(result.actions_taken) == 0

    def test_refinement_action_details(self):
        """Test that refinement actions contain proper details."""
        schema = {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 5,
                    "maxItems": 5
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert len(result.actions_taken) > 0
        action = result.actions_taken[0]

        assert isinstance(action, RefinementAction)
        assert action.issue_type == IssueType.EXACT_COUNT
        assert "properties.items" in action.path
        assert action.old_value is not None
        assert action.new_value is not None
        assert "loosened" in action.description.lower() or "converted" in action.description.lower()

    def test_original_schema_unchanged(self):
        """Test that original schema is not modified."""
        schema = {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 5,
                    "maxItems": 5
                }
            }
        }

        original_schema = copy.deepcopy(schema)

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        # Original should be unchanged
        assert schema == original_schema

        # But refined should be different
        assert result.refined_schema != original_schema

    def test_format_refinement_report(self):
        """Test refinement report formatting."""
        schema = {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "minItems": 5,
                    "maxItems": 5
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)
        report = refiner.format_refinement_report(result)

        assert "Schema Refinement Report" in report
        assert "Actions Taken" in report or "No refinements needed" in report

    def test_refinement_with_multiple_issues(self):
        """Test refinement of schema with multiple issues."""
        schema = {
            "type": "object",
            "properties": {
                "array1": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 1
                },
                "array2": {
                    "type": "array",
                    "minItems": 73
                },
                "level": {
                    "type": "integer",
                    "const": 2
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(
            schema,
            loosen_counts=True,
            round_numbers=True
        )

        assert result.success
        assert len(result.actions_taken) >= 2  # Should fix multiple issues

    def test_navigation_to_deeply_nested_path(self):
        """Test path navigation for deeply nested schemas."""
        schema = {
            "type": "object",
            "properties": {
                "level1": {
                    "type": "object",
                    "properties": {
                        "level2": {
                            "type": "object",
                            "properties": {
                                "level3": {
                                    "type": "array",
                                    "minItems": 1,
                                    "maxItems": 1
                                }
                            }
                        }
                    }
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        # Should successfully navigate and refine deep path
        refined_level3 = result.refined_schema["properties"]["level1"]["properties"]["level2"]["properties"]["level3"]
        assert refined_level3["minItems"] < 1 or refined_level3["maxItems"] > 1

    def test_deprecated_extension_detection(self):
        """Test detection (but not automatic migration) of deprecated extensions."""
        schema = {
            "type": "object",
            "x-markitect-required-sections": ["INTRO"]
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, migrate_deprecated=True)

        assert result.success
        # Should document deprecated extension but not remove it automatically
        deprecated_actions = [a for a in result.actions_taken
                            if a.issue_type == IssueType.DEPRECATED_EXTENSIONS]
        # Migration is detected but not fully automated (too risky)
        assert len(deprecated_actions) >= 0

    def test_refine_empty_schema(self):
        """Test refinement of minimal schema."""
        schema = {
            "type": "object"
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema)

        assert result.success
        # Minimal schema shouldn't crash the refiner
        assert result.refined_schema is not None

    def test_refine_schema_with_string_const(self):
        """Test refinement of non-numeric const values."""
        schema = {
            "type": "object",
            "properties": {
                "status": {
                    "type": "string",
                    "const": "active"
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        # String const should be removed (can't be converted to range)
        if len(result.actions_taken) > 0:
            refined_status = result.refined_schema["properties"]["status"]
            assert "const" not in refined_status

    def test_complex_manpage_schema(self):
        """Test refinement of a realistic manpage schema."""
        schema = {
            "type": "object",
            "properties": {
                "headings": {
                    "type": "object",
                    "properties": {
                        "level_1": {
                            "type": "array",
                            "minItems": 1,
                            "maxItems": 1,
                            "items": {
                                "type": "object",
                                "properties": {
                                    "level": {
                                        "type": "integer",
                                        "const": 1
                                    }
                                }
                            }
                        },
                        "level_2": {
                            "type": "array",
                            "minItems": 3,
                            "maxItems": 30,
                            "items": {
                                "type": "object",
                                "properties": {
                                    "level": {
                                        "type": "integer",
                                        "const": 2
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        refiner = SchemaRefiner()
        result = refiner.refine_schema(schema, loosen_counts=True)

        assert result.success
        assert len(result.actions_taken) >= 2  # Should fix at least the exact counts

        # level_1 should be loosened
        refined_level_1 = result.refined_schema["properties"]["headings"]["properties"]["level_1"]
        assert refined_level_1["minItems"] < 1 or refined_level_1["maxItems"] > 1

        # const values in items should be loosened
        items_level_1 = refined_level_1["items"]["properties"]["level"]
        assert "const" not in items_level_1
