"""
Unit tests for terminal encoding functionality.

Tests verify that:
1. Encoding settings are properly applied to terminal backends
2. Encoding affects text processing (VTE backend)
3. Legacy encodings are wrapped with luit for PyXterm.js backend
4. UTF-8/UTF-16 are not wrapped (native xterm.js support)
"""

import sys
import types
from pathlib import Path
from unittest.mock import Mock

import pytest

# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent))

# Mock GTK/GObject before importing terminal modules
class DummyGObject:
    class SignalFlags:
        RUN_FIRST = 0
    
    @staticmethod
    def connect(*args, **kwargs):
        return 12345
    
    @staticmethod
    def disconnect(*args, **kwargs):
        pass

class DummyGLib:
    @staticmethod
    def timeout_add_seconds(*args, **kwargs):
        return 0
    
    @staticmethod
    def idle_add(func, *args):
        func(*args)
        return 0

class DummyGtk:
    class Box:
        def __init__(self, *args, **kwargs):
            pass
        
        def connect(self, *args, **kwargs):
            return 12345
    
    class Orientation:
        VERTICAL = 0
    
    class EventSequenceState:
        CLAIMED = 1
    
    class Widget:
        pass
    
    class GestureClick:
        def __init__(self, *args, **kwargs):
            pass
        
        def set_button(self, *args, **kwargs):
            pass
        
        def connect(self, *args, **kwargs):
            return 12345
    
    class ScrolledWindow:
        def __init__(self, *args, **kwargs):
            pass
        
        def set_child(self, *args, **kwargs):
            pass

class DummyVte:
    def __init__(self):
        self.encoding = 'UTF-8'
        self._encodings = ['UTF-8', 'ISO-8859-1', 'Windows-1252', 'GB2312']
    
    def set_encoding(self, encoding):
        self.encoding = encoding
    
    def get_encodings(self):
        return [
            ('UTF-8', 'Unicode (UTF-8)'),
            ('ISO-8859-1', 'Latin-1 (ISO-8859-1)'),
            ('Windows-1252', 'Western European (Windows-1252)'),
            ('GB2312', 'Simplified Chinese (GB2312)'),
        ]
    
    def spawn_async(self, *args, **kwargs):
        pass
    
    def grab_focus(self):
        pass
    
    def connect(self, *args, **kwargs):
        return 12345
    
    def set_hexpand(self, *args, **kwargs):
        pass
    
    def set_vexpand(self, *args, **kwargs):
        pass
    
    def set_font(self, *args, **kwargs):
        pass
    
    def set_allow_bold(self, *args, **kwargs):
        pass
    
    def reset(self, *args, **kwargs):
        pass

class DummyPango:
    SCALE = 1024
    
    class FontDescription:
        def __init__(self):
            self._family = "Monospace"
            self._size = 12 * 1024
        
        @staticmethod
        def from_string(font_string):
            desc = DummyPango.FontDescription()
            return desc
        
        def set_family(self, family):
            self._family = family
        
        def get_family(self):
            return self._family
        
        def set_size(self, size):
            self._size = size
        
        def get_size(self):
            return self._size

# Copy concrete dummies as attributes onto the conftest-provided gi.repository
# submodules rather than replacing them — replacing the submodule slots would
# clobber the auto-creating _DummyGIModule that later-collected tests rely on
# (see #985).
from gi.repository import GObject as _GObject
from gi.repository import GLib as _GLib
from gi.repository import Gtk as _Gtk
from gi.repository import Pango as _Pango
from gi.repository import Vte as _Vte
from gi.repository import Gdk as _Gdk
from gi.repository import Adw as _Adw


def _attach(target, source):
    """Set every non-dunder attribute of *source* onto *target*."""
    for name in dir(source):
        if name.startswith("__"):
            continue
        setattr(target, name, getattr(source, name))


_attach(_GObject, DummyGObject)
_attach(_GLib, DummyGLib)
_attach(_Gtk, DummyGtk)
_attach(_Pango, DummyPango)
_Vte.Terminal = DummyVte
_Vte.PtyFlags = types.SimpleNamespace(DEFAULT=0)
_Gdk.BUTTON_SECONDARY = 3
_Gdk.Rectangle = Mock
_Adw.Application = types.SimpleNamespace(get_default=lambda: None)

# Now import the modules we want to test
from sshpilot.terminal_backends import VTETerminalBackend, PyXtermTerminalBackend, PyXtermBridgeBackend


