import json
import os
from io import BytesIO
from itertools import chain
from typing import Tuple, Dict, Any

import requests
import torch
from PIL import Image

from comfy.component_model.tensor_types import RGBImageBatch, MaskBatch, ImageBatch
from comfy.nodes.package_typing import CustomNode, Seed31
from comfy.utils import pil2tensor, tensor2pil
from ..constants.resolutions import IDEOGRAM_RESOLUTIONS
from .nodes_mask import MaskToImage

# --- ENUMs and Constants ---

ASPECT_RATIOS = [(10, 6), (16, 10), (9, 16), (3, 2), (4, 3)]
ASPECT_RATIO_ENUM = ["ASPECT_1_1"] + list(chain.from_iterable(
    [f"ASPECT_{a}_{b}", f"ASPECT_{b}_{a}"]
    for a, b in ASPECT_RATIOS
))
# New enum for v3 aspect ratios
ASPECT_RATIO_V3_ENUM = ["disabled", "1x1", "10x16", "9x16", "3x4", "2x3", "16x10", "3x2", "4x3", "16x9"]
V2_MODELS = ["V_2", "V_2_TURBO"]
MODELS_ENUM = V2_MODELS + ["V_3"]
AUTO_PROMPT_ENUM = ["AUTO", "ON", "OFF"]
STYLES_ENUM = ["AUTO", "GENERAL", "REALISTIC", "DESIGN", "RENDER_3D", "ANIME"]
RESOLUTION_ENUM = [f"RESOLUTION_{w}_{h}" for w, h in IDEOGRAM_RESOLUTIONS]
# New enum for v3 rendering speed
RENDERING_SPEED_ENUM = ["DEFAULT", "TURBO", "QUALITY"]

def to_v3_resolution(resolution: str) -> str:
    return resolution[len("RESOLUTION_"):].replace("_", "x")


def api_key_in_env_or_workflow(api_key_from_workflow: str):
    from comfy.cli_args import args
    if api_key_from_workflow is not None and "" != api_key_from_workflow.strip():
        return api_key_from_workflow
    return os.environ.get("IDEOGRAM_API_KEY", args.ideogram_api_key)

class IdeogramGenerate(CustomNode):
    @classmethod
    def INPUT_TYPES(cls) -> Dict[str, Any]:
        return {
            "required": {
                "prompt": ("STRING", {"multiline": True}),
                "resolution": (RESOLUTION_ENUM, {"default": RESOLUTION_ENUM[0]}),
                "model": (MODELS_ENUM, {"default": MODELS_ENUM[-1]}),
                "magic_prompt_option": (AUTO_PROMPT_ENUM, {"default": AUTO_PROMPT_ENUM[0]}),
            },
            "optional": {
                "api_key": ("STRING", {"default": ""}),
                "negative_prompt": ("STRING", {"multiline": True}),
                "num_images": ("INT", {"default": 1, "min": 1, "max": 8}),
                "seed": Seed31,
                "style_type": (STYLES_ENUM, {}),
                # New v3 optional args
                "rendering_speed": (RENDERING_SPEED_ENUM, {"default": "DEFAULT"}),
                "aspect_ratio": (ASPECT_RATIO_V3_ENUM, {"default": "disabled"}),
                "style_reference_images": ("IMAGE",),
            }
        }

    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "generate"
    CATEGORY = "ideogram"

    def generate(self, prompt: str, resolution: str, model: str, magic_prompt_option: str,
                 api_key: str = "", negative_prompt: str = "", num_images: int = 1, seed: int = 0, style_type: str = "AUTO",
                 rendering_speed: str = "DEFAULT", aspect_ratio: str = "disabled", style_reference_images: ImageBatch = None) -> Tuple[torch.Tensor]:
        api_key = api_key_in_env_or_workflow(api_key)

        if model in V2_MODELS:
            headers = {"Api-Key": api_key, "Content-Type": "application/json"}
            payload = {
                "image_request": {
                    "prompt": prompt, "resolution": resolution, "model": model,
                    "magic_prompt_option": magic_prompt_option, "num_images": num_images,
                    "style_type": style_type,
                }
            }
            if negative_prompt: payload["image_request"]["negative_prompt"] = negative_prompt
            if seed: payload["image_request"]["seed"] = seed
            response = requests.post("https://api.ideogram.ai/generate", headers=headers, json=payload)

        elif model == "V_3":
            payload = {
                "prompt": prompt, "model": model, "magic_prompt": magic_prompt_option,
                "num_images": num_images, "style_type": style_type, "rendering_speed": rendering_speed,
            }
            if negative_prompt: payload["negative_prompt"] = negative_prompt
            if seed: payload["seed"] = seed

            # Handle resolution vs aspect_ratio (aspect_ratio takes precedence)
            if aspect_ratio != "disabled":
                payload["aspect_ratio"] = aspect_ratio
            else:
                payload["resolution"] = to_v3_resolution(resolution)

            headers = {"Api-Key": api_key}

            # Use multipart/form-data if style references are provided
            if style_reference_images is not None:
                files = []
                for i, style_image in enumerate(style_reference_images):
                    pil_image = tensor2pil(style_image)
                    image_bytes = BytesIO()
                    pil_image.save(image_bytes, format="PNG")
                    files.append(("style_reference_images", (f"style_{i}.png", image_bytes.getvalue(), "image/png")))
                response = requests.post("https://api.ideogram.ai/v1/ideogram-v3/generate", headers=headers, data=payload, files=files)
            else:
                headers["Content-Type"] = "application/json"
                response = requests.post("https://api.ideogram.ai/v1/ideogram-v3/generate", headers=headers, json=payload)
        else:
            raise ValueError(f"Invalid model={model}")

        response.raise_for_status()
        images = []
        for item in response.json()["data"]:
            img_response = requests.get(item["url"])
            img_response.raise_for_status()
            pil_image = Image.open(BytesIO(img_response.content))
            images.append(pil2tensor(pil_image))
        return (torch.cat(images, dim=0),)


