"""Shared QR code styling, output schema, and base node logic."""

import io
from typing import Any

from flowire_sdk import BaseNodeOutput, HandleConfig, NodeExecutionContext, NodeHandles, NodeMetadata
from PIL import ImageColor
from pydantic import BaseModel, Field


def parse_color_tuple(color: str) -> tuple[int, int, int]:
    """Parse a color string (name or hex) into an RGB tuple."""
    return ImageColor.getrgb(color)[:3]


class QRStyleInput(BaseModel):
    """Shared QR code styling fields. Subclasses define format-specific fields."""

    size: int = Field(
        default=10,
        description="Box size in pixels for each QR code module",
        ge=1,
        le=50,
    )
    border: int = Field(
        default=2,
        description="Border size in modules",
        ge=0,
        le=20,
    )
    error_correction: str = Field(
        default="M",
        description="Error correction level: L (7%), M (15%), Q (25%), H (30%)",
        json_schema_extra={"enum": ["L", "M", "Q", "H"]},
    )
    module_style: str = Field(
        default="square",
        description="Shape of QR code modules",
        json_schema_extra={
            "enum": ["square", "gapped_square", "circle", "rounded", "vertical_bars", "horizontal_bars"]
        },
    )
    color_mode: str = Field(
        default="solid",
        description="Color fill mode for the QR code",
        json_schema_extra={
            "enum": ["solid", "radial_gradient", "square_gradient", "horizontal_gradient", "vertical_gradient"]
        },
    )
    fill_color: str = Field(
        default="black",
        description="Module color for solid mode, or start color for gradients (name or hex)",
    )
    back_color: str = Field(
        default="white",
        description="Background color (name or hex)",
    )
    gradient_end_color: str = Field(
        default="#0000FF",
        description="End color for gradient modes (name or hex)",
    )

    @classmethod
    def model_json_schema(cls, *args, **kwargs):
        """Reorder properties so format-specific fields appear before style fields."""
        schema = super().model_json_schema(*args, **kwargs)
        if "properties" in schema:
            style_keys = set(QRStyleInput.model_fields.keys())
            own = {k: v for k, v in schema["properties"].items() if k not in style_keys}
            style = {k: v for k, v in schema["properties"].items() if k in style_keys}
            schema["properties"] = {**own, **style}
        return schema


class QRCodeOutput(BaseNodeOutput):
    """Shared output for all QR code nodes."""

    image: dict = Field(..., description="Storage reference to the generated QR code PNG")
    size_bytes: int = Field(..., description="Image file size in bytes")
    version: int = Field(..., description="QR code version (1-40, determines grid size)")


ERROR_CORRECTION_MAP = {
    "L": 1,  # qrcode.constants.ERROR_CORRECT_L
    "M": 0,  # qrcode.constants.ERROR_CORRECT_M
    "Q": 3,  # qrcode.constants.ERROR_CORRECT_Q
    "H": 2,  # qrcode.constants.ERROR_CORRECT_H
}


def _get_module_drawer(style: str):
    from qrcode.image.styles.moduledrawers import (
        CircleModuleDrawer,
        GappedSquareModuleDrawer,
        HorizontalBarsDrawer,
        RoundedModuleDrawer,
        SquareModuleDrawer,
        VerticalBarsDrawer,
    )

    return {
        "square": SquareModuleDrawer,
        "gapped_square": GappedSquareModuleDrawer,
        "circle": CircleModuleDrawer,
        "rounded": RoundedModuleDrawer,
        "vertical_bars": VerticalBarsDrawer,
        "horizontal_bars": HorizontalBarsDrawer,
    }.get(style, SquareModuleDrawer)()


def _get_color_mask(mode: str, back_color: str, fill_color: str, gradient_end_color: str):
    from qrcode.image.styles.colormasks import (
        HorizontalGradiantColorMask,
        RadialGradiantColorMask,
        SolidFillColorMask,
        SquareGradiantColorMask,
        VerticalGradiantColorMask,
    )

    bg = parse_color_tuple(back_color)
    fg = parse_color_tuple(fill_color)
    end = parse_color_tuple(gradient_end_color)

    if mode == "radial_gradient":
        return RadialGradiantColorMask(back_color=bg, center_color=fg, edge_color=end)
    if mode == "square_gradient":
        return SquareGradiantColorMask(back_color=bg, center_color=fg, edge_color=end)
    if mode == "horizontal_gradient":
        return HorizontalGradiantColorMask(back_color=bg, left_color=fg, right_color=end)
    if mode == "vertical_gradient":
        return VerticalGradiantColorMask(back_color=bg, top_color=fg, bottom_color=end)
    return SolidFillColorMask(back_color=bg, front_color=fg)


def generate_qr_image(data: str, style_inputs: dict[str, Any], context: NodeExecutionContext) -> QRCodeOutput:
    """Generate a styled QR code PNG and store it. Shared by all QR nodes."""
    import qrcode
    from qrcode.image.styledpil import StyledPilImage

    error_level = ERROR_CORRECTION_MAP.get(style_inputs.get("error_correction", "M"), 0)

    qr = qrcode.QRCode(
        version=None,
        error_correction=error_level,
        box_size=style_inputs.get("size", 10),
        border=style_inputs.get("border", 4),
    )
    qr.add_data(data)
    qr.make(fit=True)

    module_drawer = _get_module_drawer(style_inputs.get("module_style", "square"))
    color_mask = _get_color_mask(
        style_inputs.get("color_mode", "solid"),
        style_inputs.get("back_color", "white"),
        style_inputs.get("fill_color", "black"),
        style_inputs.get("gradient_end_color", "#0000FF"),
    )

    img = qr.make_image(
        image_factory=StyledPilImage,
        module_drawer=module_drawer,
        color_mask=color_mask,
    )

    buf = io.BytesIO()
    img.save(buf, format="PNG")
    png_bytes = buf.getvalue()

    ref = context.store_binary(png_bytes, "qrcode.png", "image/png")

    return QRCodeOutput(
        image=ref,
        size_bytes=len(png_bytes),
        version=qr.version,
    )


def qr_node_metadata(name: str, description: str, icon: str = "📱") -> NodeMetadata:
    """Create standard metadata for a QR code node."""
    return NodeMetadata(
        name=name,
        description=description,
        category="qrcode",
        icon=icon,
        color="#1A1A2E",
        handles=NodeHandles(
            inputs=[HandleConfig(id="default", position="left")],
            outputs=[HandleConfig(id="default", position="right")],
        ),
    )
