import fcntl
import os
import pty
import select
import struct
import subprocess
import termios

import pyte

SPECIAL_KEYS = {
    "enter": b"\r",
    "return": b"\r",
    "backspace": b"\x08",
    "tab": b"\t",
    "escape": b"\x1b",
    "esc": b"\x1b",  # alias
    "up": b"\x1b[A",
    "down": b"\x1b[B",
    "left": b"\x1b[D",
    "right": b"\x1b[C",
    "ctrl-c": b"\x03",
    "ctrl-d": b"\x04",
}

_original_sgr = pyte.Screen.select_graphic_rendition


def _patched_sgr(self, *params, **kwargs):
    kwargs.pop("private", None)
    return _original_sgr(self, *params, **kwargs)


pyte.Screen.select_graphic_rendition = _patched_sgr


class TerminalSession:
    def __init__(self, cols, rows):
        self.screen = pyte.Screen(cols, rows)
        self.screen.set_mode(pyte.modes.LNM)
        self.stream = pyte.ByteStream(self.screen)

        self.pid, self.master_fd = pty.fork()
        if self.pid == 0:
            os.environ["TERM"] = "xterm-256color"
            shell = os.environ.get("SHELL", "/bin/bash")
            os.execvp(shell, [shell])
        self.command_buffer = ""
        self.cursor_pos = 0
        self._last_screen_hash = None

    def send_keys(self, text: str):
        """Send text to terminal"""
        os.write(self.master_fd, text.encode())

    def screen_changed(self) -> bool:
        current_hash = hash(str(self.screen.display))
        if current_hash != self._last_screen_hash:
            self._last_screen_hash = current_hash
            return True
        return False

    def invalidate(self):
        self._last_screen_hash = None

    def send_key(self, key: str):
        if to_send := SPECIAL_KEYS.get(key.lower()):
            os.write(self.master_fd, to_send)

    def read_output(self, timeout=0.01) -> bool:
        ready, _, _ = select.select([self.master_fd], [], [], timeout)
        if ready:
            data = os.read(self.master_fd, 4096)
            if not data:
                return False
            self.stream.feed(data)
        return True

    def get_screen_text(self, num_lines=50) -> str:
        """Get recent screen content as plain text"""
        lines = []
        start_line = max(0, self.screen.lines - num_lines)

        for i in range(start_line, self.screen.lines):
            line_dict = self.screen.buffer[i]
            line_chars = []
            for col in range(self.screen.columns):
                char = line_dict.get(col)
                if char:
                    line_chars.append(char.data)
                else:
                    line_chars.append(" ")
            lines.append("".join(line_chars).rstrip())

        return "\n".join(lines)

    def get_screen_with_colors(self) -> list:
        """Get screen content with color info for rendering.
        Returns list of lists: [[{char, fg, bg, bold, ...}], ...]
        """
        result = []
        for i in range(self.screen.lines):
            line_dict = self.screen.buffer[i]
            line_data = []
            for col in range(self.screen.columns):
                char = line_dict.get(col)
                if char:
                    line_data.append(
                        {
                            "char": char.data,
                            "fg": char.fg,
                            "bg": char.bg,
                            "bold": char.bold,
                            "italics": char.italics,
                            "underscore": char.underscore,
                        }
                    )
                else:
                    line_data.append(
                        {
                            "char": " ",
                            "fg": "default",
                            "bg": "default",
                            "bold": False,
                            "italics": False,
                            "underscore": False,
                        }
                    )
            result.append(line_data)
        return result

    def handle_key_for_buffer(self, event):
        """Track command buffer as user types"""
        if event.char and event.char.isprintable():
            self.command_buffer = (
                self.command_buffer[: self.cursor_pos]
                + event.char
                + self.command_buffer[self.cursor_pos :]
            )
            self.cursor_pos += 1
        elif event.keysym == "BackSpace":
            if self.cursor_pos > 0:
                self.command_buffer = (
                    self.command_buffer[: self.cursor_pos - 1]
                    + self.command_buffer[self.cursor_pos :]
                )
                self.cursor_pos -= 1
        elif event.keysym == "Left":
            self.cursor_pos = max(0, self.cursor_pos - 1)
        elif event.keysym == "Right":
            self.cursor_pos = min(len(self.command_buffer), self.cursor_pos + 1)
        elif event.keysym == "Return":
            self.command_buffer = ""
            self.cursor_pos = 0
        elif event.keysym in ["Up", "Down"]:
            # Shell history navigation - clear our buffer
            self.command_buffer = ""
            self.cursor_pos = 0

    def get_current_command(self) -> str:
        if not self.command_buffer:
            return ""

        cursor_marker = "{{CURSOR}}"
        return (
            self.command_buffer[: self.cursor_pos]
            + cursor_marker
            + self.command_buffer[self.cursor_pos :]
        )

    def clear_current_command(self):
        for _ in range(len(self.command_buffer)):
            self.send_key("backspace")
        self.command_buffer = ""
        self.cursor_pos = 0

    def resize(self, cols, rows):
        self.screen.resize(rows, cols)

        size = struct.pack("HHHH", rows, cols, 0, 0)
        fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, size)

    def get_foreground_process(self) -> str | None:
        try:
            fg_pgrp = os.tcgetpgrp(self.master_fd)
            result = subprocess.run(
                ["ps", "-p", str(fg_pgrp), "-o", "comm="],
                capture_output=True,
                text=True,
                check=False,
            )
            if result.returncode == 0 and result.stdout.strip():
                return result.stdout.strip()
            return None

        except OSError as e:
            print(f"Warning: Could not get foreground process: {e}")
            return None
