"""Tests for doc-sync apply_rules module."""

from __future__ import annotations


class TestRuleEngine:
    """Test suite for rule engine."""

    def test_load_rules_from_yaml(self):
        """Load rules from YAML configuration."""
        from scripts.doc_sync.apply_rules import RuleEngine

        engine = RuleEngine('.github/doc-sync-rules.yaml')

        assert 'removed_api_reference' in engine.rules
        assert 'stale_signature' in engine.rules
        assert 'missing_parameter_doc' in engine.rules

    def test_error_rule_severity(self):
        """Error rules have correct severity."""
        from scripts.doc_sync.apply_rules import RuleEngine

        engine = RuleEngine('.github/doc-sync-rules.yaml')

        assert engine.rules['removed_api_reference'].severity == 'error'
        assert engine.rules['breaking_signature_change'].severity == 'error'
        assert engine.rules['missing_docstring'].severity == 'error'

    def test_warning_rule_severity(self):
        """Warning rules have correct severity."""
        from scripts.doc_sync.apply_rules import RuleEngine

        engine = RuleEngine('.github/doc-sync-rules.yaml')

        assert engine.rules['stale_signature'].severity == 'warning'
        assert engine.rules['missing_new_api_doc'].severity == 'warning'
        assert engine.rules['stale_code_example'].severity == 'warning'

    def test_info_rule_severity(self):
        """Info rules have correct severity."""
        from scripts.doc_sync.apply_rules import RuleEngine

        engine = RuleEngine('.github/doc-sync-rules.yaml')

        assert engine.rules['missing_parameter_doc'].severity == 'info'
        assert engine.rules['missing_return_doc'].severity == 'info'


class TestIssueDetection:
    """Test suite for issue detection."""

    def test_detect_removed_api_reference(self):
        """Detect when removed API is still referenced in docs."""
        from scripts.doc_sync.analyze import CodeChange, DocContent
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        changes = [
            CodeChange(
                file_path='synapse_sdk/plugins/action.py',
                change_type='removed',
                element_type='class',
                element_name='OldClass',
                old_signature='class OldClass:',
                new_signature=None,
                docstring_changed=True,
                breaking_change=True,
                impact_level='critical',
                is_public=True,
            )
        ]

        doc_contents = {
            'docs/docs/plugins/index.md': DocContent(
                file_path='docs/docs/plugins/index.md',
                code_references=['OldClass', 'BaseAction'],
                code_examples=['from synapse_sdk import OldClass'],
                api_signatures=[],
            )
        }

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        error_issues = [i for i in issues if i.severity == 'error']
        assert len(error_issues) >= 1
        assert any(i.rule == 'removed_api_reference' for i in error_issues)

    def test_detect_stale_signature(self):
        """Detect when function signature changed but docs not updated."""
        from scripts.doc_sync.analyze import CodeChange, DocContent
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        changes = [
            CodeChange(
                file_path='synapse_sdk/plugins/action.py',
                change_type='modified',
                element_type='function',
                element_name='execute',
                old_signature='def execute(params: dict) -> dict:',
                new_signature='def execute(params: dict, timeout: int = 30) -> dict:',
                docstring_changed=False,
                breaking_change=False,
                impact_level='high',
                is_public=True,
            )
        ]

        doc_contents = {
            'docs/docs/plugins/index.md': DocContent(
                file_path='docs/docs/plugins/index.md',
                code_references=['execute'],
                code_examples=['result = execute(params)'],
                api_signatures=['execute'],
            )
        }

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        warning_issues = [i for i in issues if i.severity == 'warning']
        assert any(i.rule == 'stale_signature' for i in warning_issues)

    def test_detect_stale_signature_with_doc_line_number(self):
        """Detect stale signature and include document line number for suggestion."""
        from scripts.doc_sync.analyze import CodeChange, DocContent
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        changes = [
            CodeChange(
                file_path='synapse_sdk/plugins/action.py',
                change_type='modified',
                element_type='function',
                element_name='my_function',
                old_signature='def my_function(name: str) -> str:',
                new_signature='def my_function(name: str, age: int = 0) -> str:',
                docstring_changed=False,
                breaking_change=False,
                impact_level='high',
                is_public=True,
            )
        ]

        # DocContent with signature_lines mapping
        doc_contents = {
            'docs/docs/test.md': DocContent(
                file_path='docs/docs/test.md',
                code_references=['my_function'],
                code_examples=[],
                api_signatures=['my_function'],
                signature_lines={'my_function': [(38, 'def my_function(name: str) -> str:')]},
            )
        }

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        stale_issues = [i for i in issues if i.rule == 'stale_signature']
        assert len(stale_issues) >= 1

        # Check that the issue uses doc file path and line number
        issue = stale_issues[0]
        assert issue.doc_file == 'docs/docs/test.md'
        assert issue.line == 38  # Line number from signature_lines
        assert 'age' in issue.suggestion_code  # New parameter should be in suggestion

    def test_stale_signature_suggestion_code_generation(self):
        """Verify suggestion_code contains the new signature for doc files."""
        from scripts.doc_sync.analyze import CodeChange, DocContent
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        new_sig = 'def calculate(x: int, y: int, precision: int = 2) -> float:'
        changes = [
            CodeChange(
                file_path='synapse_sdk/math.py',
                change_type='modified',
                element_type='function',
                element_name='calculate',
                old_signature='def calculate(x: int, y: int) -> float:',
                new_signature=new_sig,
                docstring_changed=False,
                breaking_change=False,
                impact_level='high',
                is_public=True,
            )
        ]

        doc_contents = {
            'docs/api.md': DocContent(
                file_path='docs/api.md',
                code_references=['calculate'],
                code_examples=[],
                api_signatures=['calculate'],
                signature_lines={'calculate': [(15, 'def calculate(x: int, y: int) -> float:')]},
            )
        }

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        stale_issues = [i for i in issues if i.rule == 'stale_signature']
        assert len(stale_issues) == 1

        issue = stale_issues[0]
        # suggestion_code should contain the new signature
        assert issue.suggestion_code == new_sig

    def test_detect_missing_new_api_doc(self):
        """Detect when new public API has no documentation."""
        from scripts.doc_sync.analyze import CodeChange, DocContent
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        changes = [
            CodeChange(
                file_path='synapse_sdk/plugins/action.py',
                change_type='added',
                element_type='class',
                element_name='NewAction',
                old_signature=None,
                new_signature='class NewAction(BaseAction):',
                docstring_changed=True,
                breaking_change=False,
                impact_level='high',
                is_public=True,
            )
        ]

        doc_contents = {
            'docs/docs/plugins/index.md': DocContent(
                file_path='docs/docs/plugins/index.md',
                code_references=['BaseAction'],  # NewAction not referenced
                code_examples=[],
                api_signatures=[],
            )
        }

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        warning_issues = [i for i in issues if i.severity == 'warning']
        assert any(i.rule == 'missing_new_api_doc' for i in warning_issues)

    def test_ignore_private_api_changes(self):
        """Private API changes should not trigger documentation issues."""
        from scripts.doc_sync.analyze import CodeChange
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        changes = [
            CodeChange(
                file_path='synapse_sdk/plugins/action.py',
                change_type='added',
                element_type='function',
                element_name='_private_helper',
                old_signature=None,
                new_signature='def _private_helper() -> None:',
                docstring_changed=True,
                breaking_change=False,
                impact_level='low',
                is_public=False,
            )
        ]

        doc_contents = {}

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        # Should not create issues for private APIs
        assert len(issues) == 0

    def test_detect_missing_docstring(self):
        """Detect when public API has no docstring."""
        from scripts.doc_sync.analyze import CodeChange
        from scripts.doc_sync.apply_rules import RuleEngine, detect_issues

        changes = [
            CodeChange(
                file_path='synapse_sdk/plugins/action.py',
                change_type='added',
                element_type='function',
                element_name='new_function',
                old_signature=None,
                new_signature='def new_function() -> dict:',
                docstring_changed=False,
                breaking_change=False,
                impact_level='high',
                is_public=True,
                has_docstring=False,
            )
        ]

        doc_contents = {}

        engine = RuleEngine('.github/doc-sync-rules.yaml')
        issues = detect_issues(changes, doc_contents, engine)

        error_issues = [i for i in issues if i.severity == 'error']
        assert any(i.rule == 'missing_docstring' for i in error_issues)


