from __future__ import annotations

import mimetypes
import os
from importlib import import_module
from dataclasses import dataclass
from typing import Optional

from turbo_agent_core.schema.enums import FileType, KnowledgeType


@dataclass(frozen=True)
class ParsedKnowledgeFileType:
    """统一文件类型解析结果。"""

    file_type: Optional[FileType]
    knowledge_type: KnowledgeType
    mime_type: str
    filename: str
    detected_mime_type: Optional[str] = None
    detected_extension: Optional[str] = None


_FILETYPE_SAMPLE_SIZE = 261
_GENERIC_MIME_TYPES = {
    "application/octet-stream",
    "text/plain",
}
_DANGEROUS_DETECTED_EXTENSIONS = {
    ".exe",
    ".dll",
    ".so",
    ".dylib",
    ".msi",
    ".com",
    ".scr",
    ".bat",
    ".cmd",
    ".ps1",
    ".jar",
    ".apk",
    ".sh",
}
_DANGEROUS_DETECTED_MIME_PREFIXES = {
    "application/x-dosexec",
    "application/x-msdownload",
    "application/x-executable",
    "application/x-sharedlib",
    "application/java-archive",
    "text/x-shellscript",
}
_BLOCKED_DETECTED_EXTENSIONS = {
    ".bin",
}
_BLOCKED_DETECTED_MIME_TYPES = {
    "application/octet-stream",
}

_MIME_TO_RESULT: dict[str, tuple[Optional[FileType], KnowledgeType]] = {
    "application/pdf": (FileType.pdf, KnowledgeType.Document),
    "application/msword": (FileType.doc, KnowledgeType.Document),
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": (FileType.docx, KnowledgeType.Document),
    "application/vnd.ms-excel": (FileType.xls, KnowledgeType.DataTable),
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": (FileType.xlsx, KnowledgeType.DataTable),
    "application/vnd.ms-powerpoint": (FileType.ppt, KnowledgeType.Presentation),
    "application/vnd.openxmlformats-officedocument.presentationml.presentation": (FileType.pptx, KnowledgeType.Presentation),
    "text/plain": (FileType.txt, KnowledgeType.Document),
    "text/markdown": (FileType.md, KnowledgeType.Document),
    "text/x-markdown": (FileType.md, KnowledgeType.Document),
    "application/json": (FileType.json, KnowledgeType.DataTable),
    "text/json": (FileType.json, KnowledgeType.DataTable),
    "text/csv": (FileType.csv, KnowledgeType.DataTable),
    "application/csv": (FileType.csv, KnowledgeType.DataTable),
    "image/png": (FileType.png, KnowledgeType.Picture),
    "image/jpeg": (FileType.jpeg, KnowledgeType.Picture),
    "image/jpg": (FileType.jpeg, KnowledgeType.Picture),
    "image/gif": (FileType.gif, KnowledgeType.Picture),
    "image/tiff": (FileType.tiff, KnowledgeType.Picture),
    "image/bmp": (FileType.bmp, KnowledgeType.Picture),
    "image/webp": (FileType.webp, KnowledgeType.Picture),
    "image/svg+xml": (FileType.svg, KnowledgeType.Picture),
    "audio/mpeg": (FileType.mp3, KnowledgeType.Audio),
    "audio/mp3": (FileType.mp3, KnowledgeType.Audio),
    "audio/wav": (FileType.wav, KnowledgeType.Audio),
    "audio/x-wav": (FileType.wav, KnowledgeType.Audio),
    "audio/ogg": (FileType.ogg, KnowledgeType.Audio),
    "audio/aac": (FileType.aac, KnowledgeType.Audio),
    "audio/flac": (FileType.flac, KnowledgeType.Audio),
    "audio/wma": (FileType.wma, KnowledgeType.Audio),
    "audio/mp4a-latm": (FileType.mp4a, KnowledgeType.Audio),
    "audio/x-m4a": (FileType.m4a, KnowledgeType.Audio),
    "video/mp4": (FileType.mp4, KnowledgeType.Video),
    "video/quicktime": (FileType.mov, KnowledgeType.Video),
    "video/x-matroska": (FileType.mkv, KnowledgeType.Video),
    "video/x-flv": (FileType.flv, KnowledgeType.Video),
    "video/x-ms-wmv": (FileType.wmv, KnowledgeType.Video),
    "video/mpeg": (FileType.mpeg, KnowledgeType.Video),
    "video/mpg": (FileType.mpg, KnowledgeType.Video),
    "video/x-msvideo": (FileType.avi, KnowledgeType.Video),
    "video/webm": (FileType.webm, KnowledgeType.Video),
    "video/x-m4v": (FileType.m4v, KnowledgeType.Video),
    "video/rmvb": (FileType.rmvb, KnowledgeType.Video),
    "video/rm": (FileType.rm, KnowledgeType.Video),
    "video/mp2t": (FileType.ts, KnowledgeType.Video),
    "video/ogg": (FileType.ogv, KnowledgeType.Video),
    "application/x-mpegurl": (FileType.m3u8, KnowledgeType.Video),
}

