import os
import hashlib
import posixpath
from pathlib import Path
import paramiko
from pathspec import PathSpec
from git import Repo

VPS_HOST = ""
VPS_PORT = 22
VPS_USER = "root"
VPS_PASS = ""
SSH_KEY = None
REMOTE_PATH = "/root"
LOCAL_PATH = "."

def load_gitignore():
    gitignore = Path(LOCAL_PATH) / ".gitignore"
    if gitignore.exists():
        with gitignore.open("r", encoding="utf-8") as f:
            return PathSpec.from_lines("gitwildmatch", f)
    return PathSpec.from_lines("gitwildmatch", [])

def md5_file(path: Path) -> str:
    h = hashlib.md5()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()

def get_local_files(spec):
    root = Path(LOCAL_PATH).resolve()
    files = {}
    for p in root.rglob("*"):
        if not p.is_file():
            continue
        rel_posix = p.relative_to(root).as_posix()   # IMPORTANT: converts backslashes -> forward slashes
        if spec.match_file(rel_posix):
            continue
        files[rel_posix] = md5_file(p)
    return files

def get_remote_files(ssh):
    """
    Returns dict: { 'path/relative/to/REMOTE_PATH': md5 }
    If remote md5sum is not available or command fails, returns {}.
    """
    # Use find + md5sum (handles spaces) - will fail silently if md5sum missing
    remote_cmd = f'cd {REMOTE_PATH} && find . -type f -print0 | xargs -0 md5sum 2>/dev/null || true'
    stdin, stdout, stderr = ssh.exec_command(remote_cmd)
    out = stdout.read().decode("utf-8", errors="ignore")
    files = {}
    for line in out.splitlines():
        parts = line.strip().split(None, 1)
        if len(parts) != 2:
            continue
        checksum, path = parts
        path = path.strip()
        if path.startswith("./"):
            path = path[2:]
        files[path] = checksum
    return files

def ensure_remote_dir(sftp, remote_dir):
    remote_dir = posixpath.normpath(remote_dir)
    # Build list of directories to ensure exist, from top -> bottom
    to_create = []
    cur = remote_dir
    while cur not in ("", "/"):
        to_create.append(cur)
        cur = posixpath.dirname(cur)
    to_create.reverse()
    for d in to_create:
        try:
            sftp.stat(d)
        except IOError:
            try:
                sftp.mkdir(d)
            except Exception:
                # ignore race/permission errors here (stat might have changed)
                pass

def upload_file(sftp, local_path, remote_full_path):
    remote_dir = posixpath.dirname(remote_full_path)
    ensure_remote_dir(sftp, remote_dir)
    sftp.put(local_path, remote_full_path)
    print("Uploaded:", remote_full_path)

def connect_ssh():
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    if SSH_KEY and not VPS_PASS:
        keyfile = os.path.expanduser(SSH_KEY)
        pkey = None
        try:
            pkey = paramiko.RSAKey.from_private_key_file(keyfile)
        except Exception:
            try:
                pkey = paramiko.Ed25519Key.from_private_key_file(keyfile)
            except Exception:
                pkey = None
        if pkey:
            ssh.connect(VPS_HOST, port=VPS_PORT, username=VPS_USER, pkey=pkey)
        else:
            # fallback to password if key loading failed
            if not VPS_PASS:
                raise RuntimeError("Failed to load SSH key and no password provided.")
            ssh.connect(VPS_HOST, port=VPS_PORT, username=VPS_USER, password=VPS_PASS)
    else:
        ssh.connect(VPS_HOST, port=VPS_PORT, username=VPS_USER, password=VPS_PASS)
    return ssh

commands_1 = [
        "export NVM_DIR=\"$HOME/.nvm\"",
    "[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\"",
    "[ -s \"$NVM_DIR/bash_autocompletion\" ] && \\. \"$NVM_DIR/bash_completion\"",
]
commands = [

    "cd /root",
    "pm2 stop 0",
    "npm run build",
    "pm2 start 0"
]

full_command = " && ".join(commands)
full_command1 = "\n".join(commands_1)
def main():
    print("→ Loading .gitignore...")
    spec = load_gitignore()

    # (Optionally) open repo to ensure we are in a git project - not strictly needed
    try:
        _repo = Repo(LOCAL_PATH)
    except Exception:
        _repo = None

    print("→ Scanning local files...")
    local_files = get_local_files(spec)
    print(f"   {len(local_files)} local (non-ignored) files found.")

    print(f"→ Connecting to {VPS_HOST}...")
    ssh = connect_ssh()
    sftp = ssh.open_sftp()

    print("→ Reading remote checksums (if available)...")
    remote_files = get_remote_files(ssh)
    if not remote_files:
        print("   Remote md5sum listing failed or returned nothing — will upload all local files.")
    else:
        print(f"   {len(remote_files)} remote files found.")

    to_upload = []
    if remote_files:
        for rel, md5 in local_files.items():
            remote_md5 = remote_files.get(rel)
            if remote_md5 != md5:
                to_upload.append(rel)
    else:
        to_upload = list(local_files.keys())

    if not to_upload:
        print("→ No changes to upload.")
    else:
        print(f"[+] Uploading {len(to_upload)} files...")
        for rel in to_upload:
            local_p = Path(LOCAL_PATH).resolve() / Path(rel)
            remote_full = posixpath.join(REMOTE_PATH, rel)   # uses forward slashes for remote
            upload_file(sftp, str(local_p), remote_full)
    print("Rebuilding...")
    s, sd, sderr = ssh.exec_command(full_command1)
    stdin, stdout, stderr = ssh.exec_command(f"bash -c '{full_command}'")
    print("STDOUT:")
    for line in stdout:
        print(line.strip())
    print("\nSTDERR:")
    for line in stderr:
        print(line.strip())
    sftp.close()
    ssh.close()
    print("→ Deployment finished.")

if __name__ == "__main__":
    main()