import sys
import os

# 将当前工作目录（项目根目录）添加到 Python 的模块搜索路径中
# 这样就可以直接导入 core, plugins 等包/模块
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
    sys.path.insert(0, project_root)

import json
import uuid
import logging
from flask import Flask, request, abort
# 导入 flask-restx
from flask_restx import Api, Resource, Namespace, fields
# 导入 werkzeug 用于处理文件上传
from werkzeug.utils import secure_filename

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 从 core 包导入
from core.plugin_manager import PluginManager
from core.rule_engine import RuleEngine
from core.rule_manager import RuleManager
from core.database import get_db_manager
from flask_cors import CORS


# --- 应用和 API 初始化 ---
app = Flask(__name__)
CORS(app)
# 配置文件上传
# 设置上传文件夹
UPLOAD_FOLDER = 'uploads/certificates'
# 确保上传目录存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# 设置允许的文件扩展名
ALLOWED_EXTENSIONS = {'pem', 'crt', 'key', 'cert'}

def allowed_file(filename):
    """检查文件扩展名是否被允许"""
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# 配置 Flask-RESTX
api = Api(app, version='1.0', title='IVE Honeypot API',
          description='蜜罐系统 API，用于管理蜜罐环境和配置反制规则。',
          doc='/docs/',  # Swagger UI 的路径
          redoc_url='/redoc/'  # ReDoc 的路径
          )

# --- 命名空间定义 ---
ns_env = Namespace('environments', description='环境管理')
ns_ctrl = Namespace('control', description='环境控制')
ns_cm = Namespace('countermeasures', description='反制能力管理')
ns_files = Namespace('files', description='文件管理')

# 添加命名空间到 API
api.add_namespace(ns_env, path='/api/v1/environments')
api.add_namespace(ns_ctrl, path='/api/v1/environments')
api.add_namespace(ns_cm, path='/api/v1/environments')
api.add_namespace(ns_files, path='/api/v1/files')

# --- 模型定义 (用于文档化请求/响应体) ---
# 环境输入模型
environment_input_model = api.model('EnvironmentInput', {
    'type': fields.String(required=False, description='蜜罐类型', example='http'),
    'name': fields.String(required=False, description='蜜罐名称', example='MyHoneypot'),
    'host': fields.String(required=False, description='监听主机地址', example='0.0.0.0'),
    'port': fields.Integer(required=False, description='监听端口', example=80),
    'banner': fields.String(required=False, description='服务 Banner', example='Apache/2.4.1'),
    'ssl': fields.Raw(required=False, description='SSL 配置 (如果需要HTTPS)', example={'certfile': '/path/to/cert.pem', 'keyfile': '/path/to/key.pem'}),
    'domains': fields.List(fields.String, required=False, description='绑定的域名列表', example=['example.com', 'www.example.com'])
})

# 环境输出模型 (更新以匹配数据库存储的实际结构)
environment_output_model = api.model('EnvironmentOutput', {
    'id': fields.String(description='环境 ID'),
    'message': fields.String(description='操作结果信息')
})

# 环境列表模型 (更新以匹配数据库存储的实际结构)
environment_list_model = api.model('EnvironmentList', {
    'id': fields.String(description='环境 ID'),
    'driver': fields.String(description='使用的驱动'),
    'config': fields.Raw(description='环境配置')
})

# 环境详情模型 (新增，用于更详细地展示环境信息)
environment_detail_model = api.model('EnvironmentDetail', {
    'id': fields.String(description='环境 ID'),
    'driver': fields.String(description='使用的驱动'),
    'config': fields.Raw(description='环境配置'),
    'status': fields.String(description='环境状态')
})

# 错误模型
error_model = api.model('Error', {
    'error': fields.String(description='错误信息')
})

# 消息模型
message_model = api.model('Message', {
    'message': fields.String(description='操作结果信息')
})

# 为环境配置反制措施的输入模型
# 客户端可以发送规则ID列表或完整的规则对象列表
configure_countermeasures_input_model = api.model('ConfigureCountermeasuresInput', {
    'rule_ids': fields.List(fields.String, required=False, description='要关联到环境的规则ID列表', example=['inject_fake_flag_on_admin', 'random_delay_on_login']),
    'rules': fields.List(fields.Raw, required=False, description='要关联到环境的完整规则对象列表 (优先级高于 rule_ids)')
})