_EXT_TO_RESULT: dict[str, tuple[Optional[FileType], KnowledgeType]] = {
    ".pdf": (FileType.pdf, KnowledgeType.Document),
    ".doc": (FileType.doc, KnowledgeType.Document),
    ".docx": (FileType.docx, KnowledgeType.Document),
    ".xls": (FileType.xls, KnowledgeType.DataTable),
    ".xlsx": (FileType.xlsx, KnowledgeType.DataTable),
    ".ppt": (FileType.ppt, KnowledgeType.Presentation),
    ".pptx": (FileType.pptx, KnowledgeType.Presentation),
    ".txt": (FileType.txt, KnowledgeType.Document),
    ".md": (FileType.md, KnowledgeType.Document),
    ".json": (FileType.json, KnowledgeType.DataTable),
    ".csv": (FileType.csv, KnowledgeType.DataTable),
    ".png": (FileType.png, KnowledgeType.Picture),
    ".jpg": (FileType.jpeg, KnowledgeType.Picture),
    ".jpeg": (FileType.jpeg, KnowledgeType.Picture),
    ".gif": (FileType.gif, KnowledgeType.Picture),
    ".tiff": (FileType.tiff, KnowledgeType.Picture),
    ".bmp": (FileType.bmp, KnowledgeType.Picture),
    ".webp": (FileType.webp, KnowledgeType.Picture),
    ".svg": (FileType.svg, KnowledgeType.Picture),
    ".mp3": (FileType.mp3, KnowledgeType.Audio),
    ".wav": (FileType.wav, KnowledgeType.Audio),
    ".ogg": (FileType.ogg, KnowledgeType.Audio),
    ".aac": (FileType.aac, KnowledgeType.Audio),
    ".flac": (FileType.flac, KnowledgeType.Audio),
    ".wma": (FileType.wma, KnowledgeType.Audio),
    ".mp4a": (FileType.mp4a, KnowledgeType.Audio),
    ".m4a": (FileType.m4a, KnowledgeType.Audio),
    ".mp4": (FileType.mp4, KnowledgeType.Video),
    ".mov": (FileType.mov, KnowledgeType.Video),
    ".mkv": (FileType.mkv, KnowledgeType.Video),
    ".flv": (FileType.flv, KnowledgeType.Video),
    ".wmv": (FileType.wmv, KnowledgeType.Video),
    ".mpeg": (FileType.mpeg, KnowledgeType.Video),
    ".mpg": (FileType.mpg, KnowledgeType.Video),
    ".avi": (FileType.avi, KnowledgeType.Video),
    ".webm": (FileType.webm, KnowledgeType.Video),
    ".m4v": (FileType.m4v, KnowledgeType.Video),
    ".rmvb": (FileType.rmvb, KnowledgeType.Video),
    ".rm": (FileType.rm, KnowledgeType.Video),
    ".ts": (FileType.ts, KnowledgeType.Video),
    ".ogv": (FileType.ogv, KnowledgeType.Video),
    ".m3u8": (FileType.m3u8, KnowledgeType.Video),
}


