"""Jam als 3D-Figur (OpenGL-Billboard + Schatten)."""

from __future__ import annotations

import math
from pathlib import Path

from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QImage, QSurfaceFormat
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from PyQt6.QtWidgets import QWidget, QLabel

try:
    import numpy  # noqa: F401 – von PyOpenGL benötigt

    from OpenGL.GL import (
        GL_BLEND,
        GL_COLOR_BUFFER_BIT,
        GL_DEPTH_BUFFER_BIT,
        GL_DEPTH_TEST,
        GL_LINEAR,
        GL_ONE_MINUS_SRC_ALPHA,
        GL_PROJECTION,
        GL_QUADS,
        GL_RGBA,
        GL_SRC_ALPHA,
        GL_TEXTURE_2D,
        GL_TEXTURE_MAG_FILTER,
        GL_TEXTURE_MIN_FILTER,
        GL_TRIANGLE_FAN,
        GL_UNSIGNED_BYTE,
        GL_MODELVIEW,
        glBegin,
        glBindTexture,
        glBlendFunc,
        glClear,
        glClearColor,
        glColor4f,
        glDisable,
        glEnable,
        glEnd,
        glGenTextures,
        glLoadIdentity,
        glMatrixMode,
        glPopMatrix,
        glPushMatrix,
        glRotatef,
        glTexCoord2f,
        glTexImage2D,
        glTexParameteri,
        glTranslatef,
        glVertex3f,
        glViewport,
    )
    from OpenGL.GLU import gluPerspective

    _HAS_GL = True
except ImportError:
    _HAS_GL = False

ASSETS = Path(__file__).resolve().parent.parent / "assets"
MONSTER_IMG = ASSETS / "jam-monster.png"
SOURCE_SIZE = (280, 336)
_SHADOW_PAD = 14
# Anzeige klein (~50 % der vorherigen Größe; Wachstums-Stufen bleiben relativ)
JAM_DISPLAY_FACTOR = 0.42


def display_scale(size_scale: float) -> float:
    return max(0.35, size_scale * JAM_DISPLAY_FACTOR)


def ensure_monster_asset() -> None:
    """PNG mit Transparenz erzeugen, falls noch keine Figur vorhanden."""
    if MONSTER_IMG.exists():
        return
    try:
        from PIL import Image, ImageDraw
    except ImportError:
        return
    MONSTER_IMG.parent.mkdir(parents=True, exist_ok=True)
    w, h = SOURCE_SIZE
    img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)
    body = (52, 70, w - 52, h - 28)
    draw.ellipse(body, fill=(108, 42, 168, 255))
    draw.ellipse((body[0] + 8, body[1] + 6, body[2] - 8, body[3] - 18), fill=(140, 72, 210, 255))
    eye_y = int(h * 0.34)
    for cx in (int(w * 0.38), int(w * 0.62)):
        draw.ellipse((cx - 22, eye_y - 18, cx + 22, eye_y + 26), fill=(255, 255, 255, 255))
        draw.ellipse((cx - 10, eye_y - 2, cx + 10, eye_y + 16), fill=(25, 10, 40, 255))
    draw.arc((w // 2 - 36, int(h * 0.48), w // 2 + 36, int(h * 0.58)), 20, 160, fill=(60, 20, 90, 255), width=4)
    img.save(MONSTER_IMG, "PNG")


def _load_monster_qimage() -> "QImage | None":
    ensure_monster_asset()
    if not MONSTER_IMG.exists():
        return None
    from PyQt6.QtGui import QImage

    img = QImage(str(MONSTER_IMG))
    return None if img.isNull() else img


def monster_alpha_bbox() -> tuple[int, int, int, int] | None:
    """Sichtbare Figur im PNG: left, top, width, height."""
    if not MONSTER_IMG.exists():
        return None
    try:
        from PIL import Image

        with Image.open(MONSTER_IMG) as im:
            box = im.convert("RGBA").getbbox()
        if not box:
            return None
        l, u, r, d = box
        return l, u, r - l, d - u
    except OSError:
        return None


def monster_window_dimensions(scale: float) -> tuple[int, int]:
    """Fenster nur so groß wie die Figur (+ Schatten/Hüpfen)."""
    scale = display_scale(scale)
    box = monster_alpha_bbox()
    if box is None:
        return int(SOURCE_SIZE[0] * scale), int(SOURCE_SIZE[1] * scale)
    _, _, bw, bh = box
    pad = int(_SHADOW_PAD * max(0.85, scale))
    return int(bw * scale) + pad, int(bh * scale) + pad


def _mask_image_for_size(width: int, height: int) -> "QImage | None":
    src = _load_monster_qimage()
    if src is None or width < 8 or height < 8:
        return None
    from PyQt6.QtGui import QImage, QPainter

    scaled = src.scaled(
        width,
        height,
        Qt.AspectRatioMode.KeepAspectRatio,
        Qt.TransformationMode.SmoothTransformation,
    )
    canvas = QImage(width, height, QImage.Format.Format_ARGB32_Premultiplied)
    canvas.fill(0)
    x = (width - scaled.width()) // 2
    y = (height - scaled.height()) // 2
    p = QPainter(canvas)
    p.drawImage(x, y, scaled)
    p.end()
    return canvas


def monster_shape_mask(width: int, height: int) -> "QBitmap | None":
    """Fenster-Maske aus PNG-Alpha – nur die Figur, kein Rechteck."""
    canvas = _mask_image_for_size(width, height)
    if canvas is None or canvas.isNull():
        return None
    from PyQt6.QtGui import QBitmap

    mask_img = canvas.createAlphaMask()
    if mask_img.isNull():
        return None
    return QBitmap.fromImage(mask_img)


def apply_figure_transparency(widget: QWidget) -> None:
    """Kein natives Rechteck-Hintergrund (wichtig unter Windows)."""
    widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
    widget.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True)
    widget.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False)
    widget.setAutoFillBackground(False)
    if hasattr(Qt.WidgetAttribute, "WA_NativeWindow"):
        widget.setAttribute(Qt.WidgetAttribute.WA_NativeWindow, False)