# 反制动作描述模型
countermeasure_description_model = api.model('CountermeasureDescription', {
    'name': fields.String(description='反制动作名称'),
    'description': fields.String(description='反制动作描述')
})

# 文件上传模型
file_upload_model = api.model('FileUpload', {
    'message': fields.String(description='操作结果信息'),
    'filepath': fields.String(description='上传文件的存储路径')
})

# --- 配置加载 ---
def load_system_config(config_file='config/config.json'):
    """加载系统配置"""
    try:
        with open(config_file, 'r', encoding='utf-8') as f:
            config = json.load(f)
        logger.info(f"Loaded system configuration from {config_file}")
        return config
    except Exception as e:
        logger.error(f"Failed to load system configuration from {config_file}: {e}")
        # Return default configuration
        return {
            "server": {"host": "0.0.0.0", "port": 8081},
            "database": {"path": "data/honeypot.db"},
            "plugins": {
                "driver_dirs": ["./drivers"],
                "countermeasure_dirs": ["./countermeasures"]
            },
            "logging": {
                "level": "INFO",
                "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
                "audit_log_file": "./logs/countermeasure_audit.log"
            }
        }

# 加载系统配置
system_config = load_system_config()

# --- 核心组件初始化 ---
# 初始化数据库管理器
db_manager = get_db_manager()
# 初始化插件管理器、规则管理器和规则引擎
plugin_manager = PluginManager(
    [os.path.join(project_root, dir_path) for dir_path in system_config['plugins']['driver_dirs']] +
    [os.path.join(project_root, dir_path) for dir_path in system_config['plugins']['countermeasure_dirs']]
)
plugin_manager.load_plugins()

rule_manager = RuleManager(system_config['database']['path'])  # 从配置文件传递数据库路径
rule_engine = RuleEngine(system_config['logging']['audit_log_file']) # 从配置文件传递审计日志路径

# 注册所有可用的反制动作插件
for name in plugin_manager.countermeasures.keys():
    cm_plugin = plugin_manager.get_countermeasure(name)
    if cm_plugin:
        rule_engine.register_countermeasure(name, cm_plugin)

# 加载规则 (从配置文件中指定的路径加载规则文件)
rules = rule_manager.get_all_rules()
rule_engine.load_rules(rules)

# --- API 资源定义 (使用 Flask-RESTX) ---

@ns_env.route('/')
class EnvironmentList(Resource):
    @ns_env.doc('list_environments')
    @ns_env.marshal_list_with(environment_list_model)
    def get(self):
        '''列出所有环境'''
        # 从数据库获取所有环境列表
        # 注意：数据库返回的 env 字典已经包含了 'id', 'driver', 'config' 字段
        # 这与 environment_list_model 是兼容的
        db_environments = rule_manager.list_environments()
        return db_environments

    @ns_env.doc('create_environment')
    @ns_env.expect(environment_input_model, validate=True)
    @ns_env.marshal_with(environment_output_model, code=201)
    @ns_env.response(400, 'Invalid JSON', error_model)
    @ns_env.response(409, 'Environment already exists', error_model)
    @ns_env.response(500, 'Driver not available or creation failed', error_model)
    def post(self):
        '''创建新环境'''
        data = api.payload
        if not data:
            api.abort(400, 'Invalid JSON')

        # 根据环境类型选择驱动
        env_type = data.get('type', 'http')
        driver_name = f"{env_type}_honeypot" if env_type != 'http' else 'http_honeypot'
        
        # 如果指定类型的驱动不存在，尝试使用默认的 HTTP 驱动
        driver = plugin_manager.get_driver(driver_name)
        if not driver:
            driver = plugin_manager.get_driver('http_honeypot')
            if not driver:
                api.abort(500, f"Driver for type '{env_type}' not available")

        # 生成或使用提供的环境 ID
        # 这里我们让驱动来生成 env_id，以保持与之前逻辑的一致性
        # 如果驱动需要特定的 env_id 生成方式，它应该在 create_environment 中处理
        env_id = driver.create_environment(data)
        
        # 将环境信息存储到数据库
        # 注意：我们传递 env_id, driver_name, 和原始 data 到 rule_manager
        success = rule_manager.create_environment(env_id, driver_name, data)
        if not success:
            # 如果数据库中已存在同名环境，返回 409 Conflict
            api.abort(409, f"Environment with ID '{env_id}' already exists")
            
        # 注意：驱动可能也管理了自己的环境状态（如 Docker 容器）
        # 这里我们假设驱动的 create_environment 已经处理了它需要做的事情
        # 我们只需要确保数据库中有记录即可

        return {'id': env_id, 'message': 'Environment created'}, 201

