"""Small offline protocol guards; does not start QEMU or create a disk image."""
import unittest
import pathlib
import tempfile
from TestUsbDesktopShortcuts import require_image_backing, require_usb_only_topology, snapshots


TREE = '''bus: main-system-bus
  dev: qemu-xhci, id "XfeUsbXhci"
    bus: XfeUsbXhci.0
      dev: usb-kbd, id "XfeUsbKeyboard"
'''
STATE = ('XFE_DESKTOP_FLAGS|0|1|Active|2\n'
         'XFE_DESKTOP_APPEARANCE|Dark|657DE2|Persistent|0\n'
         'XFE_WINDOW_STATE|1|Files|150|66|800|500|0|0|0\n'
         'XFE_WINDOW_STATE|2|Files|172|88|800|500|0|0|0\n')


class UsbShortcutGuards(unittest.TestCase):
    def test_complete_sealed_guest_report_is_parsed(self):
        rows = snapshots(STATE + 'XFE_DESKTOP_ACTIVE|Files\n')
        self.assertEqual(1, len(rows))
        self.assertEqual(2, rows[0]['ActiveId'])
        self.assertEqual(1, rows[0]['WindowSwitching'])
        self.assertEqual({1, 2}, set(rows[0]['Windows']))

    def test_serial_tail_cannot_claim_a_complete_state(self):
        self.assertEqual([], snapshots(STATE))
        self.assertEqual([], snapshots(STATE[:-1]))

    def test_next_header_seals_previous_atomic_report(self):
        self.assertEqual(1, len(snapshots(STATE + 'XFE_DESKTOP_FLAGS|0|0|Active|1\n')))

    def test_wrong_appearance_or_missing_active_window_is_rejected(self):
        self.assertEqual([], snapshots(STATE.replace('Dark|657DE2', 'Invalid') + 'Seal\n'))
        self.assertEqual([], snapshots(STATE.replace('Active|2', 'Active|3') + 'Seal\n'))

    def test_duplicate_ids_are_not_accepted_as_snapshot(self):
        self.assertEqual([], snapshots(STATE.replace('STATE|2|', 'STATE|1|') + 'Seal\n'))

    def test_owned_xhci_single_keyboard_is_accepted(self):
        require_usb_only_topology(TREE)

    def test_ps2_or_second_keyboard_is_rejected(self):
        for extra in ('dev: i8042, id ""\n', 'dev: ps2-kbd, id ""\n', 'dev: usb-kbd, id "other"\n'):
            with self.subTest(extra=extra), self.assertRaises(AssertionError):
                require_usb_only_topology(TREE + extra)

    def test_missing_owned_controller_bus_or_keyboard_is_rejected(self):
        for source in ('dev: qemu-xhci, id "XfeUsbXhci"', 'bus: XfeUsbXhci.0', 'dev: usb-kbd, id "XfeUsbKeyboard"'):
            with self.subTest(source=source), self.assertRaises(AssertionError):
                require_usb_only_topology(TREE.replace(source, 'unknown'))

    def test_qmp_snapshot_must_point_to_exact_source_raw_backing(self):
        with tempfile.TemporaryDirectory() as directory:
            image = pathlib.Path(directory) / 'TinyFixture.img'
            image.write_bytes(b'Guard fixture only')
            leaf = {'filename': str(image), 'format': 'raw', 'virtual-size': image.stat().st_size}
            block = {'inserted': {'image': {'filename': 'TemporaryOverlay', 'format': 'qcow2', 'backing-image': leaf}}}
            require_image_backing([block], image)
            with self.assertRaises(AssertionError):
                require_image_backing([block, block], image)
            with self.assertRaises(AssertionError):
                require_image_backing([], image)
            leaf['format'] = 'qcow2'
            with self.assertRaises(AssertionError):
                require_image_backing([block], image)

    def test_qmp_raw_source_length_must_match(self):
        with tempfile.TemporaryDirectory() as directory:
            image = pathlib.Path(directory) / 'TinyFixture.img'
            image.write_bytes(b'Guard fixture only')
            with self.assertRaises(AssertionError):
                require_image_backing([{'inserted': {'image': {'filename': str(image), 'format': 'raw', 'virtual-size': 1}}}], image)


if __name__ == '__main__':
    unittest.main()
