import os
import re
import subprocess


def generate_key(dest_name, data_dir):
    """Génère une paire de clés ed25519 pour cette destination si elle n'existe pas encore."""
    key_name = f"dest_{_slugify(dest_name)}_ed25519"
    os.makedirs(os.path.join(data_dir, "keys"), exist_ok=True)
    key_path = os.path.join(data_dir, "keys", key_name)
    if not os.path.exists(key_path):
        subprocess.run(
            [
                "ssh-keygen", "-t", "ed25519",
                "-f", key_path,
                "-N", "",
                "-C", f"backupmanager@{dest_name}",
            ],
            check=True,
            capture_output=True,
        )
        os.chmod(key_path, 0o600)
    return key_name


def get_public_key(key_name, data_dir):
    """Retourne la clé publique à copier dans authorized_keys sur le serveur distant."""
    pub_path = os.path.join(data_dir, "keys", key_name + ".pub")
    try:
        with open(pub_path) as f:
            return f.read().strip()
    except FileNotFoundError:
        return None


def transfer_archive(archive_name, destination, backup_dir, data_dir):
    """Transfère .tar + .info.json vers la destination via rsync SSH."""
    key_path = os.path.join(data_dir, "keys", destination.key_name)
    if not os.path.exists(key_path):
        raise FileNotFoundError(
            f"Clé SSH introuvable : {key_path}\n"
            "Accédez à la page de la destination pour afficher la clé publique."
        )

    from jobs.utils import sudo_exists
    files = []
    for ext in (".tar", ".info.json"):
        path = os.path.join(backup_dir, archive_name + ext)
        if sudo_exists(path):
            files.append(path)

    if not files:
        raise FileNotFoundError(f"Aucun fichier à transférer pour {archive_name}.")

    remote = f"{destination.user}@{destination.host}:{destination.remote_path}/"
    ssh_opts = (
        f"ssh -i {key_path} -p {destination.port}"
        " -o StrictHostKeyChecking=accept-new"
        " -o BatchMode=yes"
        " -o ConnectTimeout=30"
    )

    # sudo car les archives YNH (ynh_app/ynh_system) sont owned root
    cmd = ["sudo", "rsync", "-az", "--stats", "-e", ssh_opts] + files + [remote]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=7200)
    log = (result.stdout + result.stderr).strip()

    if result.returncode != 0:
        raise RuntimeError(
            f"rsync vers {destination.host} échoué (code {result.returncode}) :\n{log}"
        )
    return log


def test_connection(destination, data_dir):
    """Teste la connexion SSH vers la destination. Retourne (True, msg) ou (False, msg)."""
    key_path = os.path.join(data_dir, "keys", destination.key_name)
    if not os.path.exists(key_path):
        return False, "Clé SSH non générée. Affichez la destination pour la créer."

    result = subprocess.run(
        [
            "ssh",
            "-i", key_path,
            "-p", str(destination.port),
            "-o", "StrictHostKeyChecking=accept-new",
            "-o", "BatchMode=yes",
            "-o", "ConnectTimeout=10",
            f"{destination.user}@{destination.host}",
            "echo ok",
        ],
        capture_output=True,
        text=True,
        timeout=20,
    )
    if result.returncode == 0:
        return True, f"Connexion réussie vers {destination.host}:{destination.port}."
    return False, result.stderr.strip() or f"Connexion échouée (code {result.returncode})."


def push_archive_to_instance(archive_name, instance, backup_dir, job=None):
    """Pousse .tar + .info.json vers une instance fédérée via HTTP chunked. Retourne un log texte."""
    import hashlib
    from federation.client import FederationClient
    from jobs.utils import sudo_exists

    archive_path = os.path.join(backup_dir, archive_name + ".tar")
    tmp_tar = f"/tmp/backupmanager_push_{archive_name}.tar"
    tmp_info = f"/tmp/backupmanager_push_{archive_name}.info.json"

    try:
        result = subprocess.run(["sudo", "rsync", archive_path, tmp_tar],
                                capture_output=True, text=True)
        if result.returncode != 0:
            raise RuntimeError(f"Copie locale échouée : {result.stderr.strip()}")

        total_size = os.path.getsize(tmp_tar)
        sha256 = hashlib.sha256()
        chunk_size = 50 * 1024 * 1024
        with open(tmp_tar, "rb") as f:
            while True:
                data = f.read(65536)
                if not data:
                    break
                sha256.update(data)
        checksum = sha256.hexdigest()

        client = FederationClient(instance)
        upload_info = client.upload_start(archive_name + ".tar", total_size, checksum, chunk_size)
        upload_id = upload_info["upload_id"]

        with open(tmp_tar, "rb") as f:
            n = 0
            while True:
                data = f.read(chunk_size)
                if not data:
                    break
                client.upload_chunk(upload_id, n, data)
                n += 1

        info_json_content = None
        info_path = os.path.join(backup_dir, archive_name + ".info.json")
        if sudo_exists(info_path):
            r = subprocess.run(["sudo", "rsync", info_path, tmp_info], capture_output=True)
            if r.returncode == 0:
                try:
                    with open(tmp_info, "r", encoding="utf-8", errors="replace") as fh:
                        info_json_content = fh.read()
                finally:
                    subprocess.run(["sudo", "rm", "-rf", tmp_info], capture_output=True)

        client.upload_finish_with_info(upload_id, info_json_content)
        log = f"Transfert HTTP chunked vers {instance.name} ({instance.url}) — {total_size // (1024*1024)} Mo en {n} chunks."

        if job is not None:
            from retention import apply_remote_retention
            try:
                deleted = apply_remote_retention(job, client)
                if deleted:
                    log += f"\nRétention distante : {len(deleted)} archive(s) supprimée(s) : {', '.join(deleted)}"
            except Exception as exc:
                log += f"\n⚠ Rétention distante échouée : {exc}"

        return log

    finally:
        if os.path.exists(tmp_tar):
            os.unlink(tmp_tar)


def _slugify(s):
    return re.sub(r'[^a-z0-9]+', '-', s.lower().strip()).strip('-')
