"""
生成结果文件操作 API

提供文件浏览、上传、下载、编辑等功能
"""
from flask import Blueprint, jsonify, request, send_file, after_this_request
from app.services.auth.decorators import login_required
from app.routes.common import (
    get_challenge_and_output_dir,
    is_binary_file,
    success_response,
    error_response
)
from pathlib import Path
from werkzeug.utils import secure_filename
import os
import tempfile
import traceback
import zipfile
import tarfile
import shutil

bp_result_file_api = Blueprint('generate_result_file_api', __name__)


@bp_result_file_api.route('/project-structure')
@login_required
def get_project_structure():
    """获取项目结构"""
    try:
        import logging
        logger = logging.getLogger(__name__)
        logger.debug("获取项目结构")

        challenge_id = request.args.get('challenge_id')
        logger.debug(f"接收到的 challenge_id 参数: {challenge_id}")

        # 使用公共函数获取题目数据和输出目录
        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir  # 错误时返回 error response

        logger.debug(f"从输出目录构建文件树: {output_dir}")
        output_path = Path(output_dir)

        if not output_path.exists():
            print(f"输出目录不存在: {output_path}")
            return error_response("输出目录不存在", 404)

        # 构建文件树
        def build_tree(path, relative_path=''):
            """递归构建文件树"""
            items = []
            try:
                for item in sorted(path.iterdir()):
                    if item.name.startswith('.'):
                        continue
                    
                    item_rel_path = os.path.join(relative_path, item.name) if relative_path else item.name
                    
                    if item.is_dir():
                        children = build_tree(item, item_rel_path)
                        items.append({
                            'name': item.name,
                            'type': 'directory',
                            'path': item_rel_path,
                            'children': children
                        })
                    else:
                        stat = item.stat()
                        items.append({
                            'name': item.name,
                            'type': 'file',
                            'path': item_rel_path,
                            'size': stat.st_size,
                            'modified': stat.st_mtime
                        })
            except PermissionError:
                pass
            
            return sorted(items, key=lambda x: (x['type'] == 'file', x['name'].lower()))

        tree = build_tree(output_path)
        logger.debug(f"构建的文件树数量: {len(tree)}")

        return success_response(tree=tree)

    except Exception as e:
        traceback.print_exc()
        return error_response(f"获取项目结构时出错: {str(e)}", 500)