class TestIssueSeverityFiltering:
    """Test suite for severity-based filtering."""

    def test_filter_by_severity(self):
        """Filter issues by severity level."""
        from scripts.doc_sync.apply_rules import DocSyncIssue, filter_by_severity

        issues = [
            DocSyncIssue(
                rule='removed_api_reference',
                severity='error',
                code_file='test.py',
                doc_file='test.md',
                element_name='OldClass',
                description='Test',
                suggestion='Fix',
                line=10,
            ),
            DocSyncIssue(
                rule='stale_signature',
                severity='warning',
                code_file='test.py',
                doc_file='test.md',
                element_name='func',
                description='Test',
                suggestion='Fix',
                line=20,
            ),
            DocSyncIssue(
                rule='missing_param_doc',
                severity='info',
                code_file='test.py',
                doc_file='test.md',
                element_name='func',
                description='Test',
                suggestion='Fix',
                line=30,
            ),
        ]

        error_only = filter_by_severity(issues, min_severity='error')
        assert len(error_only) == 1

        warning_and_above = filter_by_severity(issues, min_severity='warning')
        assert len(warning_and_above) == 2

        all_issues = filter_by_severity(issues, min_severity='info')
        assert len(all_issues) == 3

    def test_count_by_severity(self):
        """Count issues by severity level."""
        from scripts.doc_sync.apply_rules import DocSyncIssue, count_by_severity

        issues = [
            DocSyncIssue(
                rule='r1',
                severity='error',
                code_file='',
                doc_file='',
                element_name='',
                description='',
                suggestion='',
                line=0,
            ),
            DocSyncIssue(
                rule='r2',
                severity='error',
                code_file='',
                doc_file='',
                element_name='',
                description='',
                suggestion='',
                line=0,
            ),
            DocSyncIssue(
                rule='r3',
                severity='warning',
                code_file='',
                doc_file='',
                element_name='',
                description='',
                suggestion='',
                line=0,
            ),
            DocSyncIssue(
                rule='r4',
                severity='info',
                code_file='',
                doc_file='',
                element_name='',
                description='',
                suggestion='',
                line=0,
            ),
        ]

        counts = count_by_severity(issues)
        assert counts['error'] == 2
        assert counts['warning'] == 1
        assert counts['info'] == 1