@ns_env.route('/<string:env_id>')
@ns_env.param('env_id', '环境 ID')
class Environment(Resource):
    @ns_env.doc('get_environment')
    @ns_env.marshal_with(environment_detail_model)
    @ns_env.response(404, 'Environment not found', error_model)
    def get(self, env_id):
        '''获取环境详情'''
        # 从数据库获取环境信息
        env = rule_manager.get_environment(env_id)
        if not env:
            api.abort(404, 'Environment not found in database')
        
        # 从驱动获取环境状态
        driver_name = env['driver']
        driver = plugin_manager.get_driver(driver_name)
        if driver:
            env['status'] = driver.get_environment_status(env_id)
        else:
            env['status'] = 'unknown'
            
        return env

    @ns_env.doc('delete_environment')
    @ns_env.response(204, 'Environment deleted')
    @ns_env.response(404, 'Environment not found', error_model)
    @ns_env.response(500, 'Driver not available or deletion failed', error_model)
    def delete(self, env_id):
        '''删除环境'''
        # 1. 从数据库获取环境信息，以获取 driver_name
        env = rule_manager.get_environment(env_id)
        if not env:
            api.abort(404, 'Environment not found in database')

        driver_name = env['driver']
        driver = plugin_manager.get_driver(driver_name)
        if not driver:
            # 即使驱动不可用，我们仍然尝试从数据库删除环境记录
            # 但这可能意味着驱动层的状态不一致
            logger.warning(f"Driver {driver_name} not available for environment {env_id} during deletion")
            # 可以选择 abort 或继续删除数据库记录
            # 这里选择继续，但记录警告
            # api.abort(500, f'Driver {driver_name} not available')

        # 2. 调用驱动的删除方法（如果需要）
        # 注意：这取决于驱动的具体实现。
        # 有些驱动可能没有单独的 delete_environment 方法，
        # 或者删除操作可能已经在 rule_manager.delete_environment 中通过 CASCADE 完成
        # 这里假设驱动有 delete_environment 方法
        driver_delete_success = True
        if hasattr(driver, 'delete_environment'):
            driver_delete_success = driver.delete_environment(env_id)
        
        # 3. 从数据库删除环境记录
        # rule_manager.delete_environment 会同时删除环境和其关联的规则（通过 CASCADE）
        db_delete_success = rule_manager.delete_environment(env_id)

        if driver_delete_success and db_delete_success:
            return '', 204
        else:
            # 如果任一删除操作失败，则返回 500
            # 可以根据具体情况提供更详细的错误信息
            api.abort(500, 'Failed to delete environment')

@ns_ctrl.route('/<string:env_id>/start')
@ns_ctrl.param('env_id', '环境 ID')
class EnvironmentStart(Resource):
    @ns_ctrl.doc('start_environment')
    @ns_ctrl.response(200, 'Environment started', message_model)
    @ns_ctrl.response(404, 'Environment not found', error_model)
    @ns_ctrl.response(500, 'Driver not available or start failed', error_model)
    def post(self, env_id):
        '''启动环境'''
        # 从数据库获取环境信息
        env = rule_manager.get_environment(env_id)
        if not env:
            api.abort(404, 'Environment not found in database')

        driver_name = env['driver']
        driver = plugin_manager.get_driver(driver_name)
        if not driver:
            api.abort(500, f'Driver {driver_name} not available')

        print(env_id)
        success = driver.start_environment(env_id)
        print(success)
        if success:
            return {'message': 'Environment started'}, 200
        else:
            api.abort(500, 'Failed to start environment')