class IdeogramEdit(CustomNode):
    @classmethod
    def INPUT_TYPES(cls) -> Dict[str, Any]:
        return {
            "required": {
                "images": ("IMAGE",),
                "masks": ("MASK",),
                "prompt": ("STRING", {"multiline": True}),
                "model": (MODELS_ENUM, {"default": MODELS_ENUM[-1]}),
            },
            "optional": {
                "api_key": ("STRING", {"default": ""}),
                "magic_prompt_option": (AUTO_PROMPT_ENUM, {"default": AUTO_PROMPT_ENUM[0]}),
                "num_images": ("INT", {"default": 1, "min": 1, "max": 8}),
                "seed": Seed31,
                "style_type": (STYLES_ENUM, {}),
                # New v3 optional args
                "rendering_speed": (RENDERING_SPEED_ENUM, {"default": "DEFAULT"}),
                "style_reference_images": ("IMAGE",),
            }
        }

    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "edit"
    CATEGORY = "ideogram"

    def edit(self, images: RGBImageBatch, masks: MaskBatch, prompt: str, model: str,
             api_key: str = "", magic_prompt_option: str = "AUTO", num_images: int = 1, seed: int = 0,
             style_type: str = "AUTO", rendering_speed: str = "DEFAULT", style_reference_images: ImageBatch = None) -> Tuple[torch.Tensor]:
        api_key = api_key_in_env_or_workflow(api_key)
        headers = {"Api-Key": api_key}
        image_responses = []
        for mask_tensor, image_tensor in zip(torch.unbind(masks), torch.unbind(images)):
            mask_tensor, = MaskToImage().mask_to_image(mask=1. - mask_tensor)

            image_pil, mask_pil = tensor2pil(image_tensor), tensor2pil(mask_tensor)
            image_bytes, mask_bytes = BytesIO(), BytesIO()
            image_pil.save(image_bytes, format="PNG")
            mask_pil.save(mask_bytes, format="PNG")

            if model in V2_MODELS:
                files = {"image_file": ("image.png", image_bytes.getvalue()), "mask": ("mask.png", mask_bytes.getvalue())}
                data = {"prompt": prompt, "model": model, "magic_prompt_option": magic_prompt_option, "num_images": num_images, "style_type": style_type}
                if seed: data["seed"] = seed
                response = requests.post("https://api.ideogram.ai/edit", headers=headers, files=files, data=data)

            elif model == "V_3":
                data = {"prompt": prompt, "magic_prompt": magic_prompt_option, "num_images": num_images, "rendering_speed": rendering_speed}
                if seed: data["seed"] = seed

                files_list = [
                    ("image", ("image.png", image_bytes.getvalue(), "image/png")),
                    ("mask", ("mask.png", mask_bytes.getvalue(), "image/png")),
                ]
                if style_reference_images is not None:
                    for i, style_image in enumerate(style_reference_images):
                        pil_ref = tensor2pil(style_image)
                        ref_bytes = BytesIO()
                        pil_ref.save(ref_bytes, format="PNG")
                        files_list.append(("style_reference_images", (f"style_{i}.png", ref_bytes.getvalue(), "image/png")))

                response = requests.post("https://api.ideogram.ai/v1/ideogram-v3/edit", headers=headers, files=files_list, data=data)
            else:
                raise ValueError(f"Invalid model={model}")

            response.raise_for_status()
            for item in response.json()["data"]:
                img_response = requests.get(item["url"])
                img_response.raise_for_status()
                pil_image = Image.open(BytesIO(img_response.content))
                image_responses.append(pil2tensor(pil_image))

        return (torch.cat(image_responses, dim=0),)


