"""Operator to render a lineart."""
import logging
import os
import shutil
from mathutils import Vector

import bpy

from ..utils import upload_file

log = logging.getLogger("comfyui_blender")


class ComfyBlenderOperatorRenderLineart(bpy.types.Operator):
    """Operator to render a lineart."""

    bl_idname = "comfy.render_lineart"
    bl_label = "Render Lineart"
    bl_description = "Render a lineart from the camera and upload it to the ComfyUI server."

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

    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"]

        # Delete grease pencil object
        bpy.data.objects.remove(kwargs["gpencil"], do_unlink=True)

        # 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 context.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 grease pencil pass
        scene.view_layers["ViewLayer"].use_pass_grease_pencil = 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")
        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], output_file_node.inputs[0])  # From output socket GreasePencil to input socket Image

        # Calculate position behind the camera
        camera_location = scene.camera.location.copy()
        camera_backward = scene.camera.matrix_world.to_quaternion() @ Vector((0, 0, 1))  # Camera's backward direction
        gpencil_location = camera_location + camera_backward * 5  # 5 units behind camera

        # Add a new grease pencil object
        bpy.ops.object.grease_pencil_add(type="STROKE", align="WORLD", location=gpencil_location, scale=(1, 1, 1))
        gpencil = context.object
        white_material = bpy.data.materials["White"]
        gpencil.data.materials[0] = white_material
        reset_params["gpencil"] = gpencil  # Add the grease pencil to the reset param to delete it later

        # Add Lineart modifier
        bpy.ops.object.modifier_add(type="LINEART")
        lineart_modifier = context.object.modifiers["Lineart"]
        lineart_modifier.source_type = "SCENE"
        lineart_modifier.target_layer = "Color"
        lineart_modifier.target_material = white_material
        lineart_modifier.radius = 0.015

        # 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(ComfyBlenderOperatorRenderLineart)


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

    bpy.utils.unregister_class(ComfyBlenderOperatorRenderLineart)