@ns_ctrl.route('/<string:env_id>/stop')
@ns_ctrl.param('env_id', '环境 ID')
class EnvironmentStop(Resource):
    @ns_ctrl.doc('stop_environment')
    @ns_ctrl.response(200, 'Environment stopped', message_model)
    @ns_ctrl.response(404, 'Environment not found', error_model)
    @ns_ctrl.response(500, 'Driver not available or stop failed', error_model)
    def post(self, env_id):
        '''停止环境'''
        # 从数据库获取环境信息
        env = rule_manager.get_environment(env_id)
        if not env:
            api.abort(404, 'Environment not found in database')

        driver_name = env['driver']
        driver = plugin_manager.get_driver(driver_name)
        if not driver:
            api.abort(500, f'Driver {driver_name} not available')

        success = driver.stop_environment(env_id)
        if success:
            return {'message': 'Environment stopped'}, 200
        else:
            api.abort(500, 'Failed to stop environment')

@ns_cm.route('/<string:env_id>/countermeasures')
@ns_cm.param('env_id', '环境 ID')
class ConfigureCountermeasures(Resource):
    @ns_cm.doc('configure_countermeasures')
    @ns_cm.expect(configure_countermeasures_input_model, validate=True)
    @ns_cm.response(201, 'Countermeasures configured', message_model)
    @ns_cm.response(400, 'Invalid payload or rule validation failed', error_model)
    @ns_cm.response(404, 'Environment not found', error_model)
    @ns_cm.response(500, 'Failed to configure countermeasures', error_model)
    def post(self, env_id):
        '''为环境配置反制措施'''
        # 1. 验证环境是否存在 (在数据库中)
        env = rule_manager.get_environment(env_id)
        if not env:
            api.abort(404, 'Environment not found in database')
        
        # 2. 获取请求体数据
        data = api.payload
        rule_ids = data.get('rule_ids', [])
        rules = data.get('rules', [])
        
        # 3. 验证并处理规则
        environment_rules = []
        
        # 优先处理直接传递的规则列表
        if rules:
            # 简单验证：检查是否为列表且列表项为字典
            if not isinstance(rules, list) or not all(isinstance(r, dict) for r in rules):
                api.abort(400, 'Invalid rules format: rules must be a list of rule objects')
            # 假设传递的规则是有效的（在实际应用中可能需要更严格的验证）
            environment_rules = rules
        elif rule_ids:
            # 处理规则ID列表，需要从全局规则中查找
            all_global_rules = rule_manager.get_all_rules()
            global_rule_dict = {rule.get('id'): rule for rule in all_global_rules}
            
            for rule_id in rule_ids:
                if rule_id in global_rule_dict:
                    environment_rules.append(global_rule_dict[rule_id])
                else:
                    # 如果有无效的规则ID，可以选择忽略或返回错误
                    # 这里选择返回错误
                    api.abort(400, f'Rule ID \'{rule_id}\' not found in global rules')
        else:
            # 如果既没有 rules 也没有 rule_ids，则清空环境规则（使用默认全局规则）
            # environment_rules 保持为空列表
            pass # 占位符，确保缩进正确
            
        # 4. 将规则关联到环境 (存储到数据库)
        # 调用 rule_manager 的新方法 set_environment_rules
        success = rule_manager.set_environment_rules(env_id, environment_rules)
        
        if success:
            # 5. 返回成功响应
            return {'message': f'Countermeasures configured for environment {env_id}'}, 201
        else:
            api.abort(500, 'Failed to configure countermeasures in database')

# --- 反制动作管理 ---
@ns_cm.route('/descriptions')
class CountermeasureDescriptions(Resource):
    @ns_cm.doc('list_countermeasure_descriptions')
    @ns_cm.marshal_list_with(countermeasure_description_model)
    def get(self):
        '''列出所有可用反制动作的描述'''
        descriptions = []
        for name, cm_class in plugin_manager.countermeasures.items():
            try:
                # 实例化插件以获取描述
                cm_instance = cm_class()
                description = cm_instance.get_description()
                descriptions.append({'name': name, 'description': description})
            except Exception as e:
                logger.error(f"Failed to get description for countermeasure '{name}': {e}")
                descriptions.append({'name': name, 'description': 'Description unavailable'})
        return descriptions

