#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Blender渲染服务
处理Blender渲染逻辑，适配Linux服务器环境
"""

import os
import subprocess
import asyncio
import tempfile
import shutil
import math
import time
from typing import Optional, Tuple
import logging

from models import RenderRequest

logger = logging.getLogger(__name__)


class BlenderRenderService:
    """Blender渲染服务类"""

    def __init__(self, blender_path: str = None):
        """
        初始化Blender服务

        Args:
            blender_path: Blender可执行文件路径，如果为None则自动检测
        """
        self.blender_path = blender_path or self._find_blender_path()
        self.output_dir = "/data/Isometquick/"
        self._ensure_output_dir()

    def _find_blender_path(self) -> str:
        """自动查找Blender路径"""
        # Linux常见路径
        possible_paths = [
            "/data/blender/blender-4.2.11-linux-x64/blender",
            "blender"  # 系统PATH中
        ]

        for path in possible_paths:
            if shutil.which(path):
                logger.info(f"找到Blender: {path}")
                return path

        # 如果都找不到，使用默认值
        logger.warning("未找到Blender，使用默认路径")
        return "blender"

    def _ensure_output_dir(self):
        """确保输出目录存在"""
        try:
            os.makedirs(self.output_dir, exist_ok=True)
            logger.info(f"输出目录设置为: {self.output_dir}")
        except PermissionError as e:
            logger.error(f"无法创建输出目录 {self.output_dir}: 权限不足")
            raise
        except Exception as e:
            logger.error(f"创建输出目录失败 {self.output_dir}: {e}")
            raise

    def check_blender_available(self) -> bool:
        """检查Blender是否可用"""
        try:
            result = subprocess.run(
                [self.blender_path, "--version"],
                capture_output=True,
                text=True,
                timeout=10
            )
            return result.returncode == 0
        except Exception as e:
            logger.error(f"Blender检查失败: {e}")
            return False

    def _calculate_room_parameters(self, request: RenderRequest) -> Tuple[float, float, float]:
        """
        计算房间参数：floor_scale_value, x_extrude_value, y_extrude_value

        Returns:
            (floor_scale_value, x_extrude_value, y_extrude_value)
        """
        room_length = request.room.length  # Y轴（长度）
        room_width = request.room.width    # X轴（宽度）

        # 判断长宽是否相等
        if room_length == room_width:
            # 长宽相等，按原逻辑
            floor_scale_value = room_width  # 使用宽度作为基准
            x_extrude_value = 0.0
            y_extrude_value = 0.0
        else:
            # 长宽不等，需要调整
            if room_length > room_width:
                # 长比宽大（Y轴比X轴大）
                floor_scale_value = room_width  # 使用较小的值（宽度）
                x_extrude_value = 0.0
                y_extrude_value = room_length - room_width  # Y轴延伸
            else:
                # 宽比长大（X轴比Y轴大）
                floor_scale_value = room_length  # 使用较小的值（长度）
                x_extrude_value = room_width - room_length  # X轴延伸
                y_extrude_value = 0.0

        logger.info(
            f"房间参数计算: floor_scale={floor_scale_value}, x_extrude={x_extrude_value}, y_extrude={y_extrude_value}")
        return floor_scale_value, x_extrude_value, y_extrude_value

    def _calculate_camera_position(self, request: RenderRequest) -> tuple:
        """
        根据房间尺寸和视图类型计算摄像机位置

        Returns:
            (x, y, z) 摄像机位置坐标
        """
        room_length = request.room.length  # Y轴（长度）
        room_width = request.room.width    # X轴（宽度）
        camera_height = request.camera.height  # Z轴

        # 判断长宽是否相等
        if room_length == room_width:
            # 长宽相等，使用原来的逻辑
            if request.camera.view_type == 1:
                # 正视图: x=0, y=-(房间的长度/2-1), z=摄像机垂直高度
                x = 0
                y = -(room_length / 2 - 1)
                z = camera_height
            else:
                # 侧视图: x=(房间的宽度/2-1), y=-(房间的长度/2-1), z=摄像机垂直高度
                x = room_width / 2 - 1
                y = -(room_length / 2 - 1)
                z = camera_height
        else:
            # 长宽不等，使用新的逻辑
            if room_length > room_width:
                # 长比宽大（Y轴比X轴大）
                if request.camera.view_type == 1:
                    # 正视图: x=0, y=-(房间的长度-(房间的宽度/2)-1), z=摄像机垂直高度
                    x = 0
                    y = -(room_length - (room_width / 2) - 1)
                    z = camera_height
                else:
                    # 侧视图: x=(房间的宽度/2-1), y=-(房间的长度-(房间的宽度/2)-1), z=摄像机垂直高度
                    x = room_width / 2 - 1
                    y = -(room_length - (room_width / 2) - 1)
                    z = camera_height
            else:
                # 宽比长大（X轴比Y轴大）
                if request.camera.view_type == 1:
                    # 正视图: x=(房间的宽度-房间的长度)/2, y=-(房间的长度/2-1), z=摄像机垂直高度
                    x = (room_width - room_length) / 2
                    y = -(room_length / 2 - 1)
                    z = camera_height
                else:
                    # 侧视图: x=(房间的宽度-(房间的长度/2)-1), y=-(房间的长度/2-1), z=摄像机垂直高度
                    x = room_width - (room_length / 2) - 1
                    y = -(room_length / 2 - 1)
                    z = camera_height

        logger.info(f"相机位置计算: x={x}, y={y}, z={z}")
        return (x, y, z)

    def _calculate_camera_rotation(self, request: RenderRequest) -> tuple:
        """
        根据视图类型计算摄像机旋转角度

        Returns:
            (rx, ry, rz) 摄像机旋转角度（弧度）
        """
        if request.camera.view_type == 1:
            # 正视图: 90, 0, 0
            rx = math.radians(90)
            ry = math.radians(0)
            rz = math.radians(0)
        else:
            # 侧视图: 90, 0, 相机旋转角度
            rx = math.radians(90)
            ry = math.radians(0)
            rz = math.radians(request.camera.rotation_angle)

        return (rx, ry, rz)

    def _generate_blender_script(self, task_id: str, request: RenderRequest) -> str:
        """生成Blender Python脚本"""

        # 计算房间参数
        floor_scale_value, x_extrude_value, y_extrude_value = self._calculate_room_parameters(
            request)

        # 计算摄像机位置和旋转
        camera_pos = self._calculate_camera_position(request)
        camera_rot = self._calculate_camera_rotation(request)

        # 设置输出文件路径
        output_file = os.path.join(self.output_dir, f"render_{task_id}.png")

        # 渲染引擎映射
        engine_map = {
            "workbench": "BLENDER_WORKBENCH",
            "eevee": "BLENDER_EEVEE_NEXT",
            "cycles": "CYCLES"
        }
        render_engine = engine_map.get(
            request.render.engine, "BLENDER_WORKBENCH")

        # 计算灯光高度（房间高度减一米）
        light_height = request.room.height - 1.0

        script = f'''
import bpy
import bmesh
import os
import math
import time

def disable_problematic_addons():
    """禁用可能导致问题的插件"""
    try:
        problematic_addons = ['bl_tool']
        for addon in problematic_addons:
            if addon in bpy.context.preferences.addons:
                bpy.ops.preferences.addon_disable(module=addon)
                print(f"已禁用插件: {{addon}}")
    except:
        pass

def create_isometric_room():
    """创建等轴测房间"""
    try:
        disable_problematic_addons()
        
        # 清除默认立方体
        if "Cube" in bpy.data.objects:
            bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True)

        # 设置3D游标位置到原点
        bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)

        # 检查Isometquick插件
        if not hasattr(bpy.context.scene, 'iso_tool'):
            print("错误: Isometquick插件未正确加载")
            return False

        # 获取Isometquick设置
        iso_tool = bpy.context.scene.iso_tool

        # 启用需要的组件
        iso_tool.create_floor = True
        iso_tool.create_left_wall = True  
        iso_tool.create_right_wall = True
        iso_tool.create_left_light = True
        iso_tool.create_right_light = True
        iso_tool.create_hidden_ceiling = True
        iso_tool.create_hidden_leftwall = False
        iso_tool.create_hidden_rightwall = False

        # 找到3D视图区域
        view3d_area = None
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                view3d_area = area
                break
        
        if view3d_area is None:
            print("错误: 找不到3D视图区域")
            return False

        # 临时切换到3D视图上下文
        with bpy.context.temp_override(area=view3d_area):
            # 使用计算出的房间参数创建房间
            print(f"房间参数: floor_scale={floor_scale_value}, x_extrude={x_extrude_value}, y_extrude={y_extrude_value}")
            result = bpy.ops.object.isometric_operator(
                floor_scale_value={floor_scale_value},
                wall_height_value={request.room.height},
                equal_thickness_value=0.15,
                wall_thickness_value=0.0,
                floor_thickness_value=0.0,
                x_extrude_value={x_extrude_value},
                y_extrude_value={y_extrude_value}
            )
            
            if result == {{'FINISHED'}}:
                print("等轴测房间创建完成!")
                fix_visibility()
                return True
            else:
                print("房间创建失败")
                return False
                
    except Exception as e:
        print(f"创建房间时出现错误: {{str(e)}}")
        import traceback
        traceback.print_exc()
        return False

def fix_visibility():
    """修复对象可见性设置"""
    try:
        # 设置光源不可见
        light_objects = ["ISO Emission Left", "ISO Emission Right"]
        for obj_name in light_objects:
            if obj_name in bpy.data.objects:
                obj = bpy.data.objects[obj_name]
                if hasattr(obj, 'visibility_camera'):
                    obj.visibility_camera = False
                elif hasattr(obj, 'cycles_visibility'):
                    obj.cycles_visibility.camera = False
                print(f"已设置 {{obj_name}} 的可见性")
                
        # 设置天花板可见
        if "Hidden Ceiling" in bpy.data.objects:
            ceiling_obj = bpy.data.objects["Hidden Ceiling"]
            if hasattr(ceiling_obj, 'visibility_camera'):
                ceiling_obj.visibility_camera = True
            elif hasattr(ceiling_obj, 'cycles_visibility'):
                ceiling_obj.cycles_visibility.camera = True
            ceiling_obj.hide_viewport = False
            ceiling_obj.hide_render = False
            print("已设置 Hidden Ceiling 为可见")
            
    except Exception as e:
        print(f"设置可见性时出现错误: {{str(e)}}")

def setup_render_settings():
    """设置渲染参数"""
    scene = bpy.context.scene
    
    # 设置渲染引擎为EEVEE_NEXT
    scene.render.engine = 'BLENDER_EEVEE_NEXT'
    print(f"已设置渲染引擎为: BLENDER_EEVEE_NEXT")
    
    # 设置EEVEE参数
    try:
        if hasattr(scene.eevee, 'taa_render_samples'):
            scene.eevee.taa_render_samples = 64
            print(f"已设置EEVEE渲染采样: {{scene.eevee.taa_render_samples}}")
        elif hasattr(scene.eevee, 'taa_samples'):
            scene.eevee.taa_samples = 64
            print(f"已设置EEVEE采样: {{scene.eevee.taa_samples}}")
        else:
            print("警告: 无法找到EEVEE采样设置，使用默认值")
            
        # 启用屏幕空间反射
        if hasattr(scene.eevee, 'use_ssr'):
            scene.eevee.use_ssr = True
            scene.eevee.use_ssr_refraction = True
            print("已启用屏幕空间反射")
            
        # 启用环境光遮蔽
        if hasattr(scene.eevee, 'use_gtao'):
            scene.eevee.use_gtao = True
            print("已启用环境光遮蔽")
            
        # 启用体积雾
        if hasattr(scene.eevee, 'use_volumetric_lights'):
            scene.eevee.use_volumetric_lights = True
            print("已启用体积光照")
            
    except AttributeError as e:
        print(f"EEVEE设置警告: {{e}}")
        print("使用默认EEVEE渲染设置")
    
    # 设置分辨率
    scene.render.resolution_x = {request.render.resolution_x}
    scene.render.resolution_y = {request.render.resolution_y}
    scene.render.resolution_percentage = 100
    
    # 设置输出格式
    scene.render.image_settings.file_format = 'PNG'
    scene.render.image_settings.color_mode = 'RGBA'
    
    # 设置输出路径
    scene.render.filepath = "{output_file}"
    
    print(f"渲染设置完成，输出路径: {{scene.render.filepath}}")
    print(f"分辨率: {{scene.render.resolution_x}}x{{scene.render.resolution_y}}")
    return scene.render.filepath

def setup_camera_and_lighting():
    """设置摄像机和照明（180W点光源，高度为房间高度减一米）"""
    # 计算灯光高度（房间高度减一米）
    room_height = {request.room.height}
    light_height = room_height - 1.0
    
    # 设置摄像机
    camera = None
    if "Camera" in bpy.data.objects:
        camera = bpy.data.objects["Camera"]
    else:
        bpy.ops.object.camera_add(location={camera_pos})
        camera = bpy.context.active_object
        camera.name = "API Camera"
    
    # 设置摄像机位置和旋转
    camera.location = {camera_pos}
    camera.rotation_euler = {camera_rot}

    # 设置为透视投影
    camera.data.type = 'PERSP'
    camera.data.lens = 35.0
    camera.data.sensor_width = 35.0
    camera.data.sensor_fit = 'AUTO'
    
    # 设置为场景的活动摄像机
    bpy.context.scene.camera = camera
    print("摄像机设置完成")
    
    # 隐藏ISO Emission灯光（如果存在）
    light_objects = ["ISO Emission Left", "ISO Emission Right"]
    for obj_name in light_objects:
        if obj_name in bpy.data.objects:
            light_obj = bpy.data.objects[obj_name]
            # 隐藏发光对象
            light_obj.hide_render = True
            light_obj.hide_viewport = True
            print(f"已隐藏 {{obj_name}}")
    
    # 创建单个180W点光源
    try:
        # 计算点光源位置（房间中心上方）
        room_length = {request.room.length}  # Y轴
        room_width = {request.room.width}    # X轴
        
        # 点光源位置：房间中心上方
        light_x = 0  # X轴中心
        light_y = 0  # Y轴中心
        light_z = light_height
        
        # 创建点光源
        bpy.ops.object.light_add(type='POINT', location=(light_x, light_y, light_z))
        point_light = bpy.context.active_object
        point_light.name = "Main Point Light"
        point_light.data.energy = 180  # 180W
        
        # 设置软衰减
        point_light.data.falloff_type = 'INVERSE_SQUARE'  # 平方反比衰减（物理准确）
        
        # 设置阴影
        point_light.data.use_shadow = True  # 启用阴影
        point_light.data.shadow_soft_size = 0.5  # 0.5米半径，产生柔和阴影
        
        # EEVEE渲染器的阴影设置
        if hasattr(point_light.data, 'shadow_buffer_size'):
            point_light.data.shadow_buffer_size = 2048  # 阴影贴图分辨率
        
        # Cycles渲染器的阴影设置
        if hasattr(point_light.data, 'cycles'):
            point_light.data.cycles.cast_shadow = True  # Cycles中启用阴影投射
            point_light.data.cycles.use_multiple_importance_sampling = True  # 启用重要性采样
        
        # 设置衰减距离（可选）
        # point_light.data.cutoff_distance = 15.0  # 15米后完全衰减
        # point_light.data.use_custom_distance = True
        
        print(f"已创建180W点光源（软衰减+阴影）")
        print(f"点光源位置: x={{light_x}}, y={{light_y}}, z={{light_z}}")
        print("总照明功率: 180W")
        print("灯光类型: 点光源（软衰减+阴影）")
        print(f"衰减类型: 平方反比衰减")
        print(f"阴影设置: 启用，柔和阴影半径0.5米")
        print(f"灯光高度: {{light_height}}米 (房间高度减一米)")
        
    except Exception as e:
        print(f"创建点光源时出错: {{e}}")
        print("将使用默认的ISO Emission灯光")
    
    print("照明设置完成")

def render_scene():
    """渲染场景"""
    print("开始渲染...")
    bpy.ops.render.render(write_still=True)
    print(f"渲染完成!")
    return bpy.context.scene.render.filepath

def main():
    """主函数"""
    print("=" * 60)
    print("开始API渲染任务")
    print("=" * 60)
    
    try:
        # 1. 创建房间
        print("1. 创建等轴测房间...")
        if not create_isometric_room():
            print("房间创建失败，停止执行")
            return False
        
        # 2. 设置渲染参数
        print("2. 设置渲染参数...")
        output_path = setup_render_settings()
        
        # 3. 设置摄像机和照明
        print("3. 设置摄像机和照明...")
        setup_camera_and_lighting()
        
        # 4. 渲染场景
        print("4. 开始渲染...")
        final_path = render_scene()
        
        print("=" * 60)
        print("API渲染任务完成!")
        print(f"输出文件: {{final_path}}")
        print("=" * 60)
        
        return True
        
    except Exception as e:
        print(f"执行过程中出现错误: {{str(e)}}")
        import traceback
        traceback.print_exc()
        return False

# 执行主函数
if __name__ == "__main__":
    main()
'''

        return script.strip(), output_file

    async def render_room(self, task_id: str, request: RenderRequest) -> str:
        """
        执行房间渲染

        Args:
            task_id: 任务ID
            request: 渲染请求参数

        Returns:
            输出文件路径
        """
        try:
            # 生成Blender脚本
            script_content, output_file = self._generate_blender_script(
                task_id, request)

            # 创建临时脚本文件
            with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
                f.write(script_content)
                script_path = f.name

            try:
                # 构建Blender命令
                cmd = [
                    self.blender_path,
                    "--background",
                    "--disable-crash-handler",
                    "--python", script_path
                ]

                logger.info(f"执行Blender命令: {' '.join(cmd)}")

                # 执行Blender渲染
                process = await asyncio.create_subprocess_exec(
                    *cmd,
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE
                )

                stdout, stderr = await asyncio.wait_for(
                    process.communicate(),
                    timeout=300  # 5分钟超时
                )

                # 检查执行结果
                if process.returncode == 0:
                    if os.path.exists(output_file):
                        logger.info(f"渲染成功: {output_file}")
                        return output_file
                    else:
                        raise Exception("渲染完成但输出文件不存在")
                else:
                    error_msg = stderr.decode('utf-8') if stderr else "未知错误"
                    raise Exception(
                        f"Blender执行失败 (返回码: {process.returncode}): {error_msg}")

            finally:
                # 清理临时脚本文件
                try:
                    os.unlink(script_path)
                except:
                    pass

        except asyncio.TimeoutError:
            raise Exception("渲染超时（5分钟）")
        except Exception as e:
            logger.error(f"渲染失败: {e}")
            raise
