"""文件与GUI文件管理相关路由。"""
from __future__ import annotations
import os
import re
import zipfile
from io import BytesIO
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple

from flask import Blueprint, jsonify, request, send_file
from werkzeug.utils import secure_filename

from modules.upload_security import UploadSecurityError
from .auth_helpers import api_login_required, resolve_admin_policy, get_current_user_record
from .security import rate_limited
from .context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response
from .utils_common import debug_log

files_bp = Blueprint("files", __name__)


def sanitize_filename_preserve_unicode(filename: str) -> str:
    """在保留中文等字符的同时，移除危险字符和路径成分"""
    import re
    if not filename:
        return ""
    cleaned = filename.strip().replace("\x00", "")
    if not cleaned:
        return ""
    cleaned = cleaned.replace("\\", "/").split("/")[-1]
    cleaned = re.sub(r'[<>:"\\|?*\n\r\t]', "_", cleaned)
    cleaned = cleaned.strip(". ")
    if not cleaned:
        return ""
    return cleaned[:255]

@files_bp.route('/api/files')
@api_login_required
@with_terminal
def get_files(terminal, workspace, username):
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("collapse_workspace") or policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件浏览已被管理员禁用"}), 403
    structure = terminal.context_manager.get_project_structure()
    return jsonify(structure)


def _format_entry(entry) -> Dict[str, Any]:
    return {
        "name": entry.name,
        "path": entry.path,
        "type": entry.type,
        "size": entry.size,
        "modified_at": entry.modified_at,
        "extension": entry.extension,
        "is_editable": entry.is_editable,
    }


@files_bp.route('/api/gui/files/entries', methods=['GET'])
@api_login_required
@with_terminal
def gui_list_entries(terminal, workspace, username):
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件管理已被管理员禁用"}), 403
    relative_path = request.args.get('path') or ""
    manager = get_gui_manager(workspace)
    try:
        resolved_path, entries = manager.list_directory(relative_path)
        breadcrumb = manager.breadcrumb(resolved_path)
        return jsonify({
            "success": True,
            "data": {
                "path": resolved_path,
                "breadcrumb": breadcrumb,
                "items": [_format_entry(entry) for entry in entries]
            }
        })
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/create', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("gui_file_create", 30, 60, scope="user")
def gui_create_entry(terminal, workspace, username):
    payload = request.get_json() or {}
    parent = payload.get('path') or ""
    name = payload.get('name') or ""
    entry_type = payload.get('type') or "file"
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件管理已被管理员禁用"}), 403
    manager = get_gui_manager(workspace)
    try:
        new_path = manager.create_entry(parent, name, entry_type)
        return jsonify({"success": True, "path": new_path})
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/delete', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("gui_file_delete", 30, 60, scope="user")
def gui_delete_entries(terminal, workspace, username):
    payload = request.get_json() or {}
    paths = payload.get('paths') or []
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件管理已被管理员禁用"}), 403
    manager = get_gui_manager(workspace)
    try:
        result = manager.delete_entries(paths)
        return jsonify({"success": True, "result": result})
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/rename', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("gui_file_rename", 30, 60, scope="user")
def gui_rename_entry(terminal, workspace, username):
    payload = request.get_json() or {}
    path = payload.get('path')
    new_name = payload.get('new_name')
    if not path or not new_name:
        return jsonify({"success": False, "error": "缺少 path 或 new_name"}), 400
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件管理已被管理员禁用"}), 403
    manager = get_gui_manager(workspace)
    try:
        new_path = manager.rename_entry(path, new_name)
        return jsonify({"success": True, "path": new_path})
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/copy', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("gui_file_copy", 40, 120, scope="user")
def gui_copy_entries(terminal, workspace, username):
    payload = request.get_json() or {}
    paths = payload.get('paths') or []
    target_dir = payload.get('target_dir') or ""
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件管理已被管理员禁用"}), 403
    manager = get_gui_manager(workspace)
    try:
        result = manager.copy_entries(paths, target_dir)
        return jsonify({"success": True, "result": result})
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/move', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("gui_file_move", 40, 120, scope="user")
def gui_move_entries(terminal, workspace, username):
    payload = request.get_json() or {}
    paths = payload.get('paths') or []
    target_dir = payload.get('target_dir') or ""
    manager = get_gui_manager(workspace)
    try:
        result = manager.move_entries(paths, target_dir)
        return jsonify({"success": True, "result": result})
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/upload', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("gui_file_upload", 10, 300, scope="user")
def gui_upload_entry(terminal, workspace, username):
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("block_upload"):
        return jsonify({"success": False, "error": "文件上传已被管理员禁用"}), 403
    if 'file' not in request.files:
        return jsonify({"success": False, "error": "未找到文件"}), 400
    file_obj = request.files['file']
    if not file_obj or not file_obj.filename:
        return jsonify({"success": False, "error": "文件名为空"}), 400
    current_dir = request.form.get('path') or ""
    raw_name = request.form.get('filename') or file_obj.filename
    filename = sanitize_filename_preserve_unicode(raw_name) or secure_filename(raw_name)
    if not filename:
        return jsonify({"success": False, "error": "非法文件名"}), 400
    manager = get_gui_manager(workspace)
    try:
        target_path = manager.prepare_upload(current_dir, filename)
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400
    try:
        relative_path = manager._to_relative(target_path)
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400
    guard = get_upload_guard(workspace)
    try:
        result = guard.process_upload(
            file_obj,
            target_path,
            username=username,
            source="web_gui",
            original_name=raw_name,
            relative_path=relative_path,
        )
    except UploadSecurityError as exc:
        return build_upload_error_response(exc)
    except Exception as exc:
        return jsonify({"success": False, "error": f"保存文件失败: {exc}"}), 500

    metadata = result.get("metadata", {})
    return jsonify({
        "success": True,
        "path": relative_path,
        "filename": target_path.name,
        "scan": metadata.get("scan"),
        "sha256": metadata.get("sha256"),
        "size": metadata.get("size"),
    })


