import os, sys
from pathlib import Path
from typing import Iterable, List, Tuple
import configparser
import logging

import paramiko
from scp import SCPClient

import boto3
from botocore.config import Config as BotocoreConfig
from botocore.exceptions import ClientError
from boto3.s3.transfer import TransferConfig, S3Transfer

from .logging_config import get_console_handler
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
log.addHandler(get_console_handler())
log.propagate = False

###################################################
#### SCP copy - local storage to local storage ####
###################################################
def create_ssh_client(server: str, port: int, user: str, key_file: str) -> paramiko.SSHClient:
    """
    Create an SSH client session to a remote server.
    """
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh_client.connect(hostname=server, port=port, username=user, key_filename=key_file)
    return ssh_client


def ensure_remote_directory(ssh_client: paramiko.SSHClient, directory: str) -> None:
    """
    Ensure that the remote directory exists, create it if it does not.
    """
    stdin, stdout, stderr = ssh_client.exec_command(f"mkdir -p {directory}")
    stdout.channel.recv_exit_status()  # Wait for command to complete


def scp_copy_file(
    ssh_client: paramiko.SSHClient,
    local_file_path: str,
    remote_file_path: str,
) -> None:
    """
    Copy a single file using SCP.
    """
    remote_dir = os.path.dirname(remote_file_path)
    ensure_remote_directory(ssh_client, remote_dir)

    with SCPClient(ssh_client.get_transport()) as scp:
        scp.put(local_file_path, remote_file_path)


def copy_tree_to_remote(
    local_base_dir: str | Path,
    remote_base_dir: str,
    remote_host: str,
    username: str,
    key_file: str,
    port: int = 22,
    dry_run: bool = True,
) -> List[Tuple[str, str]]:
    """
    Copy a local directory tree to a remote directory via SCP.

    Args:
        local_base_dir: Root of the local directory tree to copy.
        remote_base_dir: Root directory on the remote host to mirror into.
        remote_host: IP or hostname of the destination VM.
        username: SSH username.
        key_file: Path to SSH private key.
        port: SSH port (default: 22).
        dry_run: If True, only print what would be copied.

    Returns:
        List of (local_path, remote_path) tuples that were copied (or would be copied in dry_run).
    """
    local_base_dir = Path(local_base_dir)
    copied: List[Tuple[str, str]] = []

    ssh_client = create_ssh_client(remote_host, port, username, key_file)

    try:
        for root, dirs, files in os.walk(local_base_dir):
            for file in files:
                local_file_path = Path(root) / file
                relative_path = local_file_path.relative_to(local_base_dir)
                remote_file_path = os.path.join(remote_base_dir, str(relative_path))

                if dry_run:
                    log.debug(f"[DRY RUN] Would copy {local_file_path} -> {remote_file_path}")
                else:
                    scp_copy_file(ssh_client, str(local_file_path), remote_file_path)

                log.debug(f"Copied {local_file_path} -> {remote_file_path}")
    finally:
        ssh_client.close()

    return copied

###################################################
#### s3 copy - local storage to local storage #####
###################################################

# ----------------------------------------------------------------------
# rclone → boto3 helpers (from your screenshot)
# ----------------------------------------------------------------------
def load_rclone_credentials(config_file_path: str, remote_name: str) -> dict:
    """
    Load S3-style credentials from an rclone config file.
    """
    config = configparser.ConfigParser()
    config.read(config_file_path)

    section = config[remote_name]
    return {
        "aws_access_key_id": section.get("access_key_id"),
        "aws_secret_access_key": section.get("secret_access_key"),
        "endpoint_url": "https://" + section.get("endpoint"),
    }


def create_boto3_write_client(
    rclone_config_path: str, remote_name: str = "EUGrasslandwatch"
):
    """
    Create a boto3 S3 client using credentials from an rclone config.
    """
    s3_credentials = load_rclone_credentials(rclone_config_path, remote_name)
    s3_credentials["config"] = BotocoreConfig(signature_version="s3")  # legacy sig
    s3 = boto3.client("s3", **s3_credentials)
    return s3


# ----------------------------------------------------------------------
# Verbose upload helper (from your original code)
# ----------------------------------------------------------------------
def upload_file_verbose(s3client, local_path: str, bucket: str, dst_path: str) -> None:
    import threading

    class ProgressPercentage(object):
        def __init__(self, filename):
            self._filename = filename
            self._size = float(os.path.getsize(filename))
            self._seen_so_far = 0
            self._lock = threading.Lock()

        def __call__(self, bytes_amount):
            with self._lock:
                self._seen_so_far += bytes_amount
                percentage = (self._seen_so_far / self._size) * 100
                sys.stdout.write(
                    f"\rUploading {self._filename}: "
                    f"{self._seen_so_far:.0f} / {self._size:.0f} bytes "
                    f"({percentage:.2f}%)"
                )
                sys.stdout.flush()

    try:
        s3client.upload_file(
            str(local_path),
            bucket,
            dst_path,
            Callback=ProgressPercentage(str(local_path)),
        )
        log.debug(f"\nUpload complete: {local_path} -> s3://{bucket}/{dst_path}")
    except Exception as e:
        log.debug(f"\nError uploading {local_path} to s3://{bucket}/{dst_path}: {e}")


# ----------------------------------------------------------------------
# Path logic: keep same relative path under a prefix
# ----------------------------------------------------------------------
def compute_s3_key(local_file: Path, base_dir: Path, prefix: str) -> str:
    """
    Build the S3 object key using the file's path relative to base_dir.

    Example:
        local_file = /mnt/hdd/EUGW/support/file.tif
        base_dir   = /mnt/hdd
        prefix     = clclayers

        -> key = clclayers/EUGW/support/file.tif
    """
    rel = local_file.relative_to(base_dir)
    if prefix:
        key = f"{prefix.rstrip('/')}/{rel.as_posix()}"
    else:
        key = rel.as_posix()
    return key


# ----------------------------------------------------------------------
# Main operation
# ----------------------------------------------------------------------
def upload_tree_to_s3(
    base_dir: str,
    bucket: str,
    prefix: str,
    rclone_config: str,
    remote_name: str,
    dry_run: bool = False,
    delete_local: bool = False,
):
    base_dir = Path(base_dir).resolve()

    if not base_dir.exists():
        raise FileNotFoundError(f"Base directory does not exist: {base_dir}")

    if not base_dir.is_dir():
        raise ValueError("base_dir must be a directory")

    log.debug(f"Scanning directory tree: {base_dir}")

    if not dry_run:
        s3client = create_boto3_write_client(rclone_config, remote_name)
    else:
        s3client = None

    for root, dirs, files in os.walk(base_dir):
        for file in files:
            local_file = Path(root) / file
            key = compute_s3_key(local_file, base_dir, prefix)

            log.debug(f"→ {local_file} → s3://{bucket}/{key}")

            if dry_run:
                continue

            upload_file_verbose(s3client, str(local_file), bucket, key)

            if delete_local:
                local_file.unlink()