class TestVTETerminalEncoding:
    """Test encoding functionality for VTE backend"""
    
    def test_vte_encoding_is_applied(self):
        """Test that encoding is set on VTE terminal"""
        backend = VTETerminalBackend(owner=None)
        backend.initialize()
        
        # Test UTF-8 encoding
        backend.vte.set_encoding('UTF-8')
        assert backend.vte.encoding == 'UTF-8'
        
        # Test ISO-8859-1 encoding
        backend.vte.set_encoding('ISO-8859-1')
        assert backend.vte.encoding == 'ISO-8859-1'
        
        # Test Windows-1252 encoding
        backend.vte.set_encoding('Windows-1252')
        assert backend.vte.encoding == 'Windows-1252'
    
    def test_vte_encoding_affects_text_processing(self):
        """Test that encoding setting affects how VTE processes text"""
        backend = VTETerminalBackend(owner=None)
        backend.initialize()
        
        # Set encoding to ISO-8859-1
        backend.vte.set_encoding('ISO-8859-1')
        assert backend.vte.encoding == 'ISO-8859-1'
        
        # Verify encoding is actually set (affects text processing)
        # In real VTE, this would affect how bytes are interpreted
        assert backend.vte.encoding != 'UTF-8'
    
    def test_vte_get_encodings_returns_list(self):
        """Test that VTE returns list of supported encodings"""
        backend = VTETerminalBackend(owner=None)
        backend.initialize()
        
        encodings = backend.vte.get_encodings()
        assert isinstance(encodings, list)
        assert len(encodings) > 0
        assert any('UTF-8' in str(enc) for enc in encodings)


class TestPyXtermEncoding:
    """Test encoding functionality for PyXterm.js backend"""
    
    def test_utf8_encoding_no_luit_wrapper(self, monkeypatch):
        """Test that UTF-8 encoding doesn't wrap command with luit"""
        # Test the encoding wrapping logic directly
        
        # UTF-8 should not be wrapped (native xterm.js support)
        encoding = 'UTF-8'
        command = ['bash', '-c', 'echo "test"']
        
        # According to xterm.js docs, UTF-8 is natively supported
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            # This branch shouldn't execute for UTF-8
            wrapped = ['luit', '-encoding', encoding, '--'] + command
        
        assert wrapped == command
        assert wrapped[0] != 'luit'
    
    def test_utf16_encoding_no_luit_wrapper(self):
        """Test that UTF-16 encoding doesn't wrap command with luit"""
        # UTF-16 should not be wrapped (native xterm.js support)
        encoding = 'UTF-16'
        command = ['bash', '-c', 'echo "test"']
        
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            wrapped = ['luit', '-encoding', encoding, '--'] + command
        
        assert wrapped == command
        assert wrapped[0] != 'luit'
    
    def test_legacy_encoding_wraps_with_luit(self, monkeypatch):
        """Test that legacy encodings wrap command with luit"""
        import shutil
        
        # Mock luit availability
        monkeypatch.setattr(shutil, 'which', lambda cmd: '/usr/bin/luit' if cmd == 'luit' else None)
        
        encoding = 'ISO-8859-1'
        command = ['bash', '-c', 'echo "test"']
        
        # Legacy encoding should be wrapped
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            luit_path = shutil.which('luit')
            if luit_path:
                wrapped = [luit_path, '-encoding', encoding, '--'] + list(command)
            else:
                wrapped = command
        
        # Should be wrapped with luit
        assert wrapped[0] == '/usr/bin/luit'
        assert wrapped[1] == '-encoding'
        assert wrapped[2] == 'ISO-8859-1'
        assert wrapped[3] == '--'
        assert wrapped[4:] == command
    
    def test_legacy_encoding_no_luit_returns_original(self, monkeypatch):
        """Test that missing luit for legacy encoding returns original command"""
        import shutil
        
        # Mock luit not available
        monkeypatch.setattr(shutil, 'which', lambda cmd: None)
        
        encoding = 'ISO-8859-1'
        command = ['bash', '-c', 'echo "test"']
        
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            luit_path = shutil.which('luit')
            if luit_path:
                wrapped = [luit_path, '-encoding', encoding, '--'] + list(command)
            else:
                wrapped = command
        
        # Should return original command (no wrapping)
        assert wrapped == command
    
    def test_chinese_encoding_wraps_with_luit(self, monkeypatch):
        """Test that Chinese encodings wrap command with luit"""
        import shutil
        
        monkeypatch.setattr(shutil, 'which', lambda cmd: '/usr/bin/luit' if cmd == 'luit' else None)
        
        encoding = 'GB18030'
        command = ['bash', '-c', 'echo "测试"']
        
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            luit_path = shutil.which('luit')
            if luit_path:
                wrapped = [luit_path, '-encoding', encoding, '--'] + list(command)
            else:
                wrapped = command
        
        assert wrapped[0] == '/usr/bin/luit'
        assert wrapped[1] == '-encoding'
        assert wrapped[2] == 'GB18030'
        assert wrapped[3] == '--'
        assert wrapped[4:] == command
    
    def test_korean_encoding_wraps_with_luit(self, monkeypatch):
        """Test that Korean encodings wrap command with luit"""
        import shutil
        
        monkeypatch.setattr(shutil, 'which', lambda cmd: '/usr/bin/luit' if cmd == 'luit' else None)
        
        encoding = 'EUC-KR'
        command = ['bash', '-c', 'echo "테스트"']
        
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            luit_path = shutil.which('luit')
            if luit_path:
                wrapped = [luit_path, '-encoding', encoding, '--'] + list(command)
            else:
                wrapped = command
        
        assert wrapped[0] == '/usr/bin/luit'
        assert wrapped[1] == '-encoding'
        assert wrapped[2] == 'EUC-KR'
        assert wrapped[3] == '--'
        assert wrapped[4:] == command
    
    def test_cyrillic_encoding_wraps_with_luit(self, monkeypatch):
        """Test that Cyrillic encodings wrap command with luit"""
        import shutil
        
        monkeypatch.setattr(shutil, 'which', lambda cmd: '/usr/bin/luit' if cmd == 'luit' else None)
        
        encoding = 'KOI8-R'
        command = ['bash', '-c', 'echo "тест"']
        
        if encoding.upper() in ('UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE'):
            wrapped = command
        else:
            luit_path = shutil.which('luit')
            if luit_path:
                wrapped = [luit_path, '-encoding', encoding, '--'] + list(command)
            else:
                wrapped = command
        
        assert wrapped[0] == '/usr/bin/luit'
        assert wrapped[1] == '-encoding'
        assert wrapped[2] == 'KOI8-R'
        assert wrapped[3] == '--'
        assert wrapped[4:] == command