from jam.eyes import EyeAnimator, face_paint_transform
from jam.hands import HandAnimator
from jam.motion3d import Motion3D


def _gl_surface_format() -> QSurfaceFormat:
    fmt = QSurfaceFormat()
    fmt.setRenderableType(QSurfaceFormat.OpenGL)
    fmt.setProfile(QSurfaceFormat.OpenGLProfile.CompatibilityProfile)
    fmt.setVersion(2, 1)
    fmt.setAlphaBufferSize(8)
    fmt.setDepthBufferSize(16)
    fmt.setSamples(0)
    return fmt


class JamSprite3DGL(QOpenGLWidget):
    """Echtes 3D: Textur-Billboard mit Rotation, Neigung und Bodenschatten."""

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setFormat(_gl_surface_format())
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
        self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True)
        self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False)
        self.setAutoFillBackground(False)
        self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        self.setStyleSheet("background: transparent; border: none;")
        self._tex_id = 0
        self._scale = 0.55
        self._face_left = False
        self._angle_y = 20.0
        self._bob = 0.0
        self._moving = False
        self._dancing = False
        self._spin = 0.85
        self._has_texture = False
        self._eyes = EyeAnimator()
        self._look_at: tuple[float, float] | None = None
        self._motion: Motion3D | None = None
        self._visual: dict = {}

        self._anim = QTimer(self)
        self._anim.timeout.connect(self._tick)
        self._anim.start(33)

    def set_state(
        self,
        *,
        scale: float,
        face_left: bool,
        bob: float,
        moving: bool,
        dancing: bool = False,
        look_at: tuple[float, float] | None = None,
        motion: Motion3D | None = None,
        visual: dict | None = None,
    ) -> None:
        self._scale = scale
        self._face_left = face_left
        self._bob = bob
        self._dancing = dancing
        self._moving = moving or dancing
        self._look_at = look_at
        self._motion = motion
        self._visual = visual or {}
        self._eyes.set_look_at(look_at)
        if motion is not None:
            self._angle_y = motion.yaw
        self.update()

    def _tick(self) -> None:
        self._eyes.tick(moving=self._moving)
        self.update()

    def initializeGL(self) -> None:
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_TEXTURE_2D)
        self._load_texture()

    def _load_texture(self) -> None:
        if not MONSTER_IMG.exists():
            return
        from PIL import Image

        img = Image.open(MONSTER_IMG).convert("RGBA")
        img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
        data = img.tobytes()
        w, h = img.size
        self._tex_id = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self._tex_id)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexImage2D(
            GL_TEXTURE_2D,
            0,
            GL_RGBA,
            w,
            h,
            0,
            GL_RGBA,
            GL_UNSIGNED_BYTE,
            data,
        )
        self._has_texture = True

    def resizeGL(self, w: int, h: int) -> None:
        glViewport(0, 0, max(1, w), max(1, h))

    def paintGL(self) -> None:
        w = max(1, self.width())
        h = max(1, self.height())
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        aspect = w / h
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(42.0, aspect, 0.1, 20.0)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslatef(0.0, -0.15, -2.35)

        self._draw_shadow()
        self._draw_monster()

    def _draw_shadow(self) -> None:
        glPushMatrix()
        glTranslatef(0.0, -0.52 * self._scale, 0.02)
        glRotatef(90.0, 1.0, 0.0, 0.0)
        squash = 0.55 + abs(math.sin(math.radians(self._angle_y))) * 0.12
        glDisable(GL_TEXTURE_2D)
        glBegin(GL_TRIANGLE_FAN)
        glColor4f(0.15, 0.05, 0.2, 0.45)
        glVertex3f(0.0, 0.0, 0.0)
        segments = 24
        rx = 0.42 * self._scale * squash
        ry = 0.14 * self._scale
        for i in range(segments + 1):
            a = 2 * math.pi * i / segments
            glColor4f(0.1, 0.02, 0.15, 0.08)
            glVertex3f(math.cos(a) * rx, math.sin(a) * ry, 0.0)
        glEnd()
        glEnable(GL_TEXTURE_2D)
        glPopMatrix()

    def _draw_monster(self) -> None:
        if not self._has_texture:
            return
        m = self._motion
        if m is not None:
            hop = m.hop_offset(
                moving=self._moving,
                dancing=self._dancing,
                size_scale=self._scale,
            ) / 120.0
            sway_x = m.sway * (0.08 if self._moving else 0.02)
            depth_z = (m.depth - 0.9) * 0.06
            tilt = m.pitch * 0.85
            roll = m.roll * 0.85
            yaw = m.yaw
        else:
            hop = math.sin(self._bob) * (0.06 if self._dancing else 0.09)
            sway_x = math.sin(self._bob * 0.65) * (0.04 if self._dancing else 0.1 if self._moving else 0.03)
            depth_z = math.cos(math.radians(self._angle_y)) * 0.06
            tilt = math.sin(self._bob * 0.5) * (4.0 if self._dancing else 10.0 if self._moving else 4.0)
            roll = math.sin(self._bob) * (2.5 if self._dancing else 8.0 if self._moving else 0.0)
            yaw = self._angle_y

        glPushMatrix()
        glTranslatef(sway_x, hop, depth_z)
        glRotatef(yaw, 0.0, 1.0, 0.0)
        glRotatef(12.0 + tilt, 1.0, 0.0, 0.0)
        glRotatef(roll, 0.0, 0.0, 1.0)
        s = 0.88 * self._scale
        glBindTexture(GL_TEXTURE_2D, self._tex_id)

        u0, u1 = (1.0, 0.0) if self._face_left else (0.0, 1.0)
        h = 0.95 * s
        w = 0.85 * s

        glBegin(GL_QUADS)
        vis = getattr(self, "_visual", {}) or {}
        if vis.get("step", 0) > 0:
            tr, tg, tb = vis.get("tint", (108, 42, 168))
            glColor4f(
                min(1.15, tr / 220.0 + 0.15),
                min(1.15, tg / 220.0 + 0.15),
                min(1.15, tb / 220.0 + 0.15),
                1.0,
            )
        else:
            glColor4f(1.0, 1.0, 1.0, 1.0)
        glTexCoord2f(u0, 0.0)
        glVertex3f(-w, -h, 0.0)
        glTexCoord2f(u1, 0.0)
        glVertex3f(w, -h, 0.0)
        glTexCoord2f(u1, 1.0)
        glVertex3f(w, h, 0.0)
        glTexCoord2f(u0, 1.0)
        glVertex3f(-w, h, 0.0)
        glEnd()
        glPopMatrix()


