"""
Unit tests for sprint-review workflow Step 1.6: Test plan retrieval from work items.

Tests the logic for retrieving acceptance test plan files from EPIC work item
attachments/links, validating content, and handling various error conditions.
"""

import pytest
from pathlib import Path
from unittest.mock import Mock, patch, mock_open
import tempfile
import shutil


class TestTestPlanRetrievalLogic:
    """Test test plan retrieval logic from work items."""

    def test_azure_devops_retrieval_from_attachment(self):
        """Test retrieving test plan from Azure DevOps attachment."""
        # Mock adapter
        adapter = Mock()
        adapter.platform = 'azure-devops'

        # Mock work item with attachment
        work_item = {
            'relations': [
                {
                    'rel': 'AttachedFile',
                    'url': 'https://dev.azure.com/org/proj/_apis/wit/attachments/abc123',
                    'attributes': {
                        'name': 'epic-123-test-plan.md'
                    }
                }
            ]
        }
        adapter.get_work_item.return_value = work_item

        # Mock test plan content on filesystem
        test_plan_content = """# EPIC Test Plan

## EPIC Overview
Epic #123: User Authentication

## FEATURES Covered
- Feature 1: Login
- Feature 2: Logout

## Test Cases
### Test Case TC-001
- Description: User can log in with valid credentials
- Inputs: username, password
- Expected Outputs: 200 OK, auth token
- Pass/Fail Conditions: Token received and valid
"""

        with tempfile.TemporaryDirectory() as tmpdir:
            test_plans_dir = Path(tmpdir) / 'acceptance-tests'
            test_plans_dir.mkdir(parents=True)

            test_plan_path = test_plans_dir / 'epic-123-test-plan.md'
            test_plan_path.write_text(test_plan_content, encoding='utf-8')

            # Simulate retrieval
            epic_id = 123
            attachment_found = False
            retrieved_content = None

            for relation in work_item['relations']:
                if 'AttachedFile' in relation.get('rel', ''):
                    attachment_name = relation['attributes']['name']
                    if f'epic-{epic_id}-test-plan.md' in attachment_name:
                        # Read from local (simulating download)
                        if test_plan_path.exists():
                            retrieved_content = test_plan_path.read_text(encoding='utf-8')
                            attachment_found = True
                            break

            assert attachment_found is True
            assert retrieved_content is not None
            assert 'EPIC Overview' in retrieved_content
            assert 'Test Case TC-001' in retrieved_content

    def test_file_based_retrieval_from_comment(self):
        """Test retrieving test plan from file-based adapter via comment path."""
        # Mock adapter
        adapter = Mock()
        adapter.platform = 'file-based'

        # Mock work item with test plan path in comments
        work_item = {
            'comments': [
                {
                    'text': 'Test Plan: .claude/acceptance-tests/epic-456-test-plan.md\n\nGenerated by qa-tester agent'
                }
            ]
        }
        adapter.get_work_item.return_value = work_item

        # Mock test plan file
        test_plan_content = """# EPIC Test Plan

## EPIC Overview
Epic #456: Payment Processing

## Test Cases
### Test Case TC-001
Test payment flow
"""

        with tempfile.TemporaryDirectory() as tmpdir:
            test_plans_dir = Path(tmpdir) / 'acceptance-tests'
            test_plans_dir.mkdir(parents=True)

            test_plan_path = test_plans_dir / 'epic-456-test-plan.md'
            test_plan_path.write_text(test_plan_content, encoding='utf-8')

            # Simulate retrieval from comment
            import re
            test_plan_path_from_comment = None

            for comment in work_item['comments']:
                comment_text = comment.get('text', '')
                if 'Test Plan:' in comment_text:
                    match = re.search(r'Test Plan:\s*(.+\.md)', comment_text)
                    if match:
                        test_plan_path_from_comment = match.group(1).strip()
                        break

            assert test_plan_path_from_comment is not None
            assert 'epic-456-test-plan.md' in test_plan_path_from_comment

            # Read test plan
            retrieved_content = test_plan_path.read_text(encoding='utf-8')
            assert 'Payment Processing' in retrieved_content

    def test_file_based_retrieval_from_expected_location(self):
        """Test retrieving test plan from expected location when no comment exists."""
        # Mock adapter
        adapter = Mock()
        adapter.platform = 'file-based'

        # Mock work item without test plan path in comments
        work_item = {
            'comments': []
        }
        adapter.get_work_item.return_value = work_item

        # Mock test plan file at expected location
        test_plan_content = """# EPIC Test Plan
Epic #789
"""

        with tempfile.TemporaryDirectory() as tmpdir:
            test_plans_dir = Path(tmpdir) / 'acceptance-tests'
            test_plans_dir.mkdir(parents=True)

            epic_id = 789
            test_plan_path = test_plans_dir / f'epic-{epic_id}-test-plan.md'
            test_plan_path.write_text(test_plan_content, encoding='utf-8')

            # Simulate retrieval from expected location
            retrieved_content = None
            if test_plan_path.exists():
                retrieved_content = test_plan_path.read_text(encoding='utf-8')

            assert retrieved_content is not None
            assert 'Epic #789' in retrieved_content

    def test_content_validation_success(self):
        """Test validation of test plan content with all required sections."""
        test_plan_content = """# EPIC Acceptance Test Plan

## EPIC Overview
Epic #100: Core Feature

## FEATURES Covered
- Feature 1
- Feature 2

## Test Cases

### Test Case TC-001
Description: Test the core functionality
Inputs: User data
Expected Outputs: Success response
Pass/Fail Conditions: HTTP 200
"""

        # Validate content
        required_sections = ['EPIC', 'FEATURE', 'Test Case']
        missing_sections = []

        for section in required_sections:
            if section.lower() not in test_plan_content.lower():
                missing_sections.append(section)

        assert len(missing_sections) == 0
        assert len(test_plan_content) >= 100

    def test_content_validation_missing_sections(self):
        """Test validation detects missing required sections."""
        test_plan_content = """# Test Plan

Some content here but missing key sections.
"""

        # Validate content
        required_sections = ['EPIC', 'FEATURE', 'Test Case']
        missing_sections = []

        for section in required_sections:
            if section.lower() not in test_plan_content.lower():
                missing_sections.append(section)

        assert 'EPIC' in missing_sections
        assert 'FEATURE' in missing_sections
        assert 'Test Case' in missing_sections

    def test_content_validation_too_short(self):
        """Test validation detects unusually short content."""
        test_plan_content = "Short"

        # Check file size
        is_too_short = len(test_plan_content) < 100

        assert is_too_short is True

    def test_retrieval_failure_file_not_found(self):
        """Test handling of FileNotFoundError during retrieval."""
        epic_id = 999

        with tempfile.TemporaryDirectory() as tmpdir:
            test_plan_path = Path(tmpdir) / f'epic-{epic_id}-test-plan.md'

            # Try to read non-existent file
            try:
                content = test_plan_path.read_text(encoding='utf-8')
                assert False, "Should have raised FileNotFoundError"
            except FileNotFoundError as e:
                # Expected
                error_reason = f'File not found: {e}'
                assert 'File not found' in error_reason

    def test_retrieval_failure_permission_denied(self):
        """Test handling of PermissionError during retrieval."""
        with tempfile.TemporaryDirectory() as tmpdir:
            test_plan_path = Path(tmpdir) / 'epic-111-test-plan.md'
            test_plan_path.write_text("Content", encoding='utf-8')

            # Change permissions to read-only for owner, no access for others
            test_plan_path.chmod(0o000)

            # Try to read file with no permissions
            try:
                content = test_plan_path.read_text(encoding='utf-8')
                assert False, "Should have raised PermissionError"
            except PermissionError as e:
                # Expected
                error_reason = f'Permission denied: {e}'
                assert 'Permission denied' in error_reason
            finally:
                # Restore permissions for cleanup
                test_plan_path.chmod(0o644)

    def test_retrieval_failure_corrupt_encoding(self):
        """Test handling of UnicodeDecodeError for corrupted files."""
        with tempfile.TemporaryDirectory() as tmpdir:
            test_plan_path = Path(tmpdir) / 'epic-222-test-plan.md'

            # Write binary data that's not valid UTF-8
            test_plan_path.write_bytes(b'\x80\x81\x82\x83')

            # Try to read as UTF-8
            try:
                content = test_plan_path.read_text(encoding='utf-8')
                assert False, "Should have raised UnicodeDecodeError"
            except UnicodeDecodeError as e:
                # Expected
                error_reason = f'File corrupted or invalid encoding: {e}'
                assert 'corrupted' in error_reason.lower() or 'encoding' in error_reason.lower()

    def test_multiple_epics_retrieval(self):
        """Test retrieving test plans for multiple EPICs."""
        testable_epics = [
            {'id': 100, 'title': 'EPIC 1'},
            {'id': 200, 'title': 'EPIC 2'},
            {'id': 300, 'title': 'EPIC 3'},
        ]

        with tempfile.TemporaryDirectory() as tmpdir:
            test_plans_dir = Path(tmpdir) / 'acceptance-tests'
            test_plans_dir.mkdir(parents=True)

            # Create test plans for each EPIC
            for epic in testable_epics:
                epic_id = epic['id']
                test_plan_path = test_plans_dir / f'epic-{epic_id}-test-plan.md'
                test_plan_path.write_text(f"# Test Plan for EPIC {epic_id}", encoding='utf-8')

            # Retrieve all test plans
            retrieved_test_plans = []

            for epic in testable_epics:
                epic_id = epic['id']
                test_plan_path = test_plans_dir / f'epic-{epic_id}-test-plan.md'

                if test_plan_path.exists():
                    content = test_plan_path.read_text(encoding='utf-8')
                    retrieved_test_plans.append({
                        'epic_id': epic_id,
                        'test_plan_content': content
                    })

            assert len(retrieved_test_plans) == 3
            assert all(tp['test_plan_content'] for tp in retrieved_test_plans)

    def test_retrieval_with_partial_failures(self):
        """Test retrieval when some EPICs have test plans and others don't."""
        testable_epics = [
            {'id': 100, 'title': 'EPIC 1'},  # Has test plan
            {'id': 200, 'title': 'EPIC 2'},  # Missing test plan
            {'id': 300, 'title': 'EPIC 3'},  # Has test plan
        ]

        with tempfile.TemporaryDirectory() as tmpdir:
            test_plans_dir = Path(tmpdir) / 'acceptance-tests'
            test_plans_dir.mkdir(parents=True)

            # Create test plans for EPICs 100 and 300 only
            for epic_id in [100, 300]:
                test_plan_path = test_plans_dir / f'epic-{epic_id}-test-plan.md'
                test_plan_path.write_text(f"# Test Plan for EPIC {epic_id}", encoding='utf-8')

            # Retrieve test plans
            retrieved_test_plans = []
            retrieval_failures = []

            for epic in testable_epics:
                epic_id = epic['id']
                test_plan_path = test_plans_dir / f'epic-{epic_id}-test-plan.md'

                if test_plan_path.exists():
                    content = test_plan_path.read_text(encoding='utf-8')
                    retrieved_test_plans.append({
                        'epic_id': epic_id,
                        'test_plan_content': content
                    })
                else:
                    retrieval_failures.append({
                        'epic_id': epic_id,
                        'reason': 'Test plan file not found or could not be read'
                    })

            assert len(retrieved_test_plans) == 2
            assert len(retrieval_failures) == 1
            assert retrieval_failures[0]['epic_id'] == 200

    def test_unsupported_platform_fallback(self):
        """Test retrieval falls back to filesystem for unsupported platforms."""
        # Mock adapter with unsupported platform
        adapter = Mock()
        adapter.platform = 'unsupported-platform'

        # Mock test plan file
        with tempfile.TemporaryDirectory() as tmpdir:
            test_plans_dir = Path(tmpdir) / 'acceptance-tests'
            test_plans_dir.mkdir(parents=True)

            epic_id = 500
            test_plan_path = test_plans_dir / f'epic-{epic_id}-test-plan.md'
            test_plan_content = "# Test Plan for Unsupported Platform"
            test_plan_path.write_text(test_plan_content, encoding='utf-8')

            # Simulate unsupported platform retrieval (falls back to filesystem)
            retrieved_content = None
            if test_plan_path.exists():
                retrieved_content = test_plan_path.read_text(encoding='utf-8')

            assert retrieved_content is not None
            assert 'Unsupported Platform' in retrieved_content

    def test_workflow_state_storage(self):
        """Test test plan data is properly stored in workflow state."""
        from datetime import datetime

        retrieved_test_plans = [
            {
                'epic_id': 100,
                'epic_title': 'EPIC 1',
                'test_plan_path': '.claude/acceptance-tests/epic-100-test-plan.md',
                'test_plan_content': 'Content 1',
                'content_length': 9,
                'validation_warnings': []
            },
            {
                'epic_id': 200,
                'epic_title': 'EPIC 2',
                'test_plan_path': '.claude/acceptance-tests/epic-200-test-plan.md',
                'test_plan_content': 'Content 2',
                'content_length': 9,
                'validation_warnings': ['FEATURE']
            }
        ]

        retrieval_failures = [
            {
                'epic_id': 300,
                'epic_title': 'EPIC 3',
                'reason': 'File not found'
            }
        ]

        # Create workflow state
        retrieved_test_plans_state = {
            'retrieved_test_plans': retrieved_test_plans,
            'retrieval_failures': retrieval_failures,
            'successful_retrievals': len(retrieved_test_plans),
            'failed_retrievals': len(retrieval_failures),
            'retrieval_timestamp': datetime.now().isoformat()
        }

        # Verify state structure
        assert retrieved_test_plans_state['successful_retrievals'] == 2
        assert retrieved_test_plans_state['failed_retrievals'] == 1
        assert len(retrieved_test_plans_state['retrieved_test_plans']) == 2
        assert len(retrieved_test_plans_state['retrieval_failures']) == 1
        assert 'retrieval_timestamp' in retrieved_test_plans_state

    def test_testable_epics_filtering(self):
        """Test filtering testable EPICs to only include successfully retrieved test plans."""
        testable_epics = [
            {'id': 100, 'title': 'EPIC 1'},
            {'id': 200, 'title': 'EPIC 2'},
            {'id': 300, 'title': 'EPIC 3'},
        ]

        retrieved_test_plans = [
            {'epic_id': 100, 'test_plan_content': 'Content 1'},
            {'epic_id': 300, 'test_plan_content': 'Content 3'},
        ]

        # Filter EPICs to only those with successfully retrieved test plans
        filtered_epics = [
            epic for epic in testable_epics
            if any(tp['epic_id'] == epic['id'] for tp in retrieved_test_plans)
        ]

        assert len(filtered_epics) == 2
        assert filtered_epics[0]['id'] == 100
        assert filtered_epics[1]['id'] == 300
        assert not any(epic['id'] == 200 for epic in filtered_epics)