class TestEncodingAffectsText:
    """Test that encoding actually affects text processing"""
    
    def test_iso8859_vs_utf8_byte_difference(self):
        """Test that ISO-8859-1 and UTF-8 handle bytes differently"""
        # Character that differs between ISO-8859-1 and UTF-8
        # 'é' in ISO-8859-1 is 0xE9 (single byte)
        # 'é' in UTF-8 is 0xC3 0xA9 (two bytes)
        
        text_iso8859 = b'\xE9'  # 'é' in ISO-8859-1
        text_utf8 = b'\xC3\xA9'  # 'é' in UTF-8
        
        # They are different byte sequences
        assert text_iso8859 != text_utf8
        assert len(text_iso8859) == 1
        assert len(text_utf8) == 2
        
        # Decode to verify they represent the same character
        # This demonstrates that encoding affects byte representation
        char_iso8859 = text_iso8859.decode('ISO-8859-1')
        char_utf8 = text_utf8.decode('UTF-8')
        assert char_iso8859 == char_utf8 == 'é'
        
        # This proves encoding affects how bytes are interpreted
        # In a real terminal, wrong encoding = wrong character display
    
    def test_chinese_encoding_requires_multibyte(self):
        """Test that Chinese characters require proper encoding"""
        # Chinese character '中' in different encodings
        # GB2312: 0xD6 0xD0 (2 bytes)
        # UTF-8: 0xE4 0xB8 0xAD (3 bytes)
        
        text_gb2312 = b'\xD6\xD0'
        text_utf8 = b'\xE4\xB8\xAD'
        
        assert text_gb2312 != text_utf8
        assert len(text_gb2312) == 2
        assert len(text_utf8) == 3
        
        # Decode to verify they represent the same character
        char_gb2312 = text_gb2312.decode('GB2312')
        char_utf8 = text_utf8.decode('UTF-8')
        assert char_gb2312 == char_utf8 == '中'
        
        # This demonstrates why luit transcoding is needed
        # Wrong encoding = garbled text (different bytes = different characters)
    
    def test_encoding_mismatch_causes_garbled_text(self):
        """Test that wrong encoding causes text corruption"""
        # Text encoded in ISO-8859-1
        text_bytes = 'é'.encode('ISO-8859-1')  # b'\xE9'
        
        # If interpreted as UTF-8, it's invalid
        try:
            wrong_decode = text_bytes.decode('UTF-8')
            # Should raise UnicodeDecodeError or produce replacement character
            assert '\ufffd' in wrong_decode or len(wrong_decode) == 0
        except UnicodeDecodeError:
            # Expected - wrong encoding causes decode error
            pass
        
        # Correct decoding works
        correct_decode = text_bytes.decode('ISO-8859-1')
        assert correct_decode == 'é'
        
        # This proves encoding setting affects text display
        # Terminal must use correct encoding to display text properly
    
    def test_cyrillic_encoding_difference(self):
        """Test that Cyrillic encodings differ from UTF-8"""
        # Cyrillic 'я' in different encodings
        # KOI8-R: 0xD1 (1 byte)
        # UTF-8: 0xD1 0x8F (2 bytes)
        
        text_koi8r = 'я'.encode('KOI8-R')
        text_utf8 = 'я'.encode()
        
        assert text_koi8r != text_utf8
        assert len(text_koi8r) == 1
        assert len(text_utf8) == 2
        
        # Decode to verify same character
        char_koi8r = text_koi8r.decode('KOI8-R')
        char_utf8 = text_utf8.decode('UTF-8')
        assert char_koi8r == char_utf8 == 'я'
        
        # Wrong encoding interpretation produces wrong character
        wrong_char = text_koi8r.decode('UTF-8', errors='replace')
        assert wrong_char != 'я'  # Different character or replacement


