"""Full-screen PTY terminal view."""

import curses
import os
import pty
import select
import signal
import struct
import fcntl
import termios

from muva.views.base import BaseView
from muva import theme


class TerminalView(BaseView):
    """Full-screen terminal emulator using PTY."""

    def __init__(self, cwd=None, shell=None, tab_id=1):
        self.cwd = cwd or os.getcwd()
        self.shell = shell or self._detect_shell()
        self.tab_id = tab_id
        self.master_fd = None
        self.pid = None
        self.buffer = []  # list of lines (strings)
        self.scroll = 0
        self.alive = False
        self._screen_h = 24
        self._screen_w = 80

    @property
    def title(self):
        return f"\ue795 T:{self.tab_id}"

    def _detect_shell(self):
        for sh in ["/bin/zsh", "/usr/bin/zsh", "/bin/bash", "/usr/bin/bash"]:
            if os.path.exists(sh):
                return sh
        return "/bin/sh"

    def start(self):
        """Fork a PTY and start the shell."""
        if self.alive:
            return

        pid, fd = pty.openpty()
        if pid == 0:
            # Child process
            os.chdir(self.cwd)
            os.execvp(self.shell, [self.shell])
        else:
            # Parent
            self.master_fd = fd
            self.pid = pid
            self.alive = True
            # Set non-blocking
            flags = fcntl.fcntl(fd, fcntl.F_GETFL)
            fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
            self.buffer = [""]

    def stop(self):
        """Kill the shell process."""
        if self.pid:
            try:
                os.kill(self.pid, signal.SIGTERM)
            except (ProcessLookupError, PermissionError, OSError):
                pass
            try:
                os.waitpid(self.pid, os.WNOHANG)
            except ChildProcessError:
                pass
        if self.master_fd is not None:
            try:
                os.close(self.master_fd)
            except OSError:
                pass
        self.alive = False
        self.master_fd = None
        self.pid = None

    def resize_pty(self, rows, cols):
        """Inform the PTY of a terminal size change."""
        if self.master_fd is not None:
            try:
                winsize = struct.pack("HHHH", rows, cols, 0, 0)
                fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
            except OSError:
                pass
        self._screen_h = rows
        self._screen_w = cols

    def _read_output(self):
        """Non-blocking read from PTY master."""
        if self.master_fd is None:
            return
        try:
            ready, _, _ = select.select([self.master_fd], [], [], 0.01)
            if ready:
                data = os.read(self.master_fd, 65536)
                if not data:
                    self.alive = False
                    return
                self._process_output(data.decode("utf-8", errors="replace"))
        except (OSError, ValueError):
            self.alive = False

    def _process_output(self, text):
        """Process raw output, handling basic control chars and ANSI sequences."""
        for ch in text:
            if ch == "\n":
                self.buffer.append("")
            elif ch == "\r":
                # Carriage return: move to start of current line
                pass  # we handle this simply
            elif ch == "\x08":  # backspace
                if self.buffer and self.buffer[-1]:
                    self.buffer[-1] = self.buffer[-1][:-1]
            elif ch == "\x07":  # bell
                pass
            else:
                if not self.buffer:
                    self.buffer = [""]
                self.buffer[-1] += ch

        # Trim buffer to reasonable size
        max_lines = 10000
        if len(self.buffer) > max_lines:
            self.buffer = self.buffer[-max_lines:]

    def on_enter(self):
        if not self.alive:
            self.start()
        curses.curs_set(0)

    def on_leave(self):
        pass

    def draw(self, stdscr, h, w, y_offset):
        self._read_output()
        self._screen_h = h - 1  # leave room for status
        self._screen_w = w

        # Resize PTY to match
        if self.alive:
            self.resize_pty(self._screen_h, w)

        # Fill background
        bg = theme.cp("default")
        for row in range(h):
            try:
                stdscr.addnstr(y_offset + row, 0, " " * w, w, bg)
            except curses.error:
                pass

        # Draw buffer lines (show last screen_h lines)
        visible_h = h - 1
        total = len(self.buffer)
        start = max(0, total - visible_h - self.scroll)
        end = start + visible_h

        for i, buf_idx in enumerate(range(start, min(end, total))):
            line = self.buffer[buf_idx]
            # Strip ANSI escape sequences for display
            clean = self._strip_ansi(line)
            try:
                stdscr.addnstr(y_offset + i, 0, clean[:w], w, theme.cp("fg"))
            except curses.error:
                pass

        # Status bar
        status_row = y_offset + h - 1
        if self.alive:
            status = f" \ue795 Terminal {self.tab_id} | {self.shell} | {self.cwd} "
        else:
            status = f" \ue795 Terminal {self.tab_id} [exited] "
        try:
            stdscr.addnstr(status_row, 0, status[:w].ljust(w), w,
                          theme.cp("statusbar") | curses.A_BOLD)
        except curses.error:
            pass

    def _strip_ansi(self, text):
        """Remove ANSI escape sequences from text."""
        import re
        return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b\[[\?]?[0-9;]*[a-zA-Z]", "", text)

    def handle_key(self, key):
        """Pass keys through to PTY. Leader key is handled by app before this."""
        if not self.alive:
            # Terminal exited, any key goes back
            return "back"

        if self.master_fd is None:
            return None

        # Convert curses key to bytes and send to PTY
        try:
            if key == curses.KEY_ENTER:
                os.write(self.master_fd, b"\r")
            elif key == curses.KEY_BACKSPACE or key == 127:
                os.write(self.master_fd, b"\x7f")
            elif key == curses.KEY_UP:
                os.write(self.master_fd, b"\x1b[A")
            elif key == curses.KEY_DOWN:
                os.write(self.master_fd, b"\x1b[B")
            elif key == curses.KEY_RIGHT:
                os.write(self.master_fd, b"\x1b[C")
            elif key == curses.KEY_LEFT:
                os.write(self.master_fd, b"\x1b[D")
            elif key == curses.KEY_HOME:
                os.write(self.master_fd, b"\x1b[H")
            elif key == curses.KEY_END:
                os.write(self.master_fd, b"\x1b[F")
            elif key == curses.KEY_DC:
                os.write(self.master_fd, b"\x1b[3~")
            elif key == curses.KEY_PPAGE:
                self.scroll = min(self.scroll + 10, max(0, len(self.buffer) - self._screen_h))
            elif key == curses.KEY_NPAGE:
                self.scroll = max(0, self.scroll - 10)
            elif key == 9:  # Tab
                os.write(self.master_fd, b"\t")
            elif key == 27:  # Escape
                os.write(self.master_fd, b"\x1b")
            elif 0 <= key <= 26:  # Ctrl+A through Ctrl+Z
                os.write(self.master_fd, bytes([key]))
            elif 32 <= key <= 126:
                os.write(self.master_fd, chr(key).encode("utf-8"))
            elif key >= 128:
                try:
                    os.write(self.master_fd, chr(key).encode("utf-8"))
                except (ValueError, OverflowError):
                    pass
        except OSError:
            self.alive = False

        # Reset scroll when typing
        if key not in (curses.KEY_PPAGE, curses.KEY_NPAGE):
            self.scroll = 0

        return None

    def __del__(self):
        self.stop()
