"""SSH 连接管理器"""
import os
import stat
import threading
import time
from typing import Callable, Optional, TypeVar

import paramiko

from .config import ConfigManager, ServerConfig

T = TypeVar("T")


class SSHManager:
    def __init__(self, config_manager: ConfigManager):
        self.config_manager = config_manager
        self._client: Optional[paramiko.SSHClient] = None
        self._current_server: Optional[ServerConfig] = None
        self._state = threading.Condition(threading.RLock())
        self._active_operations = 0
        self._connecting_server_id: Optional[str] = None

    @property
    def is_connected(self) -> bool:
        with self._state:
            client = self._client
            transport = client.get_transport() if client else None
            return transport is not None and transport.is_active()

    @property
    def current_server(self) -> Optional[ServerConfig]:
        with self._state:
            if not self._is_connected_locked():
                return None
            return self._current_server

    def connect(self, server_id: str) -> str:
        server = self.config_manager.get_server(server_id)
        if server is None:
            raise ValueError(f"服务器 ID '{server_id}' 不存在")

        while True:
            with self._state:
                current_server = self._current_server
                if self._is_connected_locked() and current_server and current_server.id == server_id:
                    return f"已连接到 {current_server.name} ({current_server.host}:{current_server.port})"
                while self._active_operations > 0 or self._connecting_server_id is not None:
                    self._state.wait()
                    current_server = self._current_server
                    if self._is_connected_locked() and current_server and current_server.id == server_id:
                        return f"已连接到 {current_server.name} ({current_server.host}:{current_server.port})"
                self._connecting_server_id = server_id
                break

        client = None
        old_client = None

        try:
            client = self._build_client(server)

            with self._state:
                old_client = self._client
                self._client = client
                self._current_server = server
                self._connecting_server_id = None
                self._state.notify_all()
        except Exception:
            with self._state:
                self._connecting_server_id = None
                self._state.notify_all()
            raise

        self._close_client(old_client)
        return f"已连接到 {server.name} ({server.host}:{server.port})"

    def disconnect(self) -> str:
        with self._state:
            while self._active_operations > 0 or self._connecting_server_id is not None:
                self._state.wait()
            client = self._client
            name = self._current_server.name if self._current_server else "未知"
            self._client = None
            self._current_server = None

        self._close_client(client)
        return f"已断开连接: {name}"

    def exec_command(self, command: str, timeout: int = 30) -> dict:
        if timeout <= 0:
            raise ValueError("timeout 必须大于 0")

        server = self._acquire_server()
        client = None
        try:
            client = self._build_client(server)
            transport = client.get_transport()
            if transport is None or not transport.is_active():
                raise ConnectionError("SSH 连接已断开，请重新连接")

            channel = transport.open_session(timeout=10)
            try:
                channel.exec_command(command)
                stdout_chunks, stderr_chunks = self._drain_command_output(channel, timeout)
                exit_code = channel.recv_exit_status()
                return {
                    "stdout": b"".join(stdout_chunks).decode("utf-8", errors="replace"),
                    "stderr": b"".join(stderr_chunks).decode("utf-8", errors="replace"),
                    "exit_code": exit_code,
                }
            finally:
                channel.close()
        finally:
            self._close_client(client)
            self._release_operation()

    def upload_file(self, local_path: str, remote_path: str) -> str:
        self._with_sftp(lambda sftp: sftp.put(local_path, remote_path))
        return f"已上传 {local_path} -> {remote_path}"

    def upload_content(self, content: str, remote_path: str) -> str:
        def write_content(sftp: paramiko.SFTPClient):
            with sftp.open(remote_path, "w") as file_obj:
                file_obj.write(content)

        self._with_sftp(write_content)
        return f"已写入内容到 {remote_path}"

    def download_file(self, remote_path: str, local_path: str) -> str:
        os.makedirs(os.path.dirname(local_path) or ".", exist_ok=True)
        self._with_sftp(lambda sftp: sftp.get(remote_path, local_path))
        return f"已下载 {remote_path} -> {local_path}"

    def read_file(self, remote_path: str) -> str:
        def read_content(sftp: paramiko.SFTPClient) -> str:
            with sftp.open(remote_path, "r") as file_obj:
                data = file_obj.read()
                if isinstance(data, bytes):
                    return data.decode("utf-8", errors="replace")
                return data

        return self._with_sftp(read_content)

    def list_dir(self, remote_path: str = ".") -> list[dict]:
        def read_dir(sftp: paramiko.SFTPClient) -> list[dict]:
            result = []
            for attr in sftp.listdir_attr(remote_path):
                result.append(
                    {
                        "name": attr.filename,
                        "size": attr.st_size,
                        "is_dir": stat.S_ISDIR(attr.st_mode) if attr.st_mode else False,
                        "permissions": oct(attr.st_mode & 0o777) if attr.st_mode else "unknown",
                        "modified": attr.st_mtime,
                    }
                )
            return result

        return self._with_sftp(read_dir)

    def get_status(self) -> dict:
        with self._state:
            current_server = self._current_server
            if self._is_connected_locked() and current_server:
                return {
                    "connected": True,
                    "server_name": current_server.name,
                    "host": current_server.host,
                    "port": current_server.port,
                    "username": current_server.username,
                }
            return {"connected": False}

    def _build_client(self, server: ServerConfig) -> paramiko.SSHClient:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        connect_kwargs = {
            "hostname": server.host,
            "port": server.port,
            "username": server.username,
            "timeout": 10,
            "banner_timeout": 10,
            "auth_timeout": 10,
            "channel_timeout": 10,
        }

        if server.auth_type == "password":
            connect_kwargs["password"] = server.password
        elif server.auth_type == "key":
            key_path = os.path.expanduser(server.key_path)
            if not os.path.exists(key_path):
                raise FileNotFoundError(f"密钥文件不存在: {key_path}")
            connect_kwargs["key_filename"] = key_path
            if server.key_passphrase:
                connect_kwargs["passphrase"] = server.key_passphrase

        client.connect(**connect_kwargs)
        return client

    def _drain_command_output(self, channel: paramiko.Channel, timeout: int) -> tuple[list[bytes], list[bytes]]:
        deadline = time.monotonic() + timeout
        stdout_chunks: list[bytes] = []
        stderr_chunks: list[bytes] = []

        while True:
            drained = False
            while channel.recv_ready():
                stdout_chunks.append(channel.recv(32768))
                drained = True
            while channel.recv_stderr_ready():
                stderr_chunks.append(channel.recv_stderr(32768))
                drained = True

            if channel.exit_status_ready() and not channel.recv_ready() and not channel.recv_stderr_ready():
                return stdout_chunks, stderr_chunks

            if time.monotonic() >= deadline:
                raise TimeoutError(f"命令执行超时（{timeout}s）")

            if not drained:
                time.sleep(0.05)

    def _with_sftp(self, operation: Callable[[paramiko.SFTPClient], T]) -> T:
        server = self._acquire_server()
        client = None
        sftp = None
        try:
            client = self._build_client(server)
            sftp = client.open_sftp()
            return operation(sftp)
        finally:
            if sftp is not None:
                try:
                    sftp.close()
                except Exception:
                    pass
            self._close_client(client)
            self._release_operation()

    def _acquire_server(self) -> ServerConfig:
        with self._state:
            if not self._is_connected_locked():
                raise ConnectionError("未连接到任何服务器，请先使用 connect 工具连接")
            server = self._current_server
            if server is None:
                raise ConnectionError("当前服务器不可用，请重新连接")
            self._active_operations += 1
            return server

    def _release_operation(self):
        with self._state:
            self._active_operations -= 1
            self._state.notify_all()

    def _is_connected_locked(self) -> bool:
        client = self._client
        transport = client.get_transport() if client else None
        return transport is not None and transport.is_active()

    def _close_client(self, client: Optional[paramiko.SSHClient]):
        if client is None:
            return
        try:
            client.close()
        except Exception:
            pass