class TestEncodingIntegration:
    """Integration tests for encoding with terminal backends"""
    
    def test_pyxterm_wrap_command_with_encoding_method(self, monkeypatch):
        """Test the actual _wrap_command_with_encoding method from backend"""
        import shutil

        backend = PyXtermTerminalBackend(owner=Mock())
        command = ['bash', '-c', 'echo "test"']

        monkeypatch.setattr(shutil, 'which', lambda cmd: None)
        wrapped = backend._wrap_command_with_encoding(command, 'UTF-8')
        assert wrapped == command

        monkeypatch.setattr(shutil, 'which', lambda cmd: '/usr/bin/luit' if cmd == 'luit' else None)
        wrapped = backend._wrap_command_with_encoding(command, 'ISO-8859-1')
        assert wrapped[0] == '/usr/bin/luit'
        assert wrapped[2] == 'ISO-8859-1'

    def test_pyxterm_bridge_spawn_reads_encoding_config(self, monkeypatch):
        """PyXtermBridgeBackend.spawn_async wraps argv using terminal.encoding."""
        import shutil

        owner = Mock()
        owner.config = Mock()
        owner.config.get_setting = Mock(return_value='KOI8-R')

        backend = PyXtermBridgeBackend.__new__(PyXtermBridgeBackend)
        backend.owner = owner
        backend.available = True
        backend._js_ready = False
        backend._pending_spawn = None
        backend._last_size = (24, 80)
        backend._preready_output = []
        backend.ensure_shell_loaded = lambda: None

        captured = {}

        def fake_do_spawn():
            captured['argv'] = backend._pending_spawn['argv']

        backend._do_spawn = fake_do_spawn
        monkeypatch.setattr(shutil, 'which', lambda cmd: '/usr/bin/luit' if cmd == 'luit' else None)

        backend.spawn_async(['ssh', 'host'], env=None, cwd='/tmp')

        argv = captured['argv']
        assert argv[:4] == ['/usr/bin/luit', '-encoding', 'KOI8-R', '--']
        assert argv[4:] == ['ssh', 'host']
        # any_call: spawn_async also queries terminal.autocomplete afterwards
        owner.config.get_setting.assert_any_call('terminal.encoding', 'UTF-8')
    
    def test_vte_encoding_validation(self):
        """Test that VTE validates encoding against supported list"""
        backend = VTETerminalBackend(owner=None)
        backend.initialize()
        
        # Get supported encodings
        encodings = backend.vte.get_encodings()
        encoding_codes = [enc[0] if isinstance(enc, (list, tuple)) else enc for enc in encodings]
        
        # UTF-8 should be supported
        assert 'UTF-8' in encoding_codes
        
        # Set UTF-8 encoding
        backend.vte.set_encoding('UTF-8')
        assert backend.vte.encoding == 'UTF-8'
        
        # This demonstrates that encoding validation works
        # Only supported encodings should be set


if __name__ == '__main__':
    pytest.main([__file__, '-v'])