class IdeogramRemix(CustomNode):
    @classmethod
    def INPUT_TYPES(cls) -> Dict[str, Any]:
        return {
            "required": {
                "images": ("IMAGE",), "prompt": ("STRING", {"multiline": True}),
                "resolution": (RESOLUTION_ENUM, {"default": RESOLUTION_ENUM[0]}),
                "model": (MODELS_ENUM, {"default": MODELS_ENUM[-1]}),
            },
            "optional": {
                "api_key": ("STRING", {"default": ""}),
                "image_weight": ("INT", {"default": 50, "min": 1, "max": 100}),
                "magic_prompt_option": (AUTO_PROMPT_ENUM, {"default": AUTO_PROMPT_ENUM[0]}),
                "negative_prompt": ("STRING", {"multiline": True}),
                "num_images": ("INT", {"default": 1, "min": 1, "max": 8}),
                "seed": Seed31,
                "style_type": (STYLES_ENUM, {}),
                # New v3 optional args
                "rendering_speed": (RENDERING_SPEED_ENUM, {"default": "DEFAULT"}),
                "aspect_ratio": (ASPECT_RATIO_V3_ENUM, {"default": "disabled"}),
                "style_reference_images": ("IMAGE",),
            }
        }

    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "remix"
    CATEGORY = "ideogram"

    def remix(self, images: torch.Tensor, prompt: str, resolution: str, model: str,
              api_key: str = "", image_weight: int = 50, magic_prompt_option: str = "AUTO",
              negative_prompt: str = "", num_images: int = 1, seed: int = 0, style_type: str = "AUTO",
              rendering_speed: str = "DEFAULT", aspect_ratio: str = "disabled", style_reference_images: ImageBatch = None) -> Tuple[torch.Tensor]:
        api_key = api_key_in_env_or_workflow(api_key)
        headers = {"Api-Key": api_key}
        result_images = []
        for image in images:
            image_pil = tensor2pil(image)
            image_bytes = BytesIO()
            image_pil.save(image_bytes, format="PNG")

            if model in V2_MODELS:
                files = {"image_file": ("image.png", image_bytes.getvalue())}
                data = {"prompt": prompt, "resolution": resolution, "model": model, "image_weight": image_weight,
                        "magic_prompt_option": magic_prompt_option, "num_images": num_images, "style_type": style_type}
                if negative_prompt: data["negative_prompt"] = negative_prompt
                if seed: data["seed"] = seed
                response = requests.post("https://api.ideogram.ai/remix", headers=headers, files=files, data={"image_request": json.dumps(data)})

            elif model == "V_3":
                data = {
                    "prompt": prompt, "image_weight": image_weight, "magic_prompt": magic_prompt_option,
                    "num_images": num_images, "style_type": style_type, "rendering_speed": rendering_speed,
                }
                if negative_prompt: data["negative_prompt"] = negative_prompt
                if seed: data["seed"] = seed
                if aspect_ratio != "disabled":
                    data["aspect_ratio"] = aspect_ratio
                else:
                    data["resolution"] = to_v3_resolution(resolution)

                files_list = [("image", ("image.png", image_bytes.getvalue(), "image/png"))]
                if style_reference_images is not None:
                    for i, style_image in enumerate(style_reference_images):
                        pil_ref = tensor2pil(style_image)
                        ref_bytes = BytesIO()
                        pil_ref.save(ref_bytes, format="PNG")
                        files_list.append(("style_reference_images", (f"style_{i}.png", ref_bytes.getvalue(), "image/png")))

                response = requests.post("https://api.ideogram.ai/v1/ideogram-v3/remix", headers=headers, files=files_list, data=data)
            else:
                raise ValueError(f"Invalid model={model}")

            response.raise_for_status()
            for item in response.json()["data"]:
                img_response = requests.get(item["url"])
                img_response.raise_for_status()
                pil_image = Image.open(BytesIO(img_response.content))
                result_images.append(pil2tensor(pil_image))

        return (torch.cat(result_images, dim=0),)


class IdeogramDescribe(CustomNode):
    @classmethod
    def INPUT_TYPES(cls) -> Dict[str, Any]:
        return {
            "required": {"images": ("IMAGE",)},
            "optional": {"api_key": ("STRING", {"default": ""})}
        }

    RETURN_TYPES = ("STRING",)
    OUTPUT_IS_LIST = (True,)
    FUNCTION = "describe"
    CATEGORY = "ideogram"

    def describe(self, images: ImageBatch, api_key: str = "") -> tuple[list[str]]:
        api_key = api_key_in_env_or_workflow(api_key)
        headers = {"Api-Key": api_key}
        descriptions_batch = []
        for image in images:
            pil_image = tensor2pil(image)
            image_bytes = BytesIO()
            pil_image.save(image_bytes, format="PNG")
            files = {"image_file": ("image.png", image_bytes.getvalue(), "image/png")}
            response = requests.post("https://api.ideogram.ai/describe", headers=headers, files=files)
            response.raise_for_status()
            data = response.json()
            descriptions = data.get("descriptions", [])
            descriptions_batch.append(descriptions[0].get("text", "") if descriptions else "")
        return (descriptions_batch,)


NODE_CLASS_MAPPINGS = {
    "IdeogramGenerate": IdeogramGenerate,
    "IdeogramEdit": IdeogramEdit,
    "IdeogramRemix": IdeogramRemix,
    "IdeogramDescribe": IdeogramDescribe,
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "Ideogram Generate": "Ideogram Generate",
    "Ideogram Edit": "Ideogram Edit",
    "Ideogram Remix": "Ideogram Remix",
    "Ideogram Describe": "Ideogram Describe",
}
