"""Operator to render a depth map."""
import logging
import os
import shutil

import bpy

from ..utils import upload_file

log = logging.getLogger("comfyui_blender")


class ComfyBlenderOperatorRenderDepthMap(bpy.types.Operator):
    """Operator to render a depth map."""

    bl_idname = "comfy.render_depth_map"
    bl_label = "Render Depth Map"
    bl_description = "Render a depth map from the camera and upload it to the ComfyUI server."

    workflow_property: bpy.props.StringProperty(name="Workflow Property")
    temp_filename = "blender_depth_map"

    def reset_scene(self, context, **kwargs):
        """Reset the scene to its initial state."""

        # Restore original render settings
        scene = context.scene
        scene.render.filepath = kwargs["original_filepath"]
        scene.render.image_settings.file_format = kwargs["original_file_format"]
        scene.render.image_settings.color_mode = kwargs["original_color_mode"]
        scene.render.image_settings.color_depth = kwargs["original_color_depth"]
        scene.render.image_settings.compression = kwargs["original_compression"]
        scene.display_settings.display_device = kwargs["original_display_device"]
        scene.view_settings.view_transform = kwargs["original_view_transform"]

        # Remove temporary files
        if os.path.exists(kwargs["extra_filepath"]):
            os.remove(kwargs["extra_filepath"])
        if os.path.exists(kwargs["temp_filepath"]):
            os.remove(kwargs["temp_filepath"])

    def execute(self, context):
        """Execute the operator."""

        scene = context.scene
        if not scene.camera:
            error_message = "No camera found"
            log.error(error_message)
            bpy.ops.comfy.show_error_popup("INVOKE_DEFAULT", error_message=error_message)
            return {'CANCELLED'}

        # Build temp file paths
        addon_prefs = context.preferences.addons["comfyui_blender"].preferences
        temp_folder = str(addon_prefs.temp_folder)
        extra_filepath = os.path.join(temp_folder, "tmp.png")  # Extraneous file generated by Blender renderer

        # Initialize scene reset settings
        reset_params = {}
        reset_params["extra_filepath"] = extra_filepath
        reset_params["original_filepath"] = scene.render.filepath
        reset_params["original_file_format"] = scene.render.image_settings.file_format
        reset_params["original_color_mode"] = scene.render.image_settings.color_mode
        reset_params["original_color_depth"] = scene.render.image_settings.color_depth
        reset_params["original_compression"] = scene.render.image_settings.compression
        reset_params["original_display_device"] = scene.display_settings.display_device
        reset_params["original_view_transform"] = scene.view_settings.view_transform

        # Set up the scene for rendering
        scene.render.filepath = extra_filepath
        scene.render.image_settings.file_format = "PNG"
        scene.render.image_settings.color_mode = "RGB"
        scene.render.image_settings.color_depth = "16"
        scene.render.image_settings.compression = 0
        scene.display_settings.display_device = "Display P3"
        scene.view_settings.view_transform = "Raw"

        # Enable Z pass
        scene.view_layers["ViewLayer"].use_pass_z = True

        # Create a new node tree for compositing
        scene.use_nodes = True
        tree = scene.node_tree
        tree.nodes.clear()

        # Create nodes
        rlayers_node = tree.nodes.new(type="CompositorNodeRLayers")
        map_range_node = tree.nodes.new(type="CompositorNodeMapRange")
        output_file_node = tree.nodes.new(type="CompositorNodeOutputFile")
        output_file_node.base_path = temp_folder
        output_file_node.file_slots[0].path = self.temp_filename
        output_file_node.file_slots[0].format.file_format = "PNG"

        # Link nodes
        tree.links.new(rlayers_node.outputs[2], map_range_node.inputs[0])  # From output socket Depth to input socket Value
        tree.links.new(map_range_node.outputs[0], output_file_node.inputs[0])  # From output socket Value to input socket Image

        # Get closest and furthest vertices from the camera
        cam_location = scene.camera.matrix_world.translation
        min_distance = float('inf')
        max_distance = 0.0

        for obj in scene.objects:
            if obj.type == "MESH":
                for vertex in obj.data.vertices:
                    world_coord = obj.matrix_world @ vertex.co
                    distance = (cam_location - world_coord).length
                    if distance < min_distance:
                        min_distance = distance
                    if distance > max_distance:
                        max_distance = distance
        
        # Update Map Range node
        map_range_node.inputs[1].default_value = min_distance  # From Min
        map_range_node.inputs[2].default_value = max_distance  # From Max
        map_range_node.inputs[3].default_value = 1 # To Min
        map_range_node.inputs[4].default_value = 0 # To Max

        # Render the scene
        bpy.ops.render.render(write_still=True)

        # Get the rendered filename and path based on current frame
        current_frame = scene.frame_current
        temp_filename = f"{self.temp_filename}{current_frame:04d}.png"  # Blender uses 4-digit zero-padded frame numbers
        temp_filepath = os.path.join(temp_folder, temp_filename)
        reset_params["temp_filepath"] = temp_filepath  # Add the temp filepath to the reset param to delete it later

        # Upload file on ComfyUI server
        try:
            response = upload_file(temp_filepath, type="image")
        except Exception as e:
            # Reset the scene to initial state
            self.reset_scene(context, **reset_params)
            error_message = f"Failed to upload file to ComfyUI server: {addon_prefs.server_address}. {e}"
            log.exception(error_message)
            bpy.ops.comfy.show_error_popup("INVOKE_DEFAULT", error_message=error_message)
            return {'CANCELLED'}

        if response.status_code != 200:
            # Reset the scene to initial state
            self.reset_scene(context, **reset_params)
            error_message = f"Failed to upload file: {response.status_code} - {response.text}"
            log.error(error_message)
            bpy.ops.comfy.show_error_popup("INVOKE_DEFAULT", error_message=error_message)
            return {'CANCELLED'}

        # Delete the previous input image from Blender's data
        current_workflow = scene.current_workflow
        previous_input = getattr(current_workflow, self.workflow_property)
        if bpy.data.images.get(previous_input):
            image = bpy.data.images.get(previous_input)
            bpy.data.images.remove(image)

        # Build input file paths
        inputs_folder = str(addon_prefs.inputs_folder)
        input_subfolder = response.json()["subfolder"]
        input_filename = response.json()["name"]
        input_filepath = os.path.join(inputs_folder, input_subfolder, input_filename)

        # Create the input subfolder if it doesn't exist
        os.makedirs(os.path.join(inputs_folder, input_subfolder), exist_ok=True)

        try:
            # Copy the file to the inputs folder
            shutil.copy(temp_filepath, input_filepath)
            self.report({'INFO'}, f"Input file copied to: {input_filepath}")
        except shutil.SameFileError as e:
            self.report({'INFO'}, f"Input file is already in the inputs folder: {input_filepath}")
        except Exception as e:
            # Reset the scene to initial state
            self.reset_scene(context, **reset_params)
            error_message = f"Failed to copy input file: {e}"
            log.exception(error_message)
            bpy.ops.comfy.show_error_popup("INVOKE_DEFAULT", error_message=error_message)
            return {'CANCELLED'}

        # Load image in the data block
        image = bpy.data.images.load(input_filepath, check_existing=True)

        # Update the workflow property with the image name from the data block
        current_workflow[self.workflow_property] = image.name

        # Reset the scene to initial state
        self.reset_scene(context, **reset_params)
        return {'FINISHED'}


def register():
    """Register the operator."""

    bpy.utils.register_class(ComfyBlenderOperatorRenderDepthMap)


def unregister():
    """Unregister the operator."""

    bpy.utils.unregister_class(ComfyBlenderOperatorRenderDepthMap)
