# tests/test_bridge_respond.py
import pytest
import sys
import os
from unittest.mock import MagicMock, patch, call
from datetime import datetime
import json

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

try:
    import bridge_respond
except ImportError:
    from conftest import bridge_respond


class TestBridgeRespondCLI:
    """Tests for bridge_respond.py command line interface."""
    
    def test_cli_directive_completion_with_message(self, mock_db_connection):
        """Test completing a directive with status message."""
        mock_conn, mock_cursor = mock_db_connection
        
        test_args = [
            'bridge_respond.py',
            '--directive-id', '123',
            '--status', 'completed',
            '--message', 'Implementation finished and tested'
        ]
        
        with patch.object(sys, 'argv', test_args), \
             patch('bridge_respond.get_db_connection', return_value=mock_conn), \
             patch('bridge_respond.update_directive_status') as mock_update:
            
            bridge_respond.main()
            
            mock_update.assert_called_once_with(123, 'completed', 'Implementation finished and tested')
    
    def test_cli_directive_completion_without_message(self, mock_db_connection):
        """Test completing a directive without optional message."""
        mock_conn, mock_cursor = mock_db_connection
        
        test_args = [
            'bridge_respond.py',
            '--directive-id', '456',
            '--status', 'failed'
        ]
        
        with patch.object(sys, 'argv', test_args), \
             patch('bridge_respond.get_db_connection', return_value=mock_conn), \
             patch('bridge_respond.update_directive_status') as mock_update:
            
            bridge_respond.main()
            
            mock_update.assert_called_once_with(456, 'failed', None)
    
    def test_cli_status_update_only(self, mock_db_connection):
        """Test posting status update without directive ID."""
        mock_conn, mock_cursor = mock_db_connection
        
        test_args = [
            'bridge_respond.py',
            '--status-update', 'Currently refactoring module X, 45% complete'
        ]
        
        with patch.object(sys, 'argv', test_args), \
             patch('bridge_respond.get_db_connection', return_value=mock_conn), \
             patch('bridge_respond.post_status_update') as mock_post:
            
            bridge_respond.main()
            
            mock_post.assert_called_once_with('Currently refactoring module X, 45% complete')
    
    def test_cli_missing_all_arguments(self):
        """Test error when no arguments provided."""
        test_args = ['bridge_respond.py']
        
        with patch.object(sys, 'argv', test_args), \
             patch('bridge_respond.get_db_connection'):
            
            with pytest.raises(SystemExit) as exc_info:
                bridge_respond.main()
            
            assert exc_info.value.code != 0
    
    def test_cli_missing_status_with_directive_id(self):
        """Test error when directive-id provided without status."""
        test_args = [
            'bridge_respond.py',
            '--directive-id', '123'
        ]
        
        with patch.object(sys, 'argv', test_args), \
             patch('bridge_respond.get_db_connection'):
            
            with pytest.raises(SystemExit) as exc_info:
                bridge_respond.main()
            
            assert exc_info.value.code != 0


class TestDatabaseOperations:
    """Tests for database operations in bridge_respond."""
    
    def test_update_directive_status_with_message(self, mock_db_connection):
        """Test SQL generation for status update with message."""
        mock_conn, mock_cursor = mock_db_connection
        
        bridge_respond.update_directive_status(
            mock_conn, 123, 'completed', 'All tests passing'
        )
        
        mock_cursor.execute.assert_called_once()
        sql = mock_cursor.execute.call_args[0][0]
        params = mock_cursor.execute.call_args[0][1]
        
        assert 'UPDATE' in sql.upper()
        assert 'genesis_bridge.command_queue' in sql or 'command_queue' in sql
        assert 'completed' in params or 'completed' in sql.lower()
        assert 123 in params or '123' in str(params)
        assert 'All tests passing' in str(params)
        mock_conn.commit.assert_called_once()
    
    def test_update_directive_status_without_message(self, mock_db_connection):
        """Test SQL generation for status update without message."""
        mock_conn, mock_cursor = mock_db_connection
        
        bridge_respond.update_directive_status(mock_conn, 456, 'failed')
        
        mock_cursor.execute.assert_called_once()
        params = mock_cursor.execute.call_args[0][1]
        
        # Should have None or empty string for message
        param_list = list(params) if isinstance(params, (list, tuple)) else [params]
        assert any(p is None or p == '' for p in param_list) or 'failed' in str(params)
    
    def test_post_status_update(self, mock_db_connection):
        """Test inserting status update record."""
        mock_conn, mock_cursor = mock_db_connection
        
        bridge_respond.post_status_update(mock_conn, 'Progress: 50% complete')
        
        mock_cursor.execute.assert_called_once()
        sql = mock_cursor.execute.call_args[0][0]
        params = mock_cursor.execute.call_args[0][1]
        
        assert 'INSERT' in sql.upper()
        assert 'outbound' in sql.lower()
        assert 'claude' in str(params).lower() or 'source' in sql.lower()
        assert 'Progress: 50% complete' in str(params)
        mock_conn.commit.assert_called_once()
    
    def test_database_error_handling(self, mock_db_connection):
        """Test handling of database errors during update."""
        mock_conn, mock_cursor = mock_db_connection
        import psycopg2
        
        mock_cursor.execute.side_effect = psycopg2.Error("Update failed")
        
        with pytest.raises(psycopg2.Error):
            bridge_respond.update_directive_status(mock_conn, 123, 'completed')