@files_bp.route('/api/gui/files/download', methods=['GET'])
@api_login_required
@with_terminal
def gui_download_entry(terminal, workspace, username):
    path = request.args.get('path')
    if not path:
        return jsonify({"success": False, "error": "缺少 path"}), 400
    manager = get_gui_manager(workspace)
    try:
        target = manager.prepare_download(path)
        if target.is_dir():
            memory_file = BytesIO()
            with zipfile.ZipFile(memory_file, mode='w', compression=zipfile.ZIP_DEFLATED) as zf:
                for root, dirs, files in os.walk(target):
                    for file in files:
                        full_path = Path(root) / file
                        arcname = manager._to_relative(full_path)
                        zf.write(full_path, arcname=arcname)
            memory_file.seek(0)
            download_name = f"{target.name}.zip"
            return send_file(memory_file, as_attachment=True, download_name=download_name, mimetype='application/zip')
        return send_file(target, as_attachment=True, download_name=target.name)
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/download/batch', methods=['POST'])
@api_login_required
@with_terminal
def gui_download_batch(terminal, workspace, username):
    payload = request.get_json() or {}
    paths = payload.get('paths') or []
    if not paths:
        return jsonify({"success": False, "error": "缺少待下载的路径"}), 400
    manager = get_gui_manager(workspace)
    try:
        memory_file = BytesIO()
        with zipfile.ZipFile(memory_file, mode='w', compression=zipfile.ZIP_DEFLATED) as zf:
            for rel in paths:
                target = manager.prepare_download(rel)
                arc_base = rel.strip('/') or target.name
                if target.is_dir():
                    for root, _, files in os.walk(target):
                        for file in files:
                            full_path = Path(root) / file
                            relative_sub = full_path.relative_to(target)
                            arcname = Path(arc_base) / relative_sub
                            zf.write(full_path, arcname=str(arcname))
                else:
                    zf.write(target, arcname=arc_base)
        memory_file.seek(0)
        download_name = f"selected_{len(paths)}.zip"
        return send_file(memory_file, as_attachment=True, download_name=download_name, mimetype='application/zip')
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


@files_bp.route('/api/gui/files/text', methods=['GET', 'POST'])
@api_login_required
@with_terminal
def gui_text_entry(terminal, workspace, username):
    manager = get_gui_manager(workspace)
    if request.method == 'GET':
        path = request.args.get('path')
        if not path:
            return jsonify({"success": False, "error": "缺少 path"}), 400
        try:
            content, modified = manager.read_text(path)
            return jsonify({"success": True, "path": path, "content": content, "modified_at": modified})
        except Exception as exc:
            return jsonify({"success": False, "error": str(exc)}), 400

    payload = request.get_json() or {}
    path = payload.get('path')
    content = payload.get('content')
    if path is None or content is None:
        return jsonify({"success": False, "error": "缺少 path 或 content"}), 400
    try:
        result = manager.write_text(path, content)
        return jsonify({"success": True, "data": result})
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 400


