#!/usr/bin/env python3
"""brtwin — Brew Remote Tools for Windows."""

from __future__ import annotations

import argparse
import base64
import hashlib
import json
import os
import re
import socket
import ssl
import subprocess
import sys
import uuid
import urllib.error
import urllib.parse
import urllib.request
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path

WIN_DIR = Path(__file__).resolve().parent.parent
LIB_DIR = Path(__file__).resolve().parent
SHARED = LIB_DIR / "shared"
STATE = Path.home() / ".brt"
VERSION_FILE = WIN_DIR / "version"
EMBED_VERSION = "1.5.12"


def read_version() -> str:
    if VERSION_FILE.exists():
        return VERSION_FILE.read_text(encoding="utf-8").strip()
    return EMBED_VERSION


def ok(msg: str) -> None:
    print(f"\033[32m✓\033[0m {msg}")


def info(msg: str) -> None:
    print(f"\033[36m●\033[0m {msg}")


def warn(msg: str) -> None:
    print(f"\033[33m!\033[0m {msg}", file=sys.stderr)


def err(msg: str) -> None:
    print(f"\033[31m✗\033[0m {msg}", file=sys.stderr)
    sys.exit(1)


def load_config() -> dict[str, str]:
    cfg: dict[str, str] = {}
    env_file = STATE / ".env"
    if not env_file.exists():
        return cfg
    for line in env_file.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, v = line.split("=", 1)
        cfg[k.strip()] = v.strip().strip('"').strip("'")
    return cfg


def cmd_json(args: argparse.Namespace) -> int:
    src = sys.stdin.read() if args.file == "-" else Path(args.file).read_text(encoding="utf-8")
    print(json.dumps(json.loads(src), indent=2, ensure_ascii=False))
    return 0


def cmd_encode(args: argparse.Namespace) -> int:
    data = Path(args.input).read_bytes() if Path(args.input).is_file() else args.input.encode()
    print(base64.b64encode(data).decode())
    return 0


def cmd_decode(args: argparse.Namespace) -> int:
    raw = Path(args.input).read_text(encoding="utf-8") if Path(args.input).is_file() else args.input
    sys.stdout.buffer.write(base64.b64decode(raw.strip()))
    return 0


def cmd_hash(args: argparse.Namespace) -> int:
    algo = args.algo.lower()
    data = Path(args.input).read_bytes() if Path(args.input).is_file() else args.input.encode()
    if algo == "md5":
        print(hashlib.md5(data).hexdigest())
    elif algo == "sha1":
        print(hashlib.sha1(data).hexdigest())
    elif algo == "sha256":
        print(hashlib.sha256(data).hexdigest())
    elif algo == "sha512":
        print(hashlib.sha512(data).hexdigest())
    else:
        err(f"Unsupported algorithm: {algo}")
    return 0


def cmd_uuid(_: argparse.Namespace) -> int:
    print(str(uuid.uuid4()))
    return 0


def cmd_urlencode(args: argparse.Namespace) -> int:
    print(urllib.parse.quote(args.text, safe=""))
    return 0


def cmd_urldecode(args: argparse.Namespace) -> int:
    print(urllib.parse.unquote(args.text))
    return 0


def cmd_pass(args: argparse.Namespace) -> int:
    import secrets
    import string

    alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
    print("".join(secrets.choice(alphabet) for _ in range(args.length)))
    return 0


def cmd_calc(args: argparse.Namespace) -> int:
    expr = args.expr.replace(" ", "")
    if not re.fullmatch(r"[0-9+\-*/().]+", expr):
        err("Invalid expression")
    print(eval(expr, {"__builtins__": {}}, {}))
    return 0


def _dns_socket(host: str, rtype: str) -> int:
    if rtype.upper() == "A":
        for _, _, _, _, addr in socket.getaddrinfo(host, None, socket.AF_INET):
            print(addr[0])
        return 0
    err(f"Install dnspython for {rtype} lookups, or use A records only")
    return 1


def cmd_dns(args: argparse.Namespace) -> int:
    try:
        import dns.resolver  # type: ignore
    except ImportError:
        return _dns_socket(args.host, args.rtype)
    answers = dns.resolver.resolve(args.host, args.rtype)
    for r in answers:
        print(r)
    return 0


def cmd_dns_safe(args: argparse.Namespace) -> int:
    return cmd_dns(args)


def cmd_headers(args: argparse.Namespace) -> int:
    req = urllib.request.Request(args.url, method="GET")
    with urllib.request.urlopen(req, timeout=15) as resp:
        for k, v in resp.headers.items():
            print(f"{k}: {v}")
    return 0


def cmd_ssl(args: argparse.Namespace) -> int:
    ctx = ssl.create_default_context()
    with socket.create_connection((args.host, args.port), timeout=10) as sock:
        with ctx.wrap_socket(sock, server_hostname=args.host) as ssock:
            cert = ssock.getpeercert()
    print(json.dumps(cert, indent=2))
    return 0