class TestTransactionSafety:
    """Tests for transaction safety and error recovery."""
    
    def test_rollback_on_error(self, mock_db_connection):
        """Test that transactions are rolled back on error."""
        mock_conn, mock_cursor = mock_db_connection
        import psycopg2
        
        mock_cursor.execute.side_effect = psycopg2.Error("Query failed")
        
        try:
            bridge_poller.mark_processing(mock_conn, 123)
        except psycopg2.Error:
            pass
        
        mock_conn.rollback.assert_called_once()
    
    def test_commit_on_success(self, mock_db_connection):
        """Test that transactions are committed on success."""
        mock_conn, mock_cursor = mock_db_connection
        
        bridge_poller.mark_processing(mock_conn, 123)
        
        mock_conn.commit.assert_called_once()
        mock_conn.rollback.assert_not_called()


class TestConcurrency:
    """Tests for concurrent access patterns."""
    
    def test_row_level_locking(self, mock_db_connection):
        """Test that SELECT uses FOR UPDATE for row locking."""
        mock_conn, mock_cursor = mock_db_connection
        
        bridge_poller.fetch_pending_directives(mock_conn)
        
        sql = mock_cursor.execute.call_args[0][0]
        assert 'FOR UPDATE' in sql.upper() or 'SKIP LOCKED' in sql.upper()
    
    def test_isolation_between_polls(self, mock_db_connection):
        """Test that multiple polls don't interfere with each other."""
        mock_conn, mock_cursor = mock_db_connection
        
        # First poll gets directive 1
        # Second poll should not see directive 1 again (it's processing)
        mock_cursor.fetchall.side_effect = [
            [{'id': 1, 'priority': 5}],
            [],  # Second poll empty
            [{'id': 2, 'priority': 5}]  # Third poll gets new directive
        ]
        
        with patch.object(bridge_poller, 'mark_processing'), \
             patch.object(bridge_poller, 'create_acknowledgment'), \
             patch.object(bridge_poller, 'process_directive'), \
             patch.dict(os.environ, {'LOG_FILE': '/dev/null'}):
            
            bridge_poller.poll_once(mock_conn)
            bridge_poller.poll_once(mock_conn)
            bridge_poller.poll_once(mock_conn)
            
            # All polls should succeed without interference
            assert mock_cursor.execute.call_count > 0


# Ensure coverage of main execution paths
def test_main_once_execution(mock_env_vars):
    """Test main function with --once flag."""
    with patch.object(sys, 'argv', ['bridge_poller.py', '--once']), \
         patch('psycopg2.connect') as mock_connect, \
         patch.object(bridge_poller, 'poll_once') as mock_poll, \
         patch.object(bridge_poller, 'get_db_connection') as mock_get_db:
        
        mock_conn = MagicMock()
        mock_get_db.return_value = mock_conn
        
        try:
            bridge_poller.main()
        except SystemExit:
            pass
        
        mock_poll.assert_called_once()


def test_main_daemon_execution(mock_env_vars):
    """Test main function with --daemon flag."""
    with patch.object(sys, 'argv', ['bridge_poller.py', '--daemon']), \
         patch.object(bridge_poller, 'get_db_connection') as mock_get_db, \
         patch.object(bridge_poller, 'run_daemon') as mock_run, \
         patch('signal.signal'):
        
        mock_conn = MagicMock()
        mock_get_db.return_value = mock_conn
        
        bridge_poller.main()
        
        mock_run.assert_called_once()