def _normalize_mime_type(mime_type: Optional[str]) -> str:
    normalized = (mime_type or "").split(";", 1)[0].strip().lower()
    return normalized or "application/octet-stream"


def _normalize_filename(filename: str) -> str:
    normalized = os.path.basename((filename or "").strip())
    return normalized or "unnamed"


def _guess_mime_from_bytes(file_bytes: Optional[bytes]) -> Optional[str]:
    if not file_bytes:
        return None
    try:
        puremagic = import_module("puremagic")
    except ImportError:
        puremagic = None
    try:
        puremagic_mime = puremagic.from_string(file_bytes, mime=True) if puremagic is not None else None
    except Exception:
        puremagic_mime = None
    if isinstance(puremagic_mime, str) and puremagic_mime.strip():
        return puremagic_mime.strip().lower()
    return None


def _guess_extension_from_bytes(file_bytes: Optional[bytes]) -> Optional[str]:
    if not file_bytes:
        return None
    try:
        puremagic = import_module("puremagic")
    except ImportError:
        puremagic = None
    try:
        detected_ext = puremagic.from_string(file_bytes, mime=False) if puremagic is not None else None
    except Exception:
        detected_ext = None
    if isinstance(detected_ext, str) and detected_ext.strip():
        normalized = detected_ext.strip().lower()
        return normalized if normalized.startswith(".") else f".{normalized}"
    return None


def _guess_mime_from_file(file_path: Optional[str]) -> Optional[str]:
    if not file_path:
        return None
    try:
        puremagic = import_module("puremagic")
    except ImportError:
        puremagic = None
    try:
        puremagic_mime = puremagic.from_file(file_path, mime=True) if puremagic is not None else None
    except Exception:
        puremagic_mime = None
    if isinstance(puremagic_mime, str) and puremagic_mime.strip():
        return puremagic_mime.strip().lower()
    try:
        with open(file_path, "rb") as file:
            return _guess_mime_from_bytes(file.read(_FILETYPE_SAMPLE_SIZE))
    except OSError:
        return None


def _guess_extension_from_file(file_path: Optional[str]) -> Optional[str]:
    if not file_path:
        return None
    try:
        puremagic = import_module("puremagic")
    except ImportError:
        puremagic = None
    try:
        detected_ext = puremagic.from_file(file_path, mime=False) if puremagic is not None else None
    except Exception:
        detected_ext = None
    if isinstance(detected_ext, str) and detected_ext.strip():
        normalized = detected_ext.strip().lower()
        return normalized if normalized.startswith(".") else f".{normalized}"
    return None


def _canonicalize_filename(filename: str, file_type: Optional[FileType]) -> str:
    if file_type is None:
        return filename
    stem, _ = os.path.splitext(filename)
    safe_stem = stem or "unnamed"
    return f"{safe_stem}.{file_type.value}"


def _resolve_result_by_mime(mime_type: str) -> Optional[tuple[Optional[FileType], KnowledgeType]]:
    return _MIME_TO_RESULT.get(mime_type)


def _resolve_result_by_extension(filename: str) -> Optional[tuple[Optional[FileType], KnowledgeType]]:
    normalized_ext = os.path.splitext(filename)[1].lower()
    if not normalized_ext:
        return None
    return _EXT_TO_RESULT.get(normalized_ext)


def _fallback_result(mime_type: str) -> tuple[Optional[FileType], KnowledgeType]:
    if mime_type.startswith("image/"):
        return None, KnowledgeType.Picture
    if mime_type.startswith("audio/"):
        return None, KnowledgeType.Audio
    if mime_type.startswith("video/"):
        return None, KnowledgeType.Video
    return None, KnowledgeType.Document


