#!/usr/bin/env python3
"""Convert Royal TS .rtsz files to Remmina .remmina format."""

import argparse
import gzip
import os
import sys
import xml.etree.ElementTree as ET
import zipfile
from pathlib import Path
from datetime import datetime


CONNECTION_TAGS = {
    "RoyalRDSConnection": "RDP",
    "RoyalSSHConnection": "SSH",
    "RoyalVNCConnection": "VNC",
}

FOLDER_TAG = "RoyalFolder"
CREDENTIAL_TAG = "RoyalCredential"


def get_child_text(elem: ET.Element, tag: str, default: str = "") -> str:
    child = elem.find(tag)
    if child is not None and child.text:
        return child.text.strip()
    return default


def open_rtsz(path: str) -> bytes:
    raw = Path(path).read_bytes()

    if raw[:2] == b"PK":
        with zipfile.ZipFile(path) as zf:
            for name in zf.namelist():
                if name.lower().endswith((".xml", ".rtsx")):
                    return zf.read(name)
            return zf.read(zf.namelist()[0])

    if raw[:2] == b"\x1f\x8b":
        with gzip.open(path, "rb") as f:
            return f.read()

    return raw


def collect_credentials(root: ET.Element) -> dict[str, dict[str, str]]:
    creds: dict[str, dict[str, str]] = {}
    for elem in root.iter(CREDENTIAL_TAG):
        cred_id = get_child_text(elem, "ID") or get_child_text(elem, "Id")
        if not cred_id:
            continue
        creds[cred_id] = {
            "username": get_child_text(elem, "Username"),
            "domain": get_child_text(elem, "Domain"),
        }
    return creds


def resolve_username(
    elem: ET.Element, creds: dict[str, dict[str, str]]
) -> tuple[str, str]:
    username = get_child_text(elem, "CredentialUsername") or get_child_text(
        elem, "Username"
    )
    domain = get_child_text(elem, "CredentialDomain") or get_child_text(elem, "Domain")

    cred_id = get_child_text(elem, "CredentialId") or get_child_text(
        elem, "CredentialID"
    )
    if cred_id and cred_id in creds:
        if not username:
            username = creds[cred_id].get("username", "")
        if not domain:
            domain = creds[cred_id].get("domain", "")

    return username, domain


def build_server(elem: ET.Element, default_port: int) -> str:
    host = (
        get_child_text(elem, "URI")
        or get_child_text(elem, "ComputerName")
        or get_child_text(elem, "Hostname")
        or get_child_text(elem, "HostName")
    )
    port = get_child_text(elem, "Port")
    if port and port != str(default_port):
        return f"{host}:{port}"
    return host


def rdp_to_remmina(
    elem: ET.Element,
    name: str,
    group: str,
    creds: dict[str, dict[str, str]],
) -> str:
    username, domain = resolve_username(elem, creds)
    server = build_server(elem, 3389)
    description = get_child_text(elem, "Description")

    color_depth = get_child_text(elem, "ColorDepth") or "99"
    desktop_w = get_child_text(elem, "DesktopWidth") or get_child_text(
        elem, "ResolutionWidth"
    )
    desktop_h = get_child_text(elem, "DesktopHeight") or get_child_text(
        elem, "ResolutionHeight"
    )
    console = (
        "1" if get_child_text(elem, "ConnectToAdministerOrConsole") == "True" else "0"
    )

    nla = get_child_text(elem, "NetworkLevelAuthentication")
    security = ""
    if nla == "True":
        security = "nla"

    clipboard = "0" if get_child_text(elem, "RedirectClipboard") != "False" else "1"
    printer = "1" if get_child_text(elem, "RedirectPrinters") == "True" else "0"

    gw_host = get_child_text(elem, "GatewayHostName") or get_child_text(
        elem, "RemoteDesktopGatewayComputerName"
    )
    gw_user = get_child_text(elem, "GatewayUserName") or get_child_text(
        elem, "RemoteDesktopGatewayUsername"
    )

    lines = [
        "[remmina]",
        f"name={name}",
        "protocol=RDP",
        f"server={server}",
        f"username={username}",
        f"domain={domain}",
        "password=",
        f"group={group}",
        f"colordepth={color_depth}",
        "quality=2",
    ]

    if desktop_w and desktop_h:
        lines.append("resolution_mode=0")
        lines.append(f"resolution_width={desktop_w}")
        lines.append(f"resolution_height={desktop_h}")
    else:
        lines.append("resolution_mode=2")

    lines.extend(
        [
            "viewmode=1",
            "scale=1",
            f"console={console}",
            f"security={security}",
            "cert_ignore=1",
            f"disableclipboard={clipboard}",
            f"shareprinter={printer}",
            "sound=off",
            "network=auto",
        ]
    )

    if gw_host:
        lines.extend(
            [
                f"gateway_server={gw_host}",
                f"gateway_username={gw_user}",
                "gateway_password=",
                "gateway_usage=1",
            ]
        )

    if description:
        lines.append(f"notes_text={description}")

    lines.append("disablepasswordstoring=0")
    lines.append("profile-lock=0")
    lines.append("")

    return "\n".join(lines)


