import argparse
import os
import posixpath
import shlex
import sys
from pathlib import Path

import paramiko


SKIP_DIRS = {"__pycache__", ".pytest_cache", ".git"}
SKIP_FILES = {".DS_Store"}


def parse_args():
    parser = argparse.ArgumentParser(description="Upload CCDI deployment package and run docker compose remotely.")
    parser.add_argument("--host", required=True)
    parser.add_argument("--port", type=int, required=True)
    parser.add_argument("--username", required=True)
    parser.add_argument("--password", required=True)
    parser.add_argument("--local-root", required=True)
    parser.add_argument("--remote-root", required=True)
    return parser.parse_args()


def ensure_remote_dir(ssh, remote_path):
    command = f"mkdir -p {shlex.quote(remote_path)}"
    exit_code, output, error = run_command(ssh, command)
    if exit_code != 0:
        raise RuntimeError(f"Failed to create remote directory {remote_path}:\n{output}\n{error}")


def resolve_sftp_root(sftp, shell_root):
    parts = [part for part in shell_root.split("/") if part]
    for index in range(len(parts)):
        candidate = "/" + "/".join(parts[index:])
        try:
            sftp.listdir(candidate)
            return candidate
        except OSError:
            continue
    raise RuntimeError(f"Unable to resolve SFTP path for remote root: {shell_root}")


def upload_tree(ssh, sftp, local_root, shell_remote_root, sftp_remote_root):
    for current_root, dirs, files in os.walk(local_root):
        dirs[:] = [directory for directory in dirs if directory not in SKIP_DIRS]
        relative = os.path.relpath(current_root, local_root)
        relative_posix = "" if relative == "." else relative.replace("\\", "/")
        shell_remote_dir = shell_remote_root if not relative_posix else posixpath.join(shell_remote_root, relative_posix)
        sftp_remote_dir = sftp_remote_root if not relative_posix else posixpath.join(sftp_remote_root, relative_posix)
        ensure_remote_dir(ssh, shell_remote_dir)
        for file_name in files:
            if file_name in SKIP_FILES:
                continue
            local_file = os.path.join(current_root, file_name)
            remote_file = posixpath.join(sftp_remote_dir, file_name)
            sftp.put(local_file, remote_file)


def run_command(ssh, command):
    stdin, stdout, stderr = ssh.exec_command(command)
    exit_code = stdout.channel.recv_exit_status()
    output = stdout.read().decode("utf-8", errors="ignore")
    error = stderr.read().decode("utf-8", errors="ignore")
    return exit_code, output, error


def sudo_prefix(password):
    return f"printf '%s\\n' {shlex.quote(password)} | sudo -S -p '' "


def detect_compose_command(ssh, password):
    daemon_prefix = ""
    daemon_checks = [
        ("", "docker ps >/dev/null 2>&1"),
        (sudo_prefix(password), f"{sudo_prefix(password)}docker ps >/dev/null 2>&1"),
    ]
    for prefix, probe in daemon_checks:
        exit_code, _, _ = run_command(ssh, probe)
        if exit_code == 0:
            daemon_prefix = prefix
            break
    else:
        raise RuntimeError("Docker daemon is not accessible on remote host.")

    checks = [
        (f"{daemon_prefix}docker compose", f"{daemon_prefix}docker compose version"),
        (f"{daemon_prefix}docker-compose", f"{daemon_prefix}docker-compose --version"),
    ]
    for compose_cmd, probe in checks:
        exit_code, _, _ = run_command(ssh, probe)
        if exit_code == 0:
            return compose_cmd
    raise RuntimeError("Docker Compose command not found on remote host.")


def main():
    args = parse_args()
    local_root = Path(args.local_root).resolve()
    remote_root = args.remote_root.rstrip("/")
    if not local_root.exists():
        raise FileNotFoundError(f"Local root does not exist: {local_root}")

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(
        hostname=args.host,
        port=args.port,
        username=args.username,
        password=args.password,
        timeout=20,
    )

    sftp = ssh.open_sftp()
    try:
        pre_clean = (
            "set -e;"
            f"mkdir -p {shlex.quote(remote_root)};"
            f"mkdir -p {shlex.quote(posixpath.join(remote_root, 'runtime/ruoyi'))};"
            f"mkdir -p {shlex.quote(posixpath.join(remote_root, 'runtime/logs/backend'))};"
            f"rm -rf {shlex.quote(posixpath.join(remote_root, 'backend'))} "
            f"{shlex.quote(posixpath.join(remote_root, 'frontend'))} "
            f"{shlex.quote(posixpath.join(remote_root, 'docker'))} "
            f"{shlex.quote(posixpath.join(remote_root, 'lsfx-mock-server'))};"
            f"rm -f {shlex.quote(posixpath.join(remote_root, 'docker-compose.yml'))} "
            f"{shlex.quote(posixpath.join(remote_root, '.env.example'))};"
        )
        exit_code, output, error = run_command(ssh, pre_clean)
        if exit_code != 0:
            raise RuntimeError(f"Remote cleanup failed:\n{output}\n{error}")

        sftp_remote_root = resolve_sftp_root(sftp, remote_root)
        upload_tree(ssh, sftp, str(local_root), remote_root, sftp_remote_root)

        compose_cmd = detect_compose_command(ssh, args.password)

        deploy_command = (
            "set -e;"
            f"cd {shlex.quote(remote_root)};"
            f"{compose_cmd} up -d --build;"
            f"{compose_cmd} ps;"
        )
        exit_code, output, error = run_command(ssh, deploy_command)
        if exit_code != 0:
            raise RuntimeError(f"Remote deploy failed:\n{output}\n{error}")

        logs_command = (
            "set -e;"
            f"cd {shlex.quote(remote_root)};"
            f"{compose_cmd} logs backend --tail 120;"
        )
        _, logs_output, logs_error = run_command(ssh, logs_command)

        print("=== DEPLOY OUTPUT ===")
        print(output.strip())
        if error.strip():
            print("=== DEPLOY STDERR ===")
            print(error.strip())

        if logs_output.strip():
            print("=== BACKEND LOGS ===")
            print(logs_output.strip())
        if logs_error.strip():
            print("=== BACKEND LOG STDERR ===")
            print(logs_error.strip())
    finally:
        sftp.close()
        ssh.close()


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        print(str(exc), file=sys.stderr)
        sys.exit(1)
