"""Prototype: Rich Console + prompt_toolkit patch_stdout compatibility.

This script is a standalone prototype for step:
`talk-prompt-toolkit.rich-prompt-toolkit-compatibility-prototype`.

It verifies:
1) ``console.print()`` appears above the active prompt.
2) ``sys.stdout.write()`` chunked output renders during an active prompt.
3) ``FileHistory`` persists across sessions.
"""

from __future__ import annotations

import contextlib
import io
import errno
import os
import re
import sys
import tempfile
import threading
import time
from collections.abc import Callable, Iterator
from dataclasses import dataclass
from pathlib import Path

from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from prompt_toolkit.input.defaults import create_pipe_input
from prompt_toolkit.output.defaults import create_output
from prompt_toolkit.patch_stdout import patch_stdout
from rich.console import Console


@contextlib.contextmanager
def _capture_fd_stdout() -> Iterator[io.StringIO]:
    """Capture low-level fd 1 writes during patch_stdout window."""
    stream = io.StringIO()
    saved_fd = os.dup(1)
    read_fd, write_fd = os.pipe()
    reader_ready = threading.Event()

    def _reader() -> None:
        reader_ready.set()
        try:
            while True:
                try:
                    chunk = os.read(read_fd, 4096)
                except OSError as exc:
                    if exc.errno == errno.EBADF:
                        break
                    raise
                if not chunk:
                    break
                stream.write(chunk.decode("utf-8", errors="replace"))
        finally:
            try:
                os.close(read_fd)
            except OSError as exc:
                if exc.errno != errno.EBADF:
                    raise

    reader = threading.Thread(target=_reader, daemon=True)
    reader.start()
    reader_ready.wait(timeout=1)

    try:
        os.dup2(write_fd, 1)
        yield stream
    finally:
        os.dup2(saved_fd, 1)
        os.close(saved_fd)
        os.close(write_fd)
        reader.join(timeout=1)


@dataclass(frozen=True)
class PrototypeResult:
    requirement_a: bool
    requirement_b: bool
    requirement_c: bool
    unified_terminal_transcript: str
    first_response: str
    second_response: str

    @property
    def ok(self) -> bool:
        return self.requirement_a and self.requirement_b and self.requirement_c


def _normalize(text: str) -> str:
    """Normalize ANSI control output for robust matching."""
    return re.sub(r"\x1b\[[0-9;?]*[A-Za-z]", "", text)


def _ordered_positions(text: str, tokens: tuple[str, ...]) -> tuple[int, ...]:
    """Return ordered token positions, or -1 for missing tokens."""
    positions: list[int] = []
    start = 0
    for token in tokens:
        position = text.find(token, start)
        positions.append(position)
        if position == -1:
            start = len(text)
        else:
            start = position + len(token)
    return tuple(positions)


def _wait_until(
    predicate: Callable[[], bool], timeout: float, interval: float = 0.01
) -> bool:
    """Poll until predicate is true, returning whether it happened in time."""
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        if predicate():
            return True
        time.sleep(interval)
    return predicate()


def run_prototype() -> PrototypeResult:
    """Run compatibility prototype and return verification details."""
    output = create_output(stdout=sys.stdout)
    prompt_is_active = threading.Event()
    redraw_observations: dict[str, bool] = {
        "rich_redraw": False,
        "chunk_redraw": False,
    }

    with tempfile.TemporaryDirectory(prefix="ptk-history-") as temp_dir:
        history_path = Path(temp_dir) / "history.txt"

        with _capture_fd_stdout() as terminal_capture:
            with create_pipe_input() as pipe_input:
                history = FileHistory(str(history_path))
                session = PromptSession(
                    message="PROMPT> ",
                    input=pipe_input,
                    output=output,
                    history=history,
                )

                def detect_active_prompt() -> None:
                    for _ in range(200):
                        if "PROMPT>" in terminal_capture.getvalue():
                            prompt_is_active.set()
                            return
                        time.sleep(0.01)

                def prompt_render_count() -> int:
                    return _normalize(terminal_capture.getvalue()).count("PROMPT>")

                def writer() -> None:
                    prompt_is_active.wait(timeout=1)
                    console = Console(
                        file=sys.stdout, force_terminal=True, color_system=None
                    )

                    rich_render_before = prompt_render_count()
                    console.print("RICH-EVENT")
                    redraw_observations["rich_redraw"] = _wait_until(
                        lambda: prompt_render_count() > rich_render_before,
                        timeout=1,
                    )

                    time.sleep(0.05)

                    chunk_render_before = prompt_render_count()
                    for chunk in ("chunk-1", "chunk-2", "chunk-3"):
                        sys.stdout.write(chunk)
                        sys.stdout.flush()
                        time.sleep(0.02)
                    sys.stdout.write("\n")
                    sys.stdout.flush()
                    redraw_observations["chunk_redraw"] = _wait_until(
                        lambda: prompt_render_count() > chunk_render_before,
                        timeout=1,
                    )
                    time.sleep(0.02)
                    pipe_input.send_text("alpha\n")

                detector = threading.Thread(target=detect_active_prompt, daemon=True)
                thread = threading.Thread(target=writer, daemon=True)
                detector.start()
                thread.start()
                with patch_stdout(raw=True):
                    first_response = session.prompt()
                thread.join(timeout=2)
                detector.join(timeout=1)

            with create_pipe_input() as pipe_input2:
                second_session = PromptSession(
                    message="PROMPT> ",
                    input=pipe_input2,
                    output=output,
                    history=FileHistory(str(history_path)),
                )
                pipe_input2.send_text("\x1b[A\n")
                with patch_stdout(raw=True):
                    second_response = second_session.prompt()

            unified_terminal_transcript = _normalize(terminal_capture.getvalue())

        rich_patch_stdout_positions = _ordered_positions(
            unified_terminal_transcript,
            ("PROMPT>", "RICH-EVENT", "PROMPT>"),
        )
        chunk_positions = _ordered_positions(
            unified_terminal_transcript,
            ("RICH-EVENT", "chunk-1chunk-2chunk-3"),
        )

        requirement_a = (
            prompt_is_active.is_set()
            and all(position != -1 for position in rich_patch_stdout_positions)
            and rich_patch_stdout_positions[0] < rich_patch_stdout_positions[1]
            and rich_patch_stdout_positions[1] < rich_patch_stdout_positions[2]
            and redraw_observations["rich_redraw"]
        )

        requirement_b = (
            all(position != -1 for position in chunk_positions)
            and chunk_positions[0] < chunk_positions[1]
            and redraw_observations["chunk_redraw"]
        )
        requirement_c = first_response == "alpha" and second_response == "alpha"

        return PrototypeResult(
            requirement_a=requirement_a,
            requirement_b=requirement_b,
            requirement_c=requirement_c,
            unified_terminal_transcript=unified_terminal_transcript,
            first_response=first_response,
            second_response=second_response,
        )


def main() -> int:
    result = run_prototype()

    print(f"requirement_a_console_print_above_prompt={result.requirement_a}")
    print(f"requirement_b_stdout_chunks_during_prompt={result.requirement_b}")
    print(f"requirement_c_filehistory_persists={result.requirement_c}")
    print(f"first_response={result.first_response!r}")
    print(f"second_response={result.second_response!r}")

    if not result.ok:
        print("prototype_status=FAIL")
        return 1

    print("prototype_status=PASS")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
