import curses
import os
import signal
import subprocess
import time
from datetime import datetime
from pathlib import Path

from .db import get_db, now
from .discovery import discover_agents

STATUS_LABEL = {"running": "running", "done": "done", "error": "error", "paused": "paused", "waiting": "idle?"}
TYPE_ICON = {"claude": "CL", "codex": "CX", "gemini": "GM", "gpt": "GP", "copilot": "CP", "cursor": "CR", "aider": "AI", "other": "??"}

HOME = str(Path.home())


def _format_duration(created_at):
    try:
        start = datetime.strptime(created_at, "%Y-%m-%d %H:%M:%S")
        total_secs = int((datetime.now() - start).total_seconds())
        if total_secs < 0:
            return "-"
        if total_secs < 60:
            return "{}s".format(total_secs)
        mins = total_secs // 60
        if mins < 60:
            return "{}m".format(mins)
        hours = mins // 60
        return "{}h{}m".format(hours, mins % 60)
    except (ValueError, TypeError):
        return "-"


def _get_process_tree(pid):
    if not pid:
        return []
    try:
        result = subprocess.run(
            ["ps", "-eo", "pid,ppid,state,%cpu,%mem,rss,command"],
            capture_output=True, text=True, timeout=3,
        )
        if result.returncode != 0:
            return []
    except Exception:
        return []

    all_procs = {}
    for line in result.stdout.strip().split("\n")[1:]:
        line = line.strip()
        if not line:
            continue
        parts = line.split(None, 6)
        if len(parts) < 7:
            continue
        try:
            p = int(parts[0])
            ppid = int(parts[1])
            state = parts[2]
            cpu = float(parts[3])
            mem = float(parts[4])
            rss_kb = int(parts[5])
            cmd = parts[6]
        except (ValueError, IndexError):
            continue
        all_procs[p] = {
            "pid": p, "ppid": ppid, "state": state,
            "cpu": cpu, "mem": mem, "rss_kb": rss_kb, "cmd": cmd,
        }

    agent_proc = all_procs.get(pid)
    if not agent_proc:
        return []

    tree = [agent_proc]
    _collect_children(pid, all_procs, tree, depth=1)
    return tree


def _collect_children(parent_pid, all_procs, tree, depth):
    for p, info in all_procs.items():
        if info["ppid"] == parent_pid:
            info["_depth"] = depth
            tree.append(info)
            _collect_children(p, all_procs, tree, depth + 1)


def _format_rss(rss_kb):
    if rss_kb < 1024:
        return "{}K".format(rss_kb)
    mb = rss_kb / 1024
    if mb < 1024:
        return "{:.0f}M".format(mb)
    return "{:.1f}G".format(mb / 1024)


def _shorten_cmd(cmd, max_len):
    cmd = cmd.replace(HOME, "~")
    if "/node_modules/" in cmd:
        parts = cmd.split("/node_modules/")
        cmd = ".../node_modules/" + parts[-1]
    if len(cmd) > max_len:
        cmd = cmd[:max_len - 3] + "..."
    return cmd


def run_tui(interval=2):
    curses.wrapper(lambda stdscr: _main_loop(stdscr, interval))


