"""Integration tests for TmuxManager with a real tmux server."""

import asyncio
import shutil

import pytest

from ccgram.tmux_manager import TmuxManager

pytestmark = [
    pytest.mark.integration,
    pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux not installed"),
]

TEST_SESSION = "ccgram-test-integration"


@pytest.fixture()
async def tmux(tmp_path):
    mgr = TmuxManager(session_name=TEST_SESSION)
    mgr.get_or_create_session()
    yield mgr
    session = mgr.get_session()
    if session:
        session.kill()


async def test_create_and_list_windows(tmux, tmp_path) -> None:
    ok, _msg, name, window_id = await tmux.create_window(
        str(tmp_path), window_name="test-win", start_agent=False
    )
    assert ok
    assert name == "test-win"
    assert window_id.startswith("@")

    windows = await tmux.list_windows()
    ids = [w.window_id for w in windows]
    assert window_id in ids

    match = next(w for w in windows if w.window_id == window_id)
    assert match.window_name == "test-win"


async def test_find_window_by_id(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="find-me", start_agent=False
    )
    assert ok

    found = await tmux.find_window_by_id(window_id)
    assert found is not None
    assert found.window_name == "find-me"

    missing = await tmux.find_window_by_id("@99999")
    assert missing is None


async def test_send_keys_and_capture_pane(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="echo-win", start_agent=False
    )
    assert ok

    await tmux.send_keys(window_id, "echo hello-integration")

    await asyncio.sleep(0.5)

    output = await tmux.capture_pane(window_id)
    assert output is not None
    assert "hello-integration" in output


async def test_no_agent_disables_automatic_rename(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="shell-norename", start_agent=False
    )
    assert ok

    session = tmux.get_session()
    assert session
    window = session.windows.get(window_id=window_id)
    assert window.show_option("automatic-rename") is False

    await asyncio.sleep(0.5)
    await tmux.send_keys(window_id, "echo should-not-rename")
    await asyncio.sleep(1.0)
    found = await tmux.find_window_by_id(window_id)
    assert found is not None
    assert found.window_name == "shell-norename"


async def test_kill_window(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="kill-me", start_agent=False
    )
    assert ok

    killed = await tmux.kill_window(window_id)
    assert killed is True

    windows = await tmux.list_windows()
    ids = [w.window_id for w in windows]
    assert window_id not in ids


async def test_reset_server_reconnects(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="reset-test", start_agent=False
    )
    assert ok

    tmux._reset_server()

    windows = await tmux.list_windows()
    ids = [w.window_id for w in windows]
    assert window_id in ids


async def test_capture_pane_raw_returns_tuple(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="raw-test", start_agent=False
    )
    assert ok

    # Send something so pane has content (empty panes return None)
    await tmux.send_keys(window_id, "echo raw-test-output")

    await asyncio.sleep(0.5)

    result = await tmux.capture_pane_raw(window_id)
    assert result is not None
    content, cols, rows = result
    assert isinstance(content, str)
    assert "raw-test-output" in content
    assert cols > 0
    assert rows > 0


async def test_get_pane_title(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="title-test", start_agent=False
    )
    assert ok

    title = await tmux.get_pane_title(window_id)
    assert isinstance(title, str)


# ── Pane-level operations ────────────────────────────────────────────


async def test_list_panes_single(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="pane-list", start_agent=False
    )
    assert ok

    panes = await tmux.list_panes(window_id)
    assert len(panes) == 1
    assert panes[0].active is True
    assert panes[0].pane_id.startswith("%")
    assert panes[0].width > 0
    assert panes[0].height > 0


async def test_list_panes_multiple(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="multi-pane", start_agent=False
    )
    assert ok

    # Split the window to create a second pane
    session = tmux.get_session()
    assert session
    window = session.windows.get(window_id=window_id)
    window.split()

    panes = await tmux.list_panes(window_id)
    assert len(panes) == 2
    pane_ids = [p.pane_id for p in panes]
    assert len(set(pane_ids)) == 2  # IDs are unique
    active_count = sum(1 for p in panes if p.active)
    assert active_count == 1


async def test_list_panes_missing_window(tmux) -> None:
    panes = await tmux.list_panes("@99999")
    assert panes == []


async def test_capture_pane_by_id(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="cap-pane", start_agent=False
    )
    assert ok

    panes = await tmux.list_panes(window_id)
    pane_id = panes[0].pane_id

    await tmux.send_keys(window_id, "echo pane-capture-test")
    await asyncio.sleep(0.5)

    output = await tmux.capture_pane_by_id(pane_id)
    assert output is not None
    assert "pane-capture-test" in output


async def test_capture_pane_by_id_missing(tmux) -> None:
    output = await tmux.capture_pane_by_id("%99999")
    assert output is None


