"""System tests: synthetic KeyRelease via blessed idle timeout.

Verifies that pressing a key and going idle produces a KeyRelease event,
which is the mechanism BuzzerBoard uses for tone_start / stop_playback.
"""

import time
from functools import partial

import pytest

from pyos.Activity import Activity
from pyos import Keys
from pyos.EventTypes import KeyStroke, KeyRelease
from pyos.printers.printers import print_line, print_empty_line


class ToneTestActivity(Activity):
    """Minimal activity that tracks press/release like BuzzerBoard."""

    def on_start(self):
        self.application.subscribe(KeyStroke, self, self.on_key_stroke)
        self.application.subscribe(KeyRelease, self, self.on_key_release)
        self._held_key = None
        self._last_event = "READY"

        self.display_state = {
            "body": {
                "layout": {"flex": 1},
                "line_generator": self._render,
            },
        }

    def _render(self, context, remaining_height):
        lines = [
            partial(print_line, 2, f"  {self._last_event}"),
            partial(print_line, 2, f"  held={self._held_key}"),
        ]
        while len(lines) < remaining_height:
            lines.append(print_empty_line)
        return lines[:remaining_height]

    def on_key_stroke(self, event: KeyStroke):
        if event.key == Keys.ESC:
            self.application.pop_activity()
            return
        if self._held_key == event.key:
            return  # repeat, ignore
        self._held_key = event.key
        self._last_event = f"PRESSED:{event.key}"
        self.refresh_screen()

    def on_key_release(self, event: KeyRelease):
        if self._held_key == event.key:
            self._held_key = None
            self._last_event = f"RELEASED:{event.key}"
            self.refresh_screen()


# ---------------------------------------------------------------------------
# Harness unit tests (fast, deterministic)
# ---------------------------------------------------------------------------

class TestSyntheticReleaseHarness:
    """Unit tests using HarnessApplication + MockScreen."""

    @pytest.fixture
    def app(self):
        from pyos.testing.harness import HarnessApplication, MockScreen
        screen = MockScreen(24, 80)
        application = HarnessApplication(screen)
        application._enable_kitty = True  # enable synthetic releases
        application.setup()
        yield application
        application.teardown()

    @pytest.fixture
    def mock_screen(self, app):
        return app.curses_screen

    def test_press_dispatches_keystroke(self, app, mock_screen):
        act = ToneTestActivity()
        app.start_activity(act)
        mock_screen.assert_text_on_screen("READY")

        app.send_key(ord("a"))
        mock_screen.assert_text_on_screen("PRESSED:97")
        assert act._held_key == 97

    def test_release_dispatches_keyrelease(self, app, mock_screen):
        act = ToneTestActivity()
        app.start_activity(act)

        app.send_key(ord("a"))
        assert act._held_key == 97

        app.send_key_release(ord("a"))
        mock_screen.assert_text_on_screen("RELEASED:97")
        assert act._held_key is None

    def test_different_key_releases_previous(self, app, mock_screen):
        act = ToneTestActivity()
        app.start_activity(act)

        app.send_key(ord("a"))
        assert act._held_key == 97

        # Pressing a different key should release the first
        app.send_key(ord("s"))
        app.send_key_release(ord("a"))  # explicit release of old key
        assert act._held_key == ord("s")

    def test_repeat_ignored(self, app, mock_screen):
        act = ToneTestActivity()
        app.start_activity(act)

        app.send_key(ord("a"))
        assert act._held_key == 97

        # Same key again (repeat) — should NOT change state
        app.send_key(ord("a"))
        assert act._held_key == 97
        mock_screen.assert_text_on_screen("PRESSED:97")


# ---------------------------------------------------------------------------
# PTY integration tests (real terminal, real threading)
# ---------------------------------------------------------------------------

class TestSyntheticReleasePty:
    """End-to-end tests with real PTY + blessed + synthetic releases."""

    @pytest.fixture(autouse=True)
    def _skip_if_no_pyte(self):
        pytest.importorskip("pyte")

    def test_tap_produces_press_then_release(self):
        """Single tap: PRESSED appears, then after idle RELEASED appears."""
        from pyos.testing.pty_harness import PtyHarness

        with PtyHarness(ToneTestActivity, enable_kitty=True) as h:
            h.wait_for_text("READY", timeout=5)

            h.send_key(ord("a"))
            h.wait_for_text("PRESSED:97", timeout=3)

            # Synthetic release after ~150ms idle
            h.wait_for_text("RELEASED:97", timeout=3)

    def test_hold_with_repeat_delays_release(self):
        """Holding a key (simulated repeats) should keep it pressed."""
        from pyos.testing.pty_harness import PtyHarness

        with PtyHarness(ToneTestActivity, enable_kitty=True) as h:
            h.wait_for_text("READY", timeout=5)

            # Simulate holding by sending repeats
            h.send_key(ord("a"))
            h.wait_for_text("PRESSED:97", timeout=3)

            for _ in range(5):
                time.sleep(0.05)
                h.send_key(ord("a"))  # repeat

            # Should still be pressed (repeats reset idle counter)
            assert h.find_text("PRESSED:97")
            assert not h.find_text("RELEASED")

            # Now stop sending → synthetic release after ~150ms
            h.wait_for_text("RELEASED:97", timeout=3)

    def test_switching_keys_releases_previous(self):
        """Pressing 'a' then 's' should release 'a' and press 's'."""
        from pyos.testing.pty_harness import PtyHarness

        with PtyHarness(ToneTestActivity, enable_kitty=True) as h:
            h.wait_for_text("READY", timeout=5)

            h.send_key(ord("a"))
            h.wait_for_text("PRESSED:97", timeout=3)

            h.send_key(ord("s"))
            h.wait_for_text("PRESSED:115", timeout=3)

            # After idle, 's' gets released
            h.wait_for_text("RELEASED:115", timeout=3)

    def test_rapid_taps(self):
        """Rapid taps on different keys should produce press/release for each."""
        from pyos.testing.pty_harness import PtyHarness

        with PtyHarness(ToneTestActivity, enable_kitty=True) as h:
            h.wait_for_text("READY", timeout=5)

            # Tap 'a', wait for release
            h.send_key(ord("a"))
            h.wait_for_text("RELEASED:97", timeout=3)

            # Tap 's', wait for release
            h.send_key(ord("s"))
            h.wait_for_text("RELEASED:115", timeout=3)