# --- 文件管理 ---
@ns_files.route('/certificates')
class CertificateUpload(Resource):
    @ns_files.doc('upload_certificate')
    @ns_files.expect(ns_files.parser().add_argument('file', location='files', type='file', required=True, help='证书文件'))
    @ns_files.marshal_with(file_upload_model, code=201)
    @ns_files.response(400, 'No file selected or invalid file type', error_model)
    def post(self):
        '''上传SSL证书文件'''
        # 检查是否有文件被上传
        if 'file' not in request.files:
            api.abort(400, 'No file part')
        
        file = request.files['file']
        
        # 检查文件名是否为空
        if file.filename == '':
            api.abort(400, 'No selected file')
            
        # 检查文件类型是否被允许
        if file and allowed_file(file.filename):
            # 使用 secure_filename 确保文件名安全
            filename = secure_filename(file.filename)
            # 为了防止文件名冲突，可以添加时间戳或UUID前缀
            unique_filename = f"{uuid.uuid4().hex}_{filename}"
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
            
            try:
                file.save(filepath)
                logger.info(f"Certificate file uploaded successfully: {filepath}")
                return {
                    'message': 'File uploaded successfully',
                    'filepath': filepath
                }, 201
            except Exception as e:
                logger.error(f"Failed to save uploaded file: {e}")
                api.abort(500, 'Failed to save file')
        else:
            api.abort(400, 'Invalid file type. Allowed types: pem, crt, key, cert')

# --- 模拟蜜罐服务入口 ---
# 注意：这个路由会捕获所有未被 API 路径匹配的请求。
# Flask 的路由匹配是按注册顺序进行的，因此需要确保它在所有 API 路由之后注册。
# 由于 Flask-RESTX API 是在 `api = Api(app, ...)` 时注册的，
# 我们需要将这些通用路由放在 Flask-RESTX 初始化之后。
# 在实际部署中，可能需要更精细的路由规划或使用蓝图来避免潜在冲突。

@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE'])
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def honeypot_service(path):
    # 构建请求上下文
    request_context = {
        'protocol': 'http',
        'method': request.method,
        'path': request.path,
        'headers': dict(request.headers),
        'ip': request.remote_addr,
        'args': request.args.to_dict(),
        'form': request.form.to_dict(),
        'data': request.get_data(as_text=True)
    }
    
    # 简化处理：选择一个环境ID
    # 从数据库获取所有环境ID，使用第一个
    # 如果数据库中没有环境，则使用 'default'
    db_environments = rule_manager.list_environments()
    environment_id = db_environments[0]['id'] if db_environments else 'default'
    
    # 如果数据库中确实没有环境，使用 'default' 作为 environment_id
    # 并使用全局规则
    if environment_id == 'default':
        rules_to_use = rule_manager.get_all_rules()
    else:
        # 确定要使用的规则列表
        # 1. 尝试从数据库获取与该环境关联的特定规则
        environment_rules = rule_manager.get_environment_rules(environment_id)
        
        # 2. 如果环境没有配置特定规则，则使用全局规则作为后备
        rules_to_use = environment_rules if environment_rules else rule_manager.get_all_rules()
    
    # 由规则引擎评估并执行反制动作
    # 使用新的 evaluate_and_execute_for_environment 方法
    results = rule_engine.evaluate_and_execute_for_environment(request_context, rules_to_use, environment_id)
    
    # --- 处理内容注入反制 ---
    inject_info = None
    for result in results:
        if result.get('action') == 'inject_content' and result.get('result', {}).get('success'):
            inject_info = result['result'].get('inject_info')
            break
    
    # --- 构造响应 ---
    # 默认 HTML 响应内容
    default_html_content = """"""
    
    response_content = default_html_content
    
    # 如果有内容需要注入
    if inject_info:
        content_to_inject = inject_info['content']
        location = inject_info['location']
        
        if location == 'before_body_end':
            # 在 </body> 标签前注入
            response_content = default_html_content.replace('</body>', f'{content_to_inject}\n</body>', 1)
        elif location == 'head':
            # 在 </head> 标签前注入
            response_content = default_html_content.replace('</head>', f'{content_to_inject}\n</head>', 1)
        elif location == 'body_start':
            # 在 <body> 标签后注入
            response_content = default_html_content.replace('<body>', f'<body>\n{content_to_inject}', 1)
        else:
            # 如果位置不支持，追加到内容末尾
            response_content += content_to_inject
            
    # 总是返回 HTML
    response = app.response_class(
        response=response_content,
        status=200,
        mimetype='text/html'
    )
    return response

if __name__ == '__main__':
    app.run(
        host=system_config['server']['host'], 
        port=system_config['server']['port'], 
        debug=False
    )