async def test_send_keys_to_pane(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="send-pane", start_agent=False
    )
    assert ok

    # Split to create two panes
    session = tmux.get_session()
    assert session
    window = session.windows.get(window_id=window_id)
    window.split()

    panes = await tmux.list_panes(window_id)
    assert len(panes) == 2

    # Send to the non-active pane
    inactive = next(p for p in panes if not p.active)
    sent = await tmux.send_keys_to_pane(inactive.pane_id, "echo pane-target-test")
    assert sent is True
    await asyncio.sleep(0.5)

    output = await tmux.capture_pane_by_id(inactive.pane_id)
    assert output is not None
    assert "pane-target-test" in output


async def test_send_keys_to_pane_missing(tmux) -> None:
    sent = await tmux.send_keys_to_pane("%99999", "hello")
    assert sent is False


# ── ANSI capture ───────────────────────────────────────────────────────


async def test_capture_pane_with_ansi(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="ansi-test", start_agent=False
    )
    assert ok

    await tmux.send_keys(window_id, r'printf "\033[31mred\033[0m normal"')
    await asyncio.sleep(0.5)

    plain = await tmux.capture_pane(window_id, with_ansi=False)
    ansi = await tmux.capture_pane(window_id, with_ansi=True)
    assert plain is not None
    assert ansi is not None
    assert "red" in plain
    assert "normal" in plain
    assert "\x1b[" in ansi
    assert "red" in ansi


async def test_ansi_capture_through_pyte(tmux, tmp_path) -> None:
    from ccgram.screen_buffer import ScreenBuffer

    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="pyte-e2e", start_agent=False
    )
    assert ok

    await tmux.send_keys(window_id, r'printf "\033[1mbold text\033[0m"')
    await asyncio.sleep(0.5)

    w = await tmux.find_window_by_id(window_id)
    assert w is not None
    ansi_text = await tmux.capture_pane(window_id, with_ansi=True)
    assert ansi_text is not None

    buf = ScreenBuffer(columns=w.pane_width, rows=w.pane_height)
    buf.feed(ansi_text)
    rendered = buf.rendered_text
    assert "bold text" in rendered
    assert "\x1b" not in rendered


async def test_create_window_sets_ccgram_window_id(tmux, tmp_path) -> None:
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="env-test", start_agent=False
    )
    assert ok

    await asyncio.sleep(0.5)
    await tmux.send_keys(window_id, "echo $CCGRAM_WINDOW_ID")
    await asyncio.sleep(0.5)

    output = await tmux.capture_pane(window_id)
    assert output is not None
    expected = f"{TEST_SESSION}:{window_id}"
    assert expected in output


# ── YOLO bypass prompt detection ────────────────────────────────────────


async def test_accept_yolo_confirmation_detects_prompt(tmux, tmp_path) -> None:
    from unittest.mock import patch

    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="yolo-test", start_agent=False
    )
    assert ok

    await tmux.send_keys(
        window_id,
        'echo "WARNING: Claude Code running in Bypass Permissions mode"',
    )
    await asyncio.sleep(0.5)

    with patch("ccgram.handlers.topics.directory_callbacks.tmux_manager", tmux):
        from ccgram.handlers.topics.directory_callbacks import _accept_yolo_confirmation

        result = await _accept_yolo_confirmation(window_id, timeout=3.0)

    assert result is True


async def test_create_window_with_special_char_launch_command(tmux, tmp_path) -> None:
    """Launch command containing = and / is sent literally (regression for Gemini bug).

    Gemini's hardened launch command is `env VAR=/path/to/file gemini`.
    Without literal=True in pane.send_keys, the = and / are misinterpreted
    as tmux key sequences and the command is corrupted.
    """
    marker = "CCGRAM_LAUNCH_TEST_MARKER"
    launch_cmd = (
        f"env {marker}=special/path/value sh -c 'echo ${{CCGRAM_LAUNCH_TEST_MARKER}}'"
    )
    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path),
        window_name="literal-launch",
        start_agent=True,
        launch_command=launch_cmd,
    )
    assert ok

    await asyncio.sleep(1.0)
    output = await tmux.capture_pane(window_id)
    assert output is not None
    assert "special/path/value" in output, (
        f"Launch command with = and / was not sent literally. Pane output: {output!r}"
    )


async def test_accept_yolo_confirmation_timeout_on_no_prompt(tmux, tmp_path) -> None:
    from unittest.mock import patch

    ok, _msg, _name, window_id = await tmux.create_window(
        str(tmp_path), window_name="yolo-nope", start_agent=False
    )
    assert ok

    await tmux.send_keys(window_id, "echo hello world")
    await asyncio.sleep(0.3)

    with patch("ccgram.handlers.topics.directory_callbacks.tmux_manager", tmux):
        from ccgram.handlers.topics.directory_callbacks import _accept_yolo_confirmation

        result = await _accept_yolo_confirmation(window_id, timeout=1.0)

    assert result is False