def _main_loop(stdscr, interval):
    curses.curs_set(0)
    stdscr.timeout(interval * 1000)
    curses.use_default_colors()

    curses.init_pair(1, curses.COLOR_GREEN, -1)
    curses.init_pair(2, curses.COLOR_YELLOW, -1)
    curses.init_pair(3, curses.COLOR_RED, -1)
    curses.init_pair(4, curses.COLOR_CYAN, -1)
    curses.init_pair(5, curses.COLOR_WHITE, -1)
    curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE)
    curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_CYAN)
    curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_GREEN)
    curses.init_pair(9, curses.COLOR_MAGENTA, -1)

    selected = 0
    show_done = False
    message = ""
    message_until = 0

    while True:
        db = get_db()
        discover_agents(db)

        if show_done:
            query = ("SELECT * FROM agents ORDER BY (status = 'waiting') DESC, "
                     "(status = 'running') DESC, (status = 'done'), id DESC")
        else:
            query = ("SELECT * FROM agents WHERE status != 'done' ORDER BY "
                     "(status = 'waiting') DESC, (status = 'running') DESC, id DESC")
        agents = db.execute(query).fetchall()

        total = len(agents)
        running = sum(1 for a in agents if a["status"] == "running")
        waiting = sum(1 for a in agents if a["status"] == "waiting")
        done_count = sum(1 for a in agents if a["status"] == "done")

        if total > 0:
            selected = max(0, min(selected, total - 1))

        selected_tree = []
        selected_agent = None
        if total > 0:
            selected_agent = agents[selected]
            selected_tree = _get_process_tree(selected_agent["pid"])

        all_pids = [a["pid"] for a in agents if a["pid"]]
        total_cpu, total_mem = _get_aggregate_stats(all_pids)

        height, width = stdscr.getmaxyx()
        stdscr.erase()

        ts = datetime.now().strftime("%H:%M:%S")
        title = " WOOSTER"
        stats = " {} agents | {} running | {} idle | {} done | CPU: {:.1f}% | MEM: {} | {} ".format(
            total, running, waiting, done_count, total_cpu,
            _format_rss(total_mem), ts)
        pad = max(0, width - len(title) - len(stats))
        _addstr(stdscr, 0, 0, (title + " " * pad + stats)[:width],
                curses.color_pair(7) | curses.A_BOLD)

        list_header = "  {:>4}  {:<4} {:<22} {:<8} {:>5} {:>5} {:<6} {:<6} {}".format(
            "ID", "Type", "Name", "Status", "CPU%", "MEM", "TTY", "Up", "CWD")
        _addstr(stdscr, 2, 0, list_header[:width], curses.color_pair(5) | curses.A_BOLD)
        _addstr(stdscr, 3, 0, "\u2500" * width, curses.color_pair(5) | curses.A_DIM)

        split_y = max(8, min(height // 2, 4 + total + 1))
        max_rows = split_y - 4

        for i, a in enumerate(agents):
            if i >= max_rows:
                break

            y = 4 + i
            type_str = TYPE_ICON.get(a["type"], "??")
            status_str = STATUS_LABEL.get(a["status"], a["status"])
            tty_str = a["tty"] if a["tty"] else "-"
            uptime = _format_duration(a["created_at"])
            cwd_short = str(a["cwd"]).replace(HOME, "~") if a["cwd"] else ""

            live_cpu = "-"
            live_mem = "-"
            if a["pid"]:
                proc_info = _get_proc_stats(a["pid"])
                if proc_info:
                    live_cpu = "{:.1f}".format(proc_info["cpu"])
                    live_mem = _format_rss(proc_info["rss_kb"])

            line = "  {:>4}  {:<4} {:<22} {:<8} {:>5} {:>5} {:<6} {:<6} {}".format(
                a["id"], type_str, a["name"][:21], status_str,
                live_cpu, live_mem, tty_str, uptime, cwd_short)

            if i == selected:
                attr = curses.color_pair(6) | curses.A_BOLD
                line = line.ljust(width)
            elif a["status"] == "running":
                attr = curses.color_pair(1)
            elif a["status"] == "waiting":
                attr = curses.color_pair(2)
            elif a["status"] == "error":
                attr = curses.color_pair(3)
            elif a["status"] == "done":
                attr = curses.color_pair(4) | curses.A_DIM
            else:
                attr = curses.color_pair(5)

            _addstr(stdscr, y, 0, line[:width], attr)

        detail_y = split_y
        _addstr(stdscr, detail_y, 0, "\u2500" * width, curses.color_pair(5) | curses.A_DIM)
        detail_y += 1

        if selected_agent:
            name = selected_agent["name"]
            pid = selected_agent["pid"] or "?"
            cwd = str(selected_agent["cwd"]).replace(HOME, "~") if selected_agent["cwd"] else "-"
            _addstr(stdscr, detail_y, 0,
                    " {} | PID {} | {}".format(name, pid, cwd)[:width],
                    curses.color_pair(5) | curses.A_BOLD)
            detail_y += 1

            if selected_tree:
                tree_header = "  {:>7} {:<3} {:>5} {:>5}  {}".format(
                    "PID", "ST", "CPU%", "MEM", "COMMAND")
                _addstr(stdscr, detail_y, 0, tree_header[:width],
                        curses.color_pair(9) | curses.A_BOLD)
                detail_y += 1

                for proc in selected_tree:
                    if detail_y >= height - 1:
                        break
                    depth = proc.get("_depth", 0)
                    indent = "  " + "  " * depth
                    if depth > 0:
                        indent = "  " + "  " * (depth - 1) + "\u2514\u2500"

                    cmd_max = width - 30 - len(indent)
                    cmd_short = _shorten_cmd(proc["cmd"], max(20, cmd_max))

                    tree_line = "  {:>7} {:<3} {:>5.1f} {:>5}  {}{}".format(
                        proc["pid"], proc["state"], proc["cpu"],
                        _format_rss(proc["rss_kb"]), indent, cmd_short)

                    if proc["state"].startswith("R"):
                        attr = curses.color_pair(1)
                    elif proc["cpu"] >= 0.5:
                        attr = curses.color_pair(1) | curses.A_DIM
                    elif proc["state"].startswith("Z"):
                        attr = curses.color_pair(3) | curses.A_DIM
                    else:
                        attr = curses.color_pair(9) | curses.A_DIM

                    _addstr(stdscr, detail_y, 0, tree_line[:width], attr)
                    detail_y += 1
            else:
                _addstr(stdscr, detail_y, 0, "  (no process info available)",
                        curses.color_pair(5) | curses.A_DIM)

        footer_y = height - 1
        if message and time.time() < message_until:
            _addstr(stdscr, footer_y, 0, " {} ".format(message).ljust(width)[:width],
                    curses.color_pair(3) | curses.A_BOLD)
        else:
            show_hide = "h:hide done" if show_done else "h:show done"
            footer = " q:quit  k:kill  d:done  r:remove  {}  [\u2191\u2193 navigate] ".format(show_hide)
            _addstr(stdscr, footer_y, 0, footer.ljust(width)[:width], curses.color_pair(8))

        db.close()
        stdscr.refresh()

        key = stdscr.getch()
        if key == ord("q") or key == 27:
            break
        elif key == curses.KEY_UP or key == ord("k") - 4:
            selected = max(0, selected - 1)
        elif key == curses.KEY_DOWN or key == ord("j"):
            selected = min(total - 1, selected + 1) if total > 0 else 0
        elif key == ord("k") and total > 0:
            agent = agents[selected]
            if agent["pid"]:
                try:
                    os.kill(agent["pid"], signal.SIGTERM)
                    db2 = get_db()
                    db2.execute("UPDATE agents SET status = 'done', updated_at = ? WHERE id = ?",
                                (now(), agent["id"]))
                    db2.commit()
                    db2.close()
                    message = "Killed {} (PID {})".format(agent["name"], agent["pid"])
                except ProcessLookupError:
                    message = "{} already exited".format(agent["name"])
                except PermissionError:
                    message = "Permission denied"
            else:
                message = "No PID"
            message_until = time.time() + 3
        elif key == ord("d") and total > 0:
            agent = agents[selected]
            db2 = get_db()
            db2.execute("UPDATE agents SET status = 'done', updated_at = ? WHERE id = ?",
                        (now(), agent["id"]))
            db2.commit()
            db2.close()
            message = "Marked {} as done".format(agent["name"])
            message_until = time.time() + 3
        elif key == ord("r") and total > 0:
            agent = agents[selected]
            db2 = get_db()
            db2.execute("DELETE FROM todos WHERE agent_id = ?", (agent["id"],))
            db2.execute("DELETE FROM agents WHERE id = ?", (agent["id"],))
            db2.commit()
            db2.close()
            message = "Removed {}".format(agent["name"])
            message_until = time.time() + 3
            if selected >= total - 1:
                selected = max(0, selected - 1)
        elif key == ord("h"):
            show_done = not show_done


def _get_proc_stats(pid):
    try:
        result = subprocess.run(
            ["ps", "-p", str(pid), "-o", "%cpu,rss"],
            capture_output=True, text=True, timeout=2,
        )
        lines = result.stdout.strip().split("\n")
        if len(lines) < 2:
            return None
        parts = lines[1].split()
        return {"cpu": float(parts[0]), "rss_kb": int(parts[1])}
    except Exception:
        return None


def _get_aggregate_stats(pids):
    if not pids:
        return 0.0, 0
    try:
        pid_str = ",".join(str(p) for p in pids)
        result = subprocess.run(
            ["ps", "-p", pid_str, "-o", "%cpu,rss"],
            capture_output=True, text=True, timeout=2,
        )
        total_cpu = 0.0
        total_rss = 0
        for line in result.stdout.strip().split("\n")[1:]:
            parts = line.split()
            if len(parts) >= 2:
                total_cpu += float(parts[0])
                total_rss += int(parts[1])
        return total_cpu, total_rss
    except Exception:
        return 0.0, 0


def _addstr(stdscr, y, x, text, attr=0):
    height, width = stdscr.getmaxyx()
    if y >= height or x >= width:
        return
    try:
        stdscr.addnstr(y, x, text, width - x, attr)
    except curses.error:
        pass
