import threading
import time
from pathlib import Path
from typing import List, Optional, Union

import smbclient
from smbclient import ClientConfig, register_session, reset_connection_cache
from smbprotocol.exceptions import (
    SMBException,
    SMBResponseException,
    SMBAuthenticationError,
)

from app import schemas
from app.core.config import settings, global_vars
from app.log import logger
from app.modules.filemanager import StorageBase
from app.modules.filemanager.storages import transfer_process
from app.schemas.types import StorageSchema
from app.utils.singleton import WeakSingleton

lock = threading.Lock()


class SMBConnectionError(Exception):
    """
    SMB 连接错误
    """

    pass


class SMB(StorageBase, metaclass=WeakSingleton):
    """
    SMB网络挂载存储相关操作 - 使用 smbclient 高级接口
    """

    # 存储类型
    schema = StorageSchema.SMB

    # 支持的整理方式
    transtype = {
        "move": "移动",
        "copy": "复制",
        "link": "硬链接",
    }

    # 文件块大小，默认10MB
    chunk_size = 10 * 1024 * 1024

    def __init__(self):
        super().__init__()
        self._connected = False
        self._server_path = None
        self._host = None
        self._username = None
        self._password = None

        self._init_connection()

    def _init_connection(self):
        """
        初始化SMB连接配置
        """
        try:
            conf = self.get_conf()
            if not conf:
                return

            self._host = conf.get("host")
            self._username = conf.get("username")
            self._password = conf.get("password")
            domain = conf.get("domain", "")
            share = conf.get("share", "")
            port = conf.get("port", 445)

            if not all([self._host, share]):
                logger.error("【SMB】缺少必要的连接参数：host 和 share")
                return

            # 构建服务器路径
            self._server_path = f"\\\\{self._host}\\{share}"

            # 配置全局客户端设置
            ClientConfig(
                username=self._username,
                password=self._password,
                domain=domain if domain else None,
                connection_timeout=60,
                port=port,
                auth_protocol="negotiate",  # 使用协商认证
                require_secure_negotiate=False,  # 匿名访问时可能需要关闭安全协商
            )

            # 注册会话以启用连接池
            register_session(
                self._host,
                username=self._username,
                password=self._password,
                port=port,
                encrypt=False,  # 根据需要启用加密
                connection_timeout=60,
            )

            # 测试连接
            self._test_connection()

            self._connected = True
            # 判断是否为匿名访问
            if self._is_anonymous_access():
                logger.info(f"【SMB】匿名连接成功：{self._server_path}")
            else:
                logger.info(
                    f"【SMB】认证连接成功：{self._server_path} (用户：{self._username})"
                )

        except Exception as e:
            logger.error(f"【SMB】连接初始化失败：{e}")
            self._connected = False

    def _test_connection(self):
        """
        测试SMB连接
        """
        try:
            # 尝试列出根目录来测试连接
            smbclient.listdir(self._server_path)
        except SMBAuthenticationError as e:
            raise SMBConnectionError(f"SMB认证失败：{e}")
        except SMBResponseException as e:
            raise SMBConnectionError(f"SMB响应错误：{e}")
        except SMBException as e:
            raise SMBConnectionError(f"SMB连接错误：{e}")
        except Exception as e:
            raise SMBConnectionError(f"连接测试失败：{e}")

    def _is_anonymous_access(self) -> bool:
        """
        检查是否为匿名访问
        """
        return not self._username and not self._password

    def _check_connection(self):
        """
        检查SMB连接状态
        """
        if not self._connected or not self._server_path:
            raise SMBConnectionError("【SMB】连接未建立或已断开，请检查配置！")

    def _normalize_path(self, path: Union[str, Path]) -> str:
        """
        标准化路径格式为SMB路径
        """
        path_str = str(path)

        # 处理根路径
        if path_str in ["/", "\\"]:
            return self._server_path

        # 去除前导斜杠
        if path_str.startswith("/"):
            path_str = path_str[1:]

        # 构建完整的SMB路径
        if path_str:
            return f"{self._server_path}\\{path_str.replace('/', '\\')}"
        else:
            return self._server_path

    def _create_fileitem(
        self, stat_result, file_path: str, name: str
    ) -> schemas.FileItem:
        """
        创建文件项
        """
        try:
            # 检查是否为目录
            is_directory = smbclient.path.isdir(file_path)

            # 处理路径
            relative_path = file_path.replace(self._server_path, "").replace("\\", "/")
            if not relative_path.startswith("/"):
                relative_path = "/" + relative_path

            if is_directory and not relative_path.endswith("/"):
                relative_path += "/"

            # 获取时间戳
            try:
                modify_time = int(stat_result.st_mtime)
            except (AttributeError, TypeError):
                modify_time = int(time.time())

            if is_directory:
                return schemas.FileItem(
                    storage=self.schema.value,
                    type="dir",
                    path=relative_path,
                    name=name,
                    basename=name,
                    modify_time=modify_time,
                )
            else:
                return schemas.FileItem(
                    storage=self.schema.value,
                    type="file",
                    path=relative_path,
                    name=name,
                    basename=Path(name).stem,
                    extension=Path(name).suffix[1:] if Path(name).suffix else None,
                    size=getattr(stat_result, "st_size", 0),
                    modify_time=modify_time,
                )
        except Exception as e:
            logger.error(f"【SMB】创建文件项失败：{e}")
            # 返回基本的文件项信息
            return schemas.FileItem(
                storage=self.schema.value,
                type="file",
                path=file_path.replace(self._server_path, "").replace("\\", "/"),
                name=name,
                basename=Path(name).stem,
                modify_time=int(time.time()),
            )

    def init_storage(self):
        """
        初始化存储
        """
        # 重置连接缓存
        reset_connection_cache()
        self._init_connection()

    def check(self) -> bool:
        """
        检查存储是否可用
        """
        if not self._connected:
            return False

        try:
            self._test_connection()
            return True
        except Exception as e:
            logger.debug(f"【SMB】连接检查失败：{e}")
            self._connected = False
            return False

    def list(self, fileitem: schemas.FileItem) -> List[schemas.FileItem]:
        """
        浏览文件
        """
        try:
            self._check_connection()

            if fileitem.type == "file":
                item = self.detail(fileitem)
                if item:
                    return [item]
                return []

            # 构建SMB路径
            smb_path = self._normalize_path(fileitem.path.rstrip("/"))

            # 列出目录内容
            try:
                entries = smbclient.listdir(smb_path)
            except SMBResponseException as e:
                logger.error(f"【SMB】列出目录失败: {smb_path} - {e}")
                return []
            except SMBException as e:
                logger.error(f"【SMB】列出目录失败: {smb_path} - {e}")
                return []

            items = []
            for entry in entries:
                if entry in [".", ".."]:
                    continue

                entry_path = f"{smb_path}\\{entry}"
                try:
                    stat_result = smbclient.stat(entry_path)
                    item = self._create_fileitem(stat_result, entry_path, entry)
                    items.append(item)
                except Exception as e:
                    logger.debug(f"【SMB】获取文件信息失败: {entry_path} - {e}")
                    continue

            return items
        except Exception as e:
            logger.error(f"【SMB】列出文件失败: {e}")
            return []

    def create_folder(
        self, fileitem: schemas.FileItem, name: str
    ) -> Optional[schemas.FileItem]:
        """
        创建目录
        """
        try:
            self._check_connection()

            parent_path = self._normalize_path(fileitem.path.rstrip("/"))
            new_path = f"{parent_path}\\{name}"

            # 创建目录
            smbclient.mkdir(new_path)

            # 返回创建的目录信息
            return schemas.FileItem(
                storage=self.schema.value,
                type="dir",
                path=f"{fileitem.path.rstrip('/')}/{name}/",
                name=name,
                basename=name,
                modify_time=int(time.time()),
            )
        except Exception as e:
            logger.error(f"【SMB】创建目录失败: {e}")
            return None

    def get_folder(self, path: Path) -> Optional[schemas.FileItem]:
        """
        获取目录，如目录不存在则创建
        """
        # 检查目录是否存在
        folder = self.get_item(path)
        if folder:
            return folder

        # 逐级创建目录
        parts = path.parts
        current_path = Path("/")

        for part in parts[1:]:  # 跳过根目录
            current_path = current_path / part
            folder = self.get_item(current_path)
            if not folder:
                parent_folder = self.get_item(current_path.parent)
                if not parent_folder:
                    logger.error(f"【SMB】父目录不存在: {current_path.parent}")
                    return None
                folder = self.create_folder(parent_folder, part)
                if not folder:
                    return None

        return folder

    def get_item(self, path: Path) -> Optional[schemas.FileItem]:
        """
        获取文件或目录，不存在返回None
        """
        try:
            self._check_connection()

            # 处理根目录
            if str(path) == "/":
                return schemas.FileItem(
                    storage=self.schema.value,
                    type="dir",
                    path="/",
                    name="",
                    basename="",
                    modify_time=int(time.time()),
                )

            smb_path = self._normalize_path(str(path).rstrip("/"))

            # 检查路径是否存在
            if not smbclient.path.exists(smb_path):
                return None

            stat_result = smbclient.stat(smb_path)
            file_name = Path(path).name

            return self._create_fileitem(stat_result, smb_path, file_name)

        except Exception as e:
            logger.debug(f"【SMB】获取文件项失败: {e}")
            return None

    def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
        """
        获取文件详情
        """
        return self.get_item(Path(fileitem.path))

    def delete(self, fileitem: schemas.FileItem) -> bool:
        """
        删除文件或目录
        """
        try:
            self._check_connection()

            smb_path = self._normalize_path(fileitem.path.rstrip("/"))
            logger.info(f"【SMB】开始删除: {fileitem.path} (类型: {fileitem.type})")

            # 先检查路径是否存在
            if not smbclient.path.exists(smb_path):
                logger.warn(f"【SMB】路径不存在，跳过删除: {fileitem.path}")
                return True

            if fileitem.type == "dir":
                # 递归删除目录及其内容
                logger.debug(f"【SMB】递归删除目录: {smb_path}")
                self._recursive_delete(smb_path)
            else:
                # 删除文件
                logger.debug(f"【SMB】删除文件: {smb_path}")
                smbclient.remove(smb_path)

            logger.info(f"【SMB】删除成功: {fileitem.path}")
            return True
        except SMBConnectionError as e:
            logger.error(f"【SMB】删除失败 - 连接错误: {fileitem.path} - {e}")
            return False
        except SMBResponseException as e:
            logger.error(f"【SMB】删除失败 - SMB响应错误: {fileitem.path} - {e}")
            return False
        except SMBException as e:
            logger.error(f"【SMB】删除失败 - SMB错误: {fileitem.path} - {e}")
            return False
        except Exception as e:
            logger.error(f"【SMB】删除失败 - 未知错误: {fileitem.path} - {e}")
            return False

    def _recursive_delete(self, smb_path: str):
        """
        递归删除目录及其所有内容
        """
        try:
            # 检查路径是否存在
            if not smbclient.path.exists(smb_path):
                logger.debug(f"【SMB】路径不存在，跳过删除: {smb_path}")
                return

            # 如果是文件，直接删除
            if smbclient.path.isfile(smb_path):
                logger.debug(f"【SMB】删除文件: {smb_path}")
                smbclient.remove(smb_path)
                return

            # 如果是目录，先删除其内容
            if smbclient.path.isdir(smb_path):
                logger.debug(f"【SMB】开始删除目录内容: {smb_path}")
                try:
                    # 列出目录内容
                    entries = smbclient.listdir(smb_path)
                    logger.debug(f"【SMB】目录 {smb_path} 包含 {len(entries)} 个项目")

                    for entry in entries:
                        if entry in [".", ".."]:
                            continue
                        entry_path = f"{smb_path}\\{entry}"
                        logger.debug(f"【SMB】递归删除子项: {entry_path}")
                        # 递归删除子项
                        self._recursive_delete(entry_path)

                    # 删除空目录
                    logger.debug(f"【SMB】删除空目录: {smb_path}")
                    smbclient.rmdir(smb_path)
                    logger.debug(f"【SMB】目录删除成功: {smb_path}")

                except SMBResponseException as e:
                    # 如果目录不为空，尝试强制删除
                    logger.warn(f"【SMB】目录不为空，尝试强制删除: {smb_path} - {e}")
                    # 使用remove方法尝试删除（某些SMB服务器支持）
                    try:
                        smbclient.remove(smb_path)
                        logger.info(f"【SMB】强制删除目录成功: {smb_path}")
                    except Exception as remove_error:
                        # 如果还是失败，记录错误并抛出异常
                        logger.error(
                            f"【SMB】无法删除非空目录: {smb_path} - {remove_error}"
                        )
                        raise SMBConnectionError(
                            f"无法删除非空目录 {smb_path}: {remove_error}"
                        )
                except SMBException as e:
                    logger.error(f"【SMB】SMB操作失败: {smb_path} - {e}")
                    raise SMBConnectionError(f"SMB操作失败 {smb_path}: {e}")

        except SMBConnectionError:
            # 重新抛出SMB连接错误
            raise
        except Exception as e:
            logger.error(f"【SMB】递归删除失败: {smb_path} - {e}")
            raise SMBConnectionError(f"递归删除失败 {smb_path}: {e}")

    def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
        """
        重命名文件
        """
        try:
            self._check_connection()

            old_path = self._normalize_path(fileitem.path.rstrip("/"))
            parent_path = Path(fileitem.path).parent
            new_path = self._normalize_path(str(parent_path / name))

            # 重命名
            smbclient.rename(old_path, new_path)

            logger.info(f"【SMB】重命名成功: {fileitem.path} -> {name}")
            return True
        except Exception as e:
            logger.error(f"【SMB】重命名失败: {e}")
            return False

    def download(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
        """
        带实时进度显示的下载
        """
        local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
        if not local_path:
            return None
        smb_path = self._normalize_path(fileitem.path)
        try:
            self._check_connection()

            # 确保本地目录存在
            local_path.parent.mkdir(parents=True, exist_ok=True)

            # 获取文件大小
            file_size = fileitem.size

            # 初始化进度条
            logger.info(f"【SMB】开始下载: {fileitem.name} -> {local_path}")
            progress_callback = transfer_process(Path(fileitem.path).as_posix())

            # 使用更高效的文件传输方式
            with smbclient.open_file(smb_path, mode="rb") as src_file:
                with open(local_path, "wb") as dst_file:
                    downloaded_size = 0
                    while True:
                        if global_vars.is_transfer_stopped(fileitem.path):
                            logger.info(f"【SMB】{fileitem.path} 下载已取消！")
                            return None
                        chunk = src_file.read(self.chunk_size)
                        if not chunk:
                            break
                        dst_file.write(chunk)
                        downloaded_size += len(chunk)
                        # 更新进度
                        if file_size:
                            progress = (downloaded_size * 100) / file_size
                            progress_callback(progress)

            # 完成下载
            progress_callback(100)
            logger.info(f"【SMB】下载完成: {fileitem.name}")
            return local_path

        except Exception as e:
            logger.error(f"【SMB】下载失败: {fileitem.name} - {e}")
            # 删除可能部分下载的文件
            if local_path.exists():
                local_path.unlink()
            return None

    def upload(
        self, fileitem: schemas.FileItem, path: Path, new_name: Optional[str] = None
    ) -> Optional[schemas.FileItem]:
        """
        带实时进度显示的上传
        """
        target_name = new_name or path.name
        target_path = Path(fileitem.path) / target_name
        smb_path = self._normalize_path(str(target_path))

        try:
            self._check_connection()

            # 获取文件大小
            file_size = path.stat().st_size

            # 初始化进度条
            logger.info(f"【SMB】开始上传: {path} -> {target_path}")
            progress_callback = transfer_process(path.as_posix())

            # 使用更高效的文件传输方式
            with open(path, "rb") as src_file:
                with smbclient.open_file(smb_path, mode="wb") as dst_file:
                    uploaded_size = 0
                    while True:
                        if global_vars.is_transfer_stopped(path.as_posix()):
                            logger.info(f"【SMB】{path} 上传已取消！")
                            return None
                        chunk = src_file.read(self.chunk_size)
                        if not chunk:
                            break
                        dst_file.write(chunk)
                        uploaded_size += len(chunk)
                        # 更新进度
                        if file_size:
                            progress = (uploaded_size * 100) / file_size
                            progress_callback(progress)

            # 完成上传
            progress_callback(100)
            logger.info(f"【SMB】上传完成: {target_name}")

            # 返回上传后的文件信息
            return self.get_item(target_path)

        except Exception as e:
            logger.error(f"【SMB】上传失败: {target_name} - {e}")
            return None

    def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
        """
        复制文件
        """
        try:
            # 下载到临时文件
            temp_file = self.download(fileitem)
            if not temp_file:
                return False

            # 获取目标目录
            target_folder = self.get_item(path)
            if not target_folder:
                return False

            # 上传到目标位置
            result = self.upload(target_folder, temp_file, new_name)

            # 删除临时文件
            if temp_file.exists():
                temp_file.unlink()

            return result is not None
        except Exception as e:
            logger.error(f"【SMB】复制失败: {e}")
            return False

    def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
        """
        移动文件
        """
        try:
            # 先复制
            if not self.copy(fileitem, path, new_name):
                return False

            # 再删除原文件
            if not self.delete(fileitem):
                logger.warn(f"【SMB】删除原文件失败: {fileitem.path}")
                return False

            return True
        except Exception as e:
            logger.error(f"【SMB】移动失败: {e}")
            return False

    def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
        """
        硬链接文件
        Samba服务器需要开启 unix extensions 支持
        """
        try:
            self._check_connection()
            src_path = self._normalize_path(fileitem.path)
            dst_path = self._normalize_path(target_file)

            # 检查源文件是否存在
            if not smbclient.path.exists(src_path):
                raise FileNotFoundError(f"源文件不存在: {src_path}")

            # 确保目标路径的父目录存在
            dst_parent = "\\".join(dst_path.rsplit("\\", 1)[:-1])
            if dst_parent and not smbclient.path.exists(dst_parent):
                logger.info(f"【SMB】创建目标目录: {dst_parent}")
                smbclient.makedirs(dst_parent, exist_ok=True)

            # 尝试创建硬链接
            smbclient.link(src_path, dst_path)
            logger.info(f"【SMB】硬链接创建成功: {src_path} -> {dst_path}")
            return True

        except SMBResponseException as e:
            # SMB协议错误，可能不支持硬链接
            logger.error(f"【SMB】创建硬链接失败(当前Samba服务器可能不支持硬链接): {e}")
            return False
        except Exception as e:
            logger.error(f"【SMB】创建硬链接失败: {e}")
            return False

    def softlink(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
        pass

    def usage(self) -> Optional[schemas.StorageUsage]:
        """
        存储使用情况
        """
        try:
            self._check_connection()
            volume_stat = smbclient.stat_volume(self._server_path)
            return schemas.StorageUsage(
                total=volume_stat.total_size,
                available=volume_stat.caller_available_size,
            )

        except Exception as e:
            logger.error(f"【SMB】获取存储使用情况失败: {e}")
            return None

    def __del__(self):
        """
        析构函数，清理连接
        """
        try:
            if self._connected:
                reset_connection_cache()
        except Exception as e:
            logger.debug(f"【SMB】清理连接失败: {e}")