def validate_upload_file_signature(
    mime_type: Optional[str],
    filename: str,
    *,
    file_path: Optional[str] = None,
    file_bytes: Optional[bytes] = None,
) -> ParsedKnowledgeFileType:
    """校验上传文件签名，以检测结果为准并拦截不支持类型。"""

    parsed = parse_knowledge_file_type(
        mime_type,
        filename,
        file_path=file_path,
        file_bytes=file_bytes,
    )
    detected_mime = parsed.detected_mime_type
    detected_extension = parsed.detected_extension

    if not detected_mime or not detected_extension:
        raise ValueError("无法识别上传文件的真实类型，禁止上传")

    if parsed.file_type is None:
        raise ValueError(f"当前不支持该上传文件类型: {detected_mime}")

    if detected_extension and detected_extension.lower() in _DANGEROUS_DETECTED_EXTENSIONS:
        raise ValueError(f"检测到高风险文件类型，禁止上传: {detected_extension}")

    if detected_extension and detected_extension.lower() in _BLOCKED_DETECTED_EXTENSIONS:
        raise ValueError(f"当前不支持该上传文件类型: {detected_extension}")

    if detected_mime and any(detected_mime.startswith(prefix) for prefix in _DANGEROUS_DETECTED_MIME_PREFIXES):
        raise ValueError(f"检测到高风险文件 MIME，禁止上传: {detected_mime}")

    if detected_mime in _BLOCKED_DETECTED_MIME_TYPES:
        raise ValueError(f"当前不支持该上传文件类型: {detected_mime}")

    return parsed


def parse_knowledge_file_type(
    mime_type: Optional[str],
    filename: str,
    *,
    file_path: Optional[str] = None,
    file_bytes: Optional[bytes] = None,
) -> ParsedKnowledgeFileType:
    """统一解析上传文件的 MIME、知识类型与规范化文件名。"""

    normalized_name = _normalize_filename(filename)
    declared_mime = _normalize_mime_type(mime_type)
    detected_mime = _guess_mime_from_bytes(file_bytes) or _guess_mime_from_file(file_path)
    detected_extension = _guess_extension_from_bytes(file_bytes) or _guess_extension_from_file(file_path)
    guessed_mime_from_name = _normalize_mime_type(mimetypes.guess_type(normalized_name)[0]) if mimetypes.guess_type(normalized_name)[0] else None
    parsed_by_extension = _resolve_result_by_extension(normalized_name)

    parsed = None
    resolved_mime = detected_mime or declared_mime

    if detected_mime:
        parsed = _resolve_result_by_mime(detected_mime)
        if parsed is not None:
            resolved_mime = detected_mime

    if parsed is None and detected_extension:
        parsed = _EXT_TO_RESULT.get(detected_extension.lower())

    if parsed is None and guessed_mime_from_name and declared_mime in _GENERIC_MIME_TYPES:
        parsed = _resolve_result_by_mime(guessed_mime_from_name)
        if parsed is not None:
            resolved_mime = guessed_mime_from_name

    if parsed is None and parsed_by_extension is not None and declared_mime in _GENERIC_MIME_TYPES:
        parsed = parsed_by_extension
        if guessed_mime_from_name:
            resolved_mime = guessed_mime_from_name

    if parsed is None:
        parsed = _fallback_result(detected_mime or guessed_mime_from_name or declared_mime)
        resolved_mime = detected_mime or guessed_mime_from_name or declared_mime

    file_type, knowledge_type = parsed
    return ParsedKnowledgeFileType(
        file_type=file_type,
        knowledge_type=knowledge_type,
        mime_type=resolved_mime,
        filename=_canonicalize_filename(normalized_name, file_type),
        detected_mime_type=detected_mime,
        detected_extension=detected_extension,
    )


__all__ = [
    "ParsedKnowledgeFileType",
    "parse_knowledge_file_type",
    "validate_upload_file_signature",
]