def cmd_ip(args: argparse.Namespace) -> int:
    if args.mode in ("", "local", "check"):
        hostname = socket.gethostname()
        print(f"Host: {hostname}")
        for info in socket.getaddrinfo(hostname, None):
            print(info[4][0])
    if args.mode in ("", "public", "check"):
        try:
            with urllib.request.urlopen("https://api.ipify.org", timeout=5) as r:
                print(f"Public: {r.read().decode().strip()}")
        except urllib.error.URLError as exc:
            warn(str(exc))
    return 0


def cmd_ping(args: argparse.Namespace) -> int:
    rc = subprocess.call(
        ["ping", "-n", str(args.count), args.host],
        shell=False,
    )
    return rc


def cmd_portscan(args: argparse.Namespace) -> int:
    host = args.host
    ports = range(1, 1025) if args.ports == "common" else _parse_ports(args.ports)
    open_ports = []
    for port in ports:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(0.25)
        try:
            if s.connect_ex((host, port)) == 0:
                open_ports.append(port)
        finally:
            s.close()
    for p in open_ports:
        print(p)
    info(f"{len(open_ports)} open ports")
    return 0


def _parse_ports(spec: str) -> range:
    if "-" in spec:
        a, b = spec.split("-", 1)
        return range(int(a), int(b) + 1)
    return range(int(spec), int(spec) + 1)


class _QuietHTTPRequestHandler(SimpleHTTPRequestHandler):
    def log_message(self, format: str, *args) -> None:
        pass