class JamSprite2_5D(QWidget):
    """3D-Illusion: drehen, hüpfen, Tiefe – ohne OpenGL."""

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
        self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True)
        self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False)
        self.setAutoFillBackground(False)
        self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        self.setStyleSheet("background: transparent; border: none;")
        self._scale = 0.55
        self._face_left = False
        self._angle_y = 0.0
        self._bob = 0.0
        self._moving = False
        self._dancing = False
        self._base_img = None
        self._eyes = EyeAnimator()
        self._hands = HandAnimator()
        self._look_at: tuple[float, float] | None = None
        self._motion: Motion3D | None = None
        self._visual: dict = {}
        img = _load_monster_qimage()
        if img is not None:
            self._base_img = img

        self._anim = QTimer(self)
        self._anim.timeout.connect(self._tick_spin)
        self._anim.start(33)

    def set_state(
        self,
        *,
        scale: float,
        face_left: bool,
        bob: float,
        moving: bool,
        dancing: bool = False,
        look_at: tuple[float, float] | None = None,
        motion: Motion3D | None = None,
        visual: dict | None = None,
    ) -> None:
        self._scale = scale
        self._face_left = face_left
        self._bob = bob
        self._dancing = dancing
        self._moving = moving or dancing
        self._look_at = look_at
        self._motion = motion
        self._visual = visual or {}
        self._eyes.set_look_at(look_at)
        if motion is not None:
            self._angle_y = motion.yaw
        self.update()

    def _tick_spin(self) -> None:
        self._eyes.tick(moving=self._moving)
        self._hands.tick(moving=self._moving, dancing=self._dancing)
        self.update()

    def paintEvent(self, _event) -> None:
        from PyQt6.QtGui import QPainter, QColor, QRadialGradient

        p = QPainter(self)
        p.setRenderHint(QPainter.RenderHint.Antialiasing)
        p.setCompositionMode(QPainter.CompositionMode.CompositionMode_Clear)
        p.fillRect(self.rect(), Qt.GlobalColor.transparent)
        p.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
        w, h = max(1, self.width()), max(1, self.height())
        cx, cy = w // 2, int(h * 0.72)
        size_ref = min(w, h) / 280.0
        m = self._motion
        if m is None:
            m = Motion3D(yaw=self._angle_y, bob=self._bob)
            m.tick(
                vx=1.0 if not self._face_left else -1.0,
                vy=0.0,
                bob_phase=self._bob,
                moving=self._moving,
                dancing=self._dancing,
                face_left=self._face_left,
            )

        sway = m.sway
        depth = m.depth
        shadow_w = max(8, int(50 * size_ref * self._scale * (0.7 + depth * 0.4)))
        shadow_h = max(3, int(12 * size_ref * self._scale * m.squash))
        shadow_cx = cx + int(m.shadow_offset() * shadow_w)
        floor_y = cy + 6
        grad = QRadialGradient(float(shadow_cx), float(floor_y), float(shadow_w))
        grad.setColorAt(0, QColor(40, 15, 60, 120))
        grad.setColorAt(1, QColor(40, 15, 60, 0))
        p.setBrush(grad)
        p.setPen(Qt.PenStyle.NoPen)
        p.drawEllipse(shadow_cx - shadow_w, floor_y - shadow_h // 2, shadow_w * 2, shadow_h)

        if self._base_img is None:
            p.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, "👾")
            p.end()
            return

        side = max(64, int(min(w, h) * 0.82))
        img = self._base_img.scaled(
            side,
            side,
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.SmoothTransformation,
        )

        t, body = face_paint_transform(
            w,
            h,
            scale=self._scale,
            bob=self._bob,
            angle_y=m.yaw,
            face_left=self._face_left,
            moving=self._moving,
            dancing=self._dancing,
            motion=m,
        )
        pw, ph = int(body.width()), int(body.height())
        img = img.scaled(pw, ph, Qt.AspectRatioMode.KeepAspectRatio,
                         Qt.TransformationMode.SmoothTransformation)

        p.setTransform(t)
        p.drawImage(0, 0, img)
        step = getattr(m, "_step", 0.0)
        vis = self._visual or {}
        tint = vis.get("tint") if vis.get("step", 0) > 0 else None
        self._hands.draw(
            p,
            body,
            face_left=self._face_left,
            moving=self._moving,
            dancing=self._dancing,
            step=step,
            tint=tint,
        )
        self._eyes.draw(p, body, face_left=self._face_left)
        from jam.appearance_evolve import draw_3d_extras

        draw_3d_extras(
            p,
            body,
            self._visual,
            face_left=self._face_left,
        )
        p.end()


def create_sprite(parent=None) -> QWidget:
    """
    Standard: 3D-Bewegung (2.5D, stabil).
    Echtes OpenGL mit JAM_USE_GL=1 (experimentell).
    """
    import os

    if os.environ.get("JAM_NO_GL", "").strip() in ("1", "true", "yes"):
        w = JamSprite2_5D(parent)
        apply_figure_transparency(w)
        return w

    use_gl = os.environ.get("JAM_USE_GL", "").strip() in ("1", "true", "yes")
    use_gl = use_gl or os.environ.get("JAM_FORCE_3D") == "1"

    if use_gl and _HAS_GL:
        try:
            w = JamSprite3DGL(parent)
            apply_figure_transparency(w)
            w.hide()
            w.show()
            return w
        except Exception:
            pass
    w = JamSprite2_5D(parent)
    apply_figure_transparency(w)
    return w


def is_real_3d(widget: QWidget) -> bool:
    return isinstance(widget, JamSprite3DGL)