@bp_result_file_api.route('/file-content')
@login_required
def get_file_content():
    """获取文件内容"""
    try:
        file_path = request.args.get('path', '')
        challenge_id = request.args.get('challenge_id')

        if not file_path:
            return error_response("未提供文件路径", 400)

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir

        clean_path = file_path.strip().lstrip('/').replace('..', '')
        output_path = Path(output_dir)
        target_file = output_path / clean_path

        try:
            target_file = target_file.resolve()
            output_path = output_path.resolve()
            target_file.relative_to(output_path)
        except (ValueError, OSError):
            return error_response("非法路径", 400)

        if not target_file.exists():
            return error_response("文件不存在", 404)

        if target_file.is_dir():
            return success_response(is_directory=True, content="")

        if is_binary_file(target_file.name):
            return success_response(is_binary=True, content=f"[二进制文件] {clean_path}")

        try:
            with open(target_file, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            return success_response(is_binary=False, is_directory=False, content=content)
        except Exception as e:
            return error_response(f"读取文件失败: {str(e)}", 500)

    except Exception as e:
        traceback.print_exc()
        return error_response(f"获取文件内容失败: {str(e)}", 500)


@bp_result_file_api.route('/file-download')
@login_required
def download_file():
    """下载单个文件"""
    try:
        file_path = request.args.get('path', '')
        challenge_id = request.args.get('challenge_id')

        if not file_path:
            return error_response("未提供文件路径", 400)

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir

        clean_path = file_path.strip().lstrip('/').replace('..', '')
        output_path = Path(output_dir)
        target_file = output_path / clean_path

        try:
            target_file = target_file.resolve()
            output_path = output_path.resolve()
            target_file.relative_to(output_path)
        except (ValueError, OSError):
            return error_response("非法路径", 400)

        if not target_file.exists():
            return error_response("文件不存在", 404)

        if target_file.is_dir():
            return error_response("无法下载目录", 400)

        return send_file(str(target_file), as_attachment=True, download_name=target_file.name, mimetype='application/octet-stream')

    except Exception as e:
        traceback.print_exc()
        return error_response(f"下载文件失败: {str(e)}", 500)


@bp_result_file_api.route('/file-create', methods=['POST'])
@login_required
def create_file_or_folder():
    """创建文件或文件夹"""
    try:
        data = request.get_json()
        name = data.get('name')
        parent_path = data.get('path', '')
        is_directory = data.get('is_directory', False)
        challenge_id = data.get('challenge_id') or request.args.get('challenge_id')

        if not name:
            return error_response("缺少名称参数", 400)

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir
        output_path = Path(output_dir)

        if parent_path:
            clean_path = parent_path.strip().lstrip('/').replace('..', '')
            parent_dir = output_path / clean_path
        else:
            parent_dir = output_path

        try:
            parent_dir = parent_dir.resolve()
            output_path = output_path.resolve()
            parent_dir.relative_to(output_path)
        except (ValueError, OSError):
            return error_response("非法路径", 400)

        parent_dir.mkdir(parents=True, exist_ok=True)
        new_path = parent_dir / name

        if new_path.exists():
            return error_response(f'{"文件夹" if is_directory else "文件"} {name} 已存在', 400)

        if is_directory:
            new_path.mkdir(parents=True, exist_ok=True)
            message = f'文件夹 {name} 创建成功'
        else:
            new_path.write_text('', encoding='utf-8')
            message = f'文件 {name} 创建成功'

        return success_response(message=message)
    except Exception as e:
        traceback.print_exc()
        return error_response(str(e), 500)


@bp_result_file_api.route('/file-delete', methods=['DELETE'])
@login_required
def delete_file_or_folder():
    """删除文件或文件夹"""
    try:
        data = request.get_json()
        file_path = data.get('path')
        is_directory = data.get('is_directory', False)
        challenge_id = data.get('challenge_id') or request.args.get('challenge_id')

        if not file_path:
            return error_response("缺少路径参数", 400)

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir
        output_path = Path(output_dir)

        clean_path = file_path.strip().lstrip('/').replace('..', '')
        target_file = output_path / clean_path

        try:
            target_file = target_file.resolve()
            output_path = output_path.resolve()
            target_file.relative_to(output_path)
        except (ValueError, OSError):
            return error_response("非法路径", 400)

        if not target_file.exists():
            return error_response("文件或文件夹不存在", 404)

        if is_directory:
            shutil.rmtree(target_file)
            message = f'文件夹 {target_file.name} 删除成功'
        else:
            target_file.unlink()
            message = f'文件 {target_file.name} 删除成功'

        return success_response(message=message)
    except Exception as e:
        traceback.print_exc()
        return error_response(str(e), 500)


@bp_result_file_api.route('/file-write', methods=['POST'])
@login_required
def write_file_content():
    """写入文件内容"""
    try:
        data = request.get_json()
        file_path = data.get('path')
        content = data.get('content', '')
        challenge_id = data.get('challenge_id') or request.args.get('challenge_id')

        if not file_path:
            return error_response("缺少路径参数", 400)

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir
        output_path = Path(output_dir)

        clean_path = file_path.strip().lstrip('/').replace('..', '')
        target_file = output_path / clean_path

        try:
            target_file = target_file.resolve()
            output_path = output_path.resolve()
            target_file.relative_to(output_path)
        except (ValueError, OSError):
            return error_response("非法路径", 400)

        target_file.parent.mkdir(parents=True, exist_ok=True)
        target_file.write_text(content, encoding='utf-8')

        return success_response(message="文件保存成功")
    except Exception as e:
        traceback.print_exc()
        return error_response(str(e), 500)


@bp_result_file_api.route('/file-upload', methods=['POST'])
@login_required
def upload_file():
    """上传文件，支持压缩包自动解压"""
    try:
        if 'file' not in request.files:
            return error_response("请选择文件", 400)

        file = request.files['file']
        parent_path = request.form.get('path', '')
        extract = request.form.get('extract', 'false').lower() == 'true'
        challenge_id = request.form.get('challenge_id') or request.args.get('challenge_id')

        if file.filename == '':
            return error_response("请选择文件", 400)

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir
        output_path = Path(output_dir)

        if parent_path:
            clean_path = parent_path.strip().lstrip('/').replace('..', '')
            save_dir = output_path / clean_path
        else:
            save_dir = output_path

        try:
            save_dir = save_dir.resolve()
            output_path = output_path.resolve()
            save_dir.relative_to(output_path)
        except (ValueError, OSError):
            return error_response("非法路径", 400)

        save_dir.mkdir(parents=True, exist_ok=True)
        safe_filename = secure_filename(file.filename)
        file_path = save_dir / safe_filename

        if extract and safe_filename.endswith(('.zip', '.rar', '.7z', '.tar', '.gz')):
            temp_path = save_dir / f"_{safe_filename}"
            file.save(str(temp_path))

            try:
                if safe_filename.endswith('.zip'):
                    with zipfile.ZipFile(temp_path, 'r') as zip_ref:
                        zip_ref.extractall(save_dir)
                elif safe_filename.endswith(('.tar', '.gz')):
                    with tarfile.open(temp_path, 'r:*') as tar_ref:
                        tar_ref.extractall(save_dir)

                temp_path.unlink()
                return success_response(message=f"压缩包 {safe_filename} 上传并解压成功")
            except Exception as e:
                if temp_path.exists():
                    temp_path.unlink()
                raise e
        else:
            if file_path.exists():
                return error_response(f"文件 {safe_filename} 已存在", 400)

            file.save(str(file_path))
            stat = file_path.stat()

            return success_response(
                file={"name": safe_filename, "path": str(file_path.relative_to(output_path)), "size": stat.st_size, "modified": stat.st_mtime},
                message=f"文件 {safe_filename} 上传成功"
            )

    except Exception as e:
        traceback.print_exc()
        return error_response(str(e), 500)


@bp_result_file_api.route('/download_all', methods=['GET'])
@login_required
def download_all():
    """下载完整题目包"""
    try:
        challenge_id = request.args.get('challenge_id')

        challenge_data, output_dir, category_id = get_challenge_and_output_dir(challenge_id)
        if challenge_data is None:
            return output_dir

        writeup_file = Path(output_dir) / 'writeup.md'
        challenge_name = '题目'
        if writeup_file.exists():
            try:
                import re
                with open(writeup_file, 'r', encoding='utf-8') as f:
                    writeup_content = f.read()
                match = re.search(r'^#\s+(.+)$', writeup_content, re.MULTILINE)
                if match:
                    challenge_name = match.group(1).strip()
            except Exception:
                pass

        temp_zip = tempfile.NamedTemporaryFile(suffix='.zip', delete=False)
        temp_zip.close()

        shutil.make_archive(temp_zip.name[:-4], 'zip', output_dir)

        @after_this_request
        def remove_file(response):
            try:
                os.remove(temp_zip.name)
            except Exception:
                pass
            return response

        return send_file(temp_zip.name, as_attachment=True, download_name=f"{challenge_name}_complete.zip", mimetype='application/zip')

    except Exception as e:
        traceback.print_exc()
        return error_response(f"下载失败: {str(e)}", 500)