def _score_file_match(path: str, query: str) -> Optional[Tuple[int, ...]]:
    """为 @文件 搜索计算匹配分数；分数越低越靠前，None 表示不匹配。"""
    path_lower = path.lower()
    query_lower = query.lower()

    if path_lower.startswith(query_lower):
        return (0, len(path))

    query_parts = query_lower.split('/')
    path_parts = path_lower.split('/')

    if len(query_parts) > 1 and len(path_parts) >= len(query_parts):
        matched = True
        for i, q in enumerate(query_parts):
            p = path_parts[i]
            if not p.startswith(q) and q not in p:
                matched = False
                break
        if matched:
            return (1, len(path))

    name = path_parts[-1] if path_parts else path_lower
    if name.startswith(query_lower):
        return (2, len(path))

    if query_lower in name:
        return (3, len(path))

    if query_lower in path_lower:
        return (4, len(path))

    return None


def _scan_project_entries(project_path: Path, max_depth: int = 6) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
    """扫描项目目录，返回文件和文件夹列表（包含隐藏目录）。"""
    files: List[Dict[str, Any]] = []
    folders: List[Dict[str, Any]] = []

    for root, dirs, filenames in os.walk(project_path):
        rel_root = Path(root).relative_to(project_path)
        level = len(rel_root.parts) if str(rel_root) != '.' else 0
        if level > max_depth:
            del dirs[:]
            continue

        for d in list(dirs):
            dir_path = rel_root / d if str(rel_root) != '.' else Path(d)
            folders.append({
                "name": d,
                "path": str(dir_path).replace('\\', '/'),
                "type": "directory"
            })

        for f in filenames:
            file_path = rel_root / f if str(rel_root) != '.' else Path(f)
            ext = Path(f).suffix.lower()
            files.append({
                "name": f,
                "path": str(file_path).replace('\\', '/'),
                "type": "file",
                "extension": ext
            })

    return files, folders


def _scan_root_entries(project_path: Path) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
    """只扫描项目根目录的直接子项（包含隐藏目录）。"""
    files: List[Dict[str, Any]] = []
    folders: List[Dict[str, Any]] = []
    if not project_path.exists() or not project_path.is_dir():
        return files, folders
    for entry in sorted(project_path.iterdir(), key=lambda p: (p.is_file(), p.name.lower())):
        if entry.is_dir():
            folders.append({
                "name": entry.name,
                "path": entry.name,
                "type": "directory"
            })
        elif entry.is_file():
            files.append({
                "name": entry.name,
                "path": entry.name,
                "type": "file",
                "extension": entry.suffix.lower()
            })
    return files, folders


@files_bp.route('/api/project/files/search', methods=['GET'])
@api_login_required
@with_terminal
def search_project_files(terminal, workspace, username):
    """为 @文件 功能提供项目内文件/文件夹搜索（包含隐藏目录）。"""
    policy = resolve_admin_policy(get_current_user_record())
    if policy.get("ui_blocks", {}).get("collapse_workspace") or policy.get("ui_blocks", {}).get("block_file_manager"):
        return jsonify({"success": False, "error": "文件浏览已被管理员禁用"}), 403

    query = str(request.args.get('q') or '').strip()

    try:
        project_path = Path(getattr(workspace, 'project_path', '') or '').expanduser().resolve()
        if not project_path.exists():
            return jsonify({"success": False, "error": "项目路径不存在"}), 400
    except Exception as exc:
        return jsonify({"success": False, "error": f"项目路径无效: {exc}"}), 400

    try:
        files, folders = _scan_project_entries(project_path, max_depth=6)
    except Exception as exc:
        return jsonify({"success": False, "error": f"扫描项目失败: {exc}"}), 500

    if not query:
        root_files, root_folders = _scan_root_entries(project_path)
        root_items = root_folders + root_files
        return jsonify({
            "success": True,
            "data": {
                "items": root_items[:50],
                "total": len(root_items)
            }
        })

    scored: List[Tuple[Tuple[int, ...], Dict[str, Any]]] = []
    for entry in files + folders:
        score = _score_file_match(entry["path"], query)
        if score is not None:
            scored.append((score, entry))

    scored.sort(key=lambda x: (x[0], x[1]["path"].lower()))
    limit = max(10, min(100, int(request.args.get('limit') or 50)))
    return jsonify({
        "success": True,
        "data": {
            "items": [entry for _, entry in scored[:limit]],
            "total": len(scored)
        }
    })


__all__ = ["files_bp"]