def cmd_serve(args: argparse.Namespace) -> int:
    os.chdir(args.directory)
    server = ThreadingHTTPServer(("0.0.0.0", args.port), _QuietHTTPRequestHandler)
    info(f"Serving {os.getcwd()} on port {args.port}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    return 0


def cmd_killport(args: argparse.Namespace) -> int:
    port = str(args.port)
    out = subprocess.check_output(["netstat", "-ano"], text=True, errors="replace")
    pids = set()
    for line in out.splitlines():
        if f":{port} " in line and "LISTENING" in line.upper():
            parts = line.split()
            if parts:
                pids.add(parts[-1])
    if not pids:
        warn(f"Nothing listening on port {port}")
        return 0
    for pid in pids:
        subprocess.call(["taskkill", "/F", "/PID", pid], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        ok(f"Killed PID {pid} on port {port}")
    return 0


def cmd_sysinfo(_: argparse.Namespace) -> int:
    import platform

    print(f"  OS:       {platform.system()} {platform.release()} ({platform.machine()})")
    print(f"  Python:   {platform.python_version()}")
    print(f"  brtwin:   {read_version()}")
    print(f"  Host:     {socket.gethostname()}")
    return 0


def cmd_config(args: argparse.Namespace) -> int:
    STATE.mkdir(parents=True, exist_ok=True)
    env = STATE / ".env"
    if args.action == "path":
        print(env)
        return 0
    if args.action == "show":
        print(env.read_text(encoding="utf-8") if env.exists() else "# empty")
        return 0
    if not env.exists():
        env.write_text("# brtwin config\n", encoding="utf-8")
    os.environ.setdefault("EDITOR", "notepad")
    editor = os.environ.get("EDITOR", "notepad")
    subprocess.call([editor, str(env)])
    return 0


def cmd_browse(args: argparse.Namespace) -> int:
    browse = SHARED / "browse.py"
    if not browse.exists():
        err("browse.py not installed")
    cfg = load_config()
    env = os.environ.copy()
    for k, v in cfg.items():
        env.setdefault(k, v)
    return subprocess.call([sys.executable, str(browse), args.query], env=env)


def cmd_access(args: argparse.Namespace) -> int:
    ps1 = LIB_DIR / "access.ps1"
    if not ps1.exists():
        err("access.ps1 not found")
    subcmd = (args.access_cmd or "").strip()
    if not subcmd:
        err("Usage: brtwin -access <serve|connect|clients|...>")
    cmd = [
        "powershell",
        "-NoProfile",
        "-ExecutionPolicy",
        "Bypass",
        "-File",
        str(ps1),
        "-SubCmd",
        subcmd,
        *args.rest,
    ]
    return subprocess.call(cmd)


def cmd_version(_: argparse.Namespace) -> int:
    print(f"brtwin {read_version()}")
    return 0


def install_hints() -> tuple[str, str]:
    base = os.environ.get("BRTWIN_BASE_URL") or os.environ.get("BRT_BASE_URL") or "https://brt.sushii.dev"
    if sys.platform == "win32":
        primary = f"irm {base}/win/install.ps1 | iex"
        secondary = f"macOS / Linux: curl -fsSL {base}/install.sh | sh"
    else:
        primary = f"curl -fsSL {base}/install.sh | sh"
        secondary = f"Windows: irm {base}/win/install.ps1 | iex"
    return primary, secondary


def usage() -> None:
    ver = read_version()
    install_primary, install_secondary = install_hints()
    print(
        f"""brtwin — Brew Remote Tools for Windows v{ver}

Usage: brtwin -<command> [args...]

Networking
  -dns <host> [type]              DNS lookup (A, AAAA, MX, TXT...)
  -headers <url>                  HTTP response headers
  -ssl <host> [port]              SSL certificate info
  -ip [local|public|check]        IP information
  -ping <host> [-c count]         Ping host
  -portscan <host> [ports]        Scan TCP ports

Text & Data
  -json [file|-]                  Pretty-print JSON
  -encode <string|file>            Base64 encode
  -decode <string|file>            Base64 decode
  -hash <algo> <input>             Hash (md5, sha1, sha256, sha512)
  -urlencode <string>              URL-encode
  -urldecode <string>              URL-decode
  -uuid                            Random UUID
  -pass [length]                   Secure password
  -calc <expression>               Calculator

Media & Search
  -browse "query"                  Terminal web search

System
  -serve <port> [directory]       Static file server
  -killport <port>                 Kill process on port
  -sysinfo                         System information
  -config [show|path]              Config file (~/.brt/.env)

Remote Access (compatible with brt on macOS/Linux)
  -access serve [-p port]         Start relay + live console
  -access connect <host:port> <token>
  -access clients|disconnect|logs|clear-logs|status|stop|force-stop

  -version                         Show version
  -help                            Show this help

Install: {install_primary}
{install_secondary}
  Remote access works across brt and brtwin.
"""
    )


def main(argv: list[str] | None = None) -> int:
    argv = list(argv or sys.argv[1:])
    if not argv or argv[0] in ("-help", "-h", "help"):
        usage()
        return 0

    flag = argv[0]
    rest = argv[1:]

    p = argparse.ArgumentParser(add_help=False)
    sub: argparse.Namespace | None = None

    if flag == "-json":
        p.add_argument("file", nargs="?", default="-")
        sub = p.parse_args(rest)
        return cmd_json(sub)
    if flag == "-encode":
        p.add_argument("input")
        return cmd_encode(p.parse_args(rest))
    if flag == "-decode":
        p.add_argument("input")
        return cmd_decode(p.parse_args(rest))
    if flag == "-hash":
        p.add_argument("algo")
        p.add_argument("input")
        return cmd_hash(p.parse_args(rest))
    if flag == "-uuid":
        return cmd_uuid(p.parse_args(rest))
    if flag == "-urlencode":
        p.add_argument("text")
        return cmd_urlencode(p.parse_args(rest))
    if flag == "-urldecode":
        p.add_argument("text")
        return cmd_urldecode(p.parse_args(rest))
    if flag == "-pass":
        p.add_argument("length", nargs="?", type=int, default=20)
        return cmd_pass(p.parse_args(rest))
    if flag == "-calc":
        p.add_argument("expr")
        return cmd_calc(p.parse_args(rest))
    if flag == "-dns":
        p.add_argument("host")
        p.add_argument("rtype", nargs="?", default="A")
        return cmd_dns_safe(p.parse_args(rest))
    if flag == "-headers":
        p.add_argument("url")
        return cmd_headers(p.parse_args(rest))
    if flag == "-ssl":
        p.add_argument("host")
        p.add_argument("port", nargs="?", type=int, default=443)
        return cmd_ssl(p.parse_args(rest))
    if flag == "-ip":
        p.add_argument("mode", nargs="?", default="")
        return cmd_ip(p.parse_args(rest))
    if flag == "-ping":
        p.add_argument("host")
        p.add_argument("-c", "--count", type=int, default=4)
        return cmd_ping(p.parse_args(rest))
    if flag == "-portscan":
        p.add_argument("host")
        p.add_argument("ports", nargs="?", default="common")
        return cmd_portscan(p.parse_args(rest))
    if flag == "-serve":
        p.add_argument("port", type=int)
        p.add_argument("directory", nargs="?", default=".")
        return cmd_serve(p.parse_args(rest))
    if flag == "-killport":
        p.add_argument("port", type=int)
        return cmd_killport(p.parse_args(rest))
    if flag == "-sysinfo":
        return cmd_sysinfo(p.parse_args(rest))
    if flag == "-config":
        p.add_argument("action", nargs="?", default="edit")
        return cmd_config(p.parse_args(rest))
    if flag in ("-browse", "-search"):
        p.add_argument("query")
        return cmd_browse(p.parse_args(rest))
    if flag == "-access":
        if not rest:
            err("Usage: brtwin -access <serve|connect|...>")
        p.add_argument("access_cmd")
        p.add_argument("rest", nargs=argparse.REMAINDER)
        sub = p.parse_args(rest)
        return cmd_access(sub)
    if flag in ("-version", "-v"):
        return cmd_version(p.parse_args(rest))

    err(f"Unknown command: {flag}\nRun 'brtwin -help' for usage.")
    return 1


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