def ssh_to_remmina(
    elem: ET.Element,
    name: str,
    group: str,
    creds: dict[str, dict[str, str]],
) -> str:
    username, _ = resolve_username(elem, creds)
    server = build_server(elem, 22)
    description = get_child_text(elem, "Description")

    keyfile = get_child_text(elem, "KeyFilePath") or get_child_text(
        elem, "CredentialKeyFile"
    )
    ssh_auth = "1" if keyfile else "0"

    lines = [
        "[remmina]",
        f"name={name}",
        "protocol=SSH",
        f"server={server}",
        f"username={username}",
        "password=",
        f"group={group}",
        f"ssh_auth={ssh_auth}",
    ]

    if keyfile:
        lines.append(f"ssh_privatekey={keyfile}")
        lines.append("ssh_passphrase=")

    lines.extend(
        [
            "ssh_charset=UTF-8",
            "ssh_color_scheme=0",
            "ssh_compression=0",
            "ssh_stricthostkeycheck=0",
            "viewmode=1",
        ]
    )

    if description:
        lines.append(f"notes_text={description}")

    lines.append("disablepasswordstoring=0")
    lines.append("profile-lock=0")
    lines.append("")

    return "\n".join(lines)


def vnc_to_remmina(
    elem: ET.Element,
    name: str,
    group: str,
    creds: dict[str, dict[str, str]],
) -> str:
    username, _ = resolve_username(elem, creds)
    server = build_server(elem, 5900)
    description = get_child_text(elem, "Description")
    color_depth = get_child_text(elem, "ColorDepth") or "24"

    viewonly = "1" if get_child_text(elem, "ViewOnly") == "True" else "0"

    lines = [
        "[remmina]",
        f"name={name}",
        "protocol=VNC",
        f"server={server}",
        f"username={username}",
        "password=",
        f"group={group}",
        f"colordepth={color_depth}",
        "quality=9",
        f"viewonly={viewonly}",
        "viewmode=1",
        "disableclipboard=0",
    ]

    ssh_tunnel = get_child_text(elem, "SSHTunnelEnabled")
    if ssh_tunnel == "True":
        ssh_host = get_child_text(elem, "SSHTunnelHost")
        lines.extend(
            [
                "ssh_tunnel_enabled=1",
                f"ssh_tunnel_server={ssh_host}",
                "ssh_tunnel_auth=0",
            ]
        )

    if description:
        lines.append(f"notes_text={description}")

    lines.append("disablepasswordstoring=0")
    lines.append("profile-lock=0")
    lines.append("")

    return "\n".join(lines)


def sanitize_filename(name: str) -> str:
    name = name.replace("/", "_").replace("\\", "_")
    for ch in '<>:"|?*':
        name = name.replace(ch, "_")
    return name


def walk_tree(
    elem: ET.Element,
    path_parts: list[str],
    creds: dict[str, dict[str, str]],
    results: list[tuple[str, str, str]],
) -> None:
    for child in elem:
        tag = child.tag

        if tag == FOLDER_TAG:
            folder_name = get_child_text(child, "Name") or "Unnamed"
            walk_tree(child, path_parts + [folder_name], creds, results)

        elif tag in CONNECTION_TAGS:
            protocol = CONNECTION_TAGS[tag]
            conn_name = get_child_text(child, "Name") or "Unnamed"
            group = "/".join(path_parts)

            if protocol == "RDP":
                content = rdp_to_remmina(child, conn_name, group, creds)
            elif protocol == "SSH":
                content = ssh_to_remmina(child, conn_name, group, creds)
            elif protocol == "VNC":
                content = vnc_to_remmina(child, conn_name, group, creds)
            else:
                continue

            results.append((conn_name, group, content))

        else:
            walk_tree(child, path_parts, creds, results)


def dump_xml(root: ET.Element, indent: int = 0) -> None:
    prefix = "  " * indent
    attrs = " ".join(f'{k}="{v}"' for k, v in root.attrib.items())
    tag_str = f"{root.tag} {attrs}".strip() if attrs else root.tag
    text = (root.text or "").strip()
    if text and len(text) > 80:
        text = text[:77] + "..."
    if text:
        print(f"{prefix}<{tag_str}> {text}")
    else:
        print(f"{prefix}<{tag_str}>")
    for child in root:
        dump_xml(child, indent + 1)


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Convert Royal TS .rtsz files to Remmina .remmina format"
    )
    parser.add_argument("input", help="Path to .rtsz file")
    parser.add_argument(
        "-o",
        "--output-dir",
        default=".",
        help="Output directory for .remmina files (default: current directory)",
    )
    parser.add_argument(
        "--dump",
        action="store_true",
        help="Dump the XML structure and exit (for debugging)",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Show what would be created without writing files",
    )
    args = parser.parse_args()

    if not os.path.isfile(args.input):
        print(f"Error: file not found: {args.input}", file=sys.stderr)
        return 1

    try:
        xml_data = open_rtsz(args.input)
    except Exception as e:
        print(f"Error: cannot open {args.input}: {e}", file=sys.stderr)
        print(
            "The file may be encrypted with a document password.",
            file=sys.stderr,
        )
        return 1

    try:
        root = ET.fromstring(xml_data)
    except ET.ParseError as e:
        print(f"Error: cannot parse XML: {e}", file=sys.stderr)
        print(
            "The file may be encrypted or in an unsupported format.",
            file=sys.stderr,
        )
        return 1

    if args.dump:
        dump_xml(root)
        return 0

    creds = collect_credentials(root)
    results: list[tuple[str, str, str]] = []
    walk_tree(root, [], creds, results)

    if not results:
        print("No supported connections found (RDP, SSH, VNC).", file=sys.stderr)
        print("Use --dump to inspect the XML structure.", file=sys.stderr)
        return 1

    outdir = Path(args.output_dir)
    if not args.dry_run:
        outdir.mkdir(parents=True, exist_ok=True)

    created = 0
    for conn_name, group, content in results:
        ts = datetime.now().strftime("%s")
        safe_name = sanitize_filename(conn_name)
        filename = f"{ts}_{safe_name}_{created}.remmina"
        filepath = outdir / filename

        if args.dry_run:
            label = f"{group}/{conn_name}" if group else conn_name
            print(f"  {label} -> {filepath}")
        else:
            filepath.write_text(content)
            created += 1

    if args.dry_run:
        print(f"\nWould create {len(results)} file(s)")
    else:
        print(f"Created {created} .remmina file(s) in {outdir}")

    if not args.dry_run:
        print(
            "\nNote: passwords are not converted (Royal TS encrypts them)."
            "\nYou will need to re-enter passwords in Remmina."
        )

    return 0


if __name__ == "__main__":
    sys.exit(main())
