# retoor <retoor@molodetz.nl>
from __future__ import annotations

import colorsys
import io
import re

from devplacepy.services.jobs.isslop.analysis.domsignals.base import DomPageContext, dom_check
from devplacepy.services.jobs.isslop.analysis.signals import (
    AXIS_ORIGIN,
    SEVERITY_MEDIUM,
    SEVERITY_STRONG,
    SEVERITY_WEAK,
    Signal,
)

HEX_COLOR_PATTERN = re.compile(r"#[0-9a-fA-F]{6}\b")
RGB_COLOR_PATTERN = re.compile(r"rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)")
GRADIENT_PATTERN = re.compile(r"linear-gradient\([^)]*\)", re.IGNORECASE)

TAILWIND_DEFAULT_ACCENTS: frozenset[str] = frozenset(
    {"#6366f1", "#4f46e5", "#8b5cf6", "#a855f7", "#7c3aed", "#818cf8"}
)
SIGNATURE_GRADIENT_PATTERN = re.compile(r"#667eea.{0,80}#764ba2|#764ba2.{0,80}#667eea", re.IGNORECASE | re.DOTALL)
OKLCH_PATTERN = re.compile(r"oklch\(\s*([\d.]+)", re.IGNORECASE)
BOX_SHADOW_PURPLE_PATTERN = re.compile(
    r"(rgba?\(\s*(?:99|79|139|168|124|129)\s*,\s*\d+\s*,\s*\d+|#(?:6366f1|4f46e5|8b5cf6|a855f7|7c3aed|818cf8))"
    r"[\s\S]{0,60}?(\d+)px",
    re.IGNORECASE,
)
DARK_LUMINANCE_THRESHOLD: int = 40


def _hue_from_hex(hex_code: str) -> float | None:
    try:
        r = int(hex_code[1:3], 16) / 255.0
        g = int(hex_code[3:5], 16) / 255.0
        b = int(hex_code[5:7], 16) / 255.0
    except ValueError:
        return None
    hue, _lightness, _saturation = colorsys.rgb_to_hls(r, g, b)
    return hue * 360.0


def _hue_from_rgb(r: int, g: int, b: int) -> float:
    hue, _lightness, _saturation = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
    return hue * 360.0


def _is_blue_or_purple(hue: float) -> bool:
    return 210.0 <= hue <= 300.0


def _sample_text(sample: dict) -> str:
    return " ".join(
        str(sample.get(field, ""))
        for field in ("backgroundImage", "backgroundColor", "color", "boxShadow")
    )


def _detect_gradient_hue_combo(samples: list[dict]) -> Signal | None:
    for sample in samples:
        gradient = GRADIENT_PATTERN.search(str(sample.get("backgroundImage", "")))
        if not gradient:
            continue
        text = gradient.group(0)
        hues: list[float] = []
        for match in HEX_COLOR_PATTERN.finditer(text):
            hue = _hue_from_hex(match.group(0))
            if hue is not None:
                hues.append(hue)
        for match in RGB_COLOR_PATTERN.finditer(text):
            hues.append(_hue_from_rgb(int(match.group(1)), int(match.group(2)), int(match.group(3))))
        if sum(1 for hue in hues if _is_blue_or_purple(hue)) >= 2:
            return Signal(
                "SIGNATURE_GRADIENT_HUE_COMBO",
                "Gradient stops both land in the default LLM blue/purple hue range",
                SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                2.0,
                0,
                text[:150],
            )
    return None


def _detect_tailwind_accent(samples: list[dict]) -> Signal | None:
    for sample in samples:
        text = _sample_text(sample).lower()
        for accent in TAILWIND_DEFAULT_ACCENTS:
            if accent in text:
                return Signal(
                    "TAILWIND_DEFAULT_ACCENT",
                    "Literal Tailwind default accent color used verbatim",
                    SEVERITY_WEAK,
                    AXIS_ORIGIN,
                    1.0,
                    0,
                    accent,
                )
    return None


def _detect_signature_gradient(samples: list[dict]) -> Signal | None:
    for sample in samples:
        match = SIGNATURE_GRADIENT_PATTERN.search(_sample_text(sample))
        if match:
            return Signal(
                "SIGNATURE_GRADIENT",
                "Canonical LLM purple gradient #667eea to #764ba2",
                SEVERITY_STRONG,
                AXIS_ORIGIN,
                3.0,
                0,
                match.group(0)[:120],
            )
    return None


def _detect_oklch_token(samples: list[dict]) -> Signal | None:
    for sample in samples:
        match = OKLCH_PATTERN.search(_sample_text(sample))
        if not match:
            continue
        try:
            lightness = float(match.group(1))
        except ValueError:
            continue
        if 0.55 <= lightness <= 0.75:
            return Signal(
                "OKLCH_SHADCN_TOKEN",
                "oklch() color token in the shadcn/ui default lightness band",
                SEVERITY_WEAK,
                AXIS_ORIGIN,
                1.0,
                0,
                match.group(0),
            )
    return None


def _detect_purple_glow_shadow(samples: list[dict]) -> Signal | None:
    for sample in samples:
        match = BOX_SHADOW_PURPLE_PATTERN.search(str(sample.get("boxShadow", "")))
        if not match:
            continue
        try:
            blur = int(match.group(2))
        except ValueError:
            continue
        if blur >= 40:
            return Signal(
                "PURPLE_GLOW_SHADOW",
                "Large-blur purple/blue glow box-shadow",
                SEVERITY_WEAK,
                AXIS_ORIGIN,
                1.0,
                0,
                match.group(0)[:120],
            )
    return None


def _detect_dark_mode_default(samples: list[dict]) -> Signal | None:
    for sample in samples:
        if sample.get("tag") != "body":
            continue
        match = RGB_COLOR_PATTERN.search(str(sample.get("backgroundColor", "")))
        if not match:
            continue
        channels = [int(match.group(1)), int(match.group(2)), int(match.group(3))]
        if sum(channels) / 3.0 < DARK_LUMINANCE_THRESHOLD:
            return Signal(
                "DARK_MODE_DEFAULT",
                "Dark background color by default",
                SEVERITY_WEAK,
                AXIS_ORIGIN,
                0.5,
                0,
                str(sample.get("backgroundColor", "")),
            )
    return None


def _screenshot_hue_buckets(screenshot_bytes: bytes) -> dict[int, int]:
    from PIL import Image

    image = Image.open(io.BytesIO(screenshot_bytes)).convert("RGB")
    image = image.resize((64, 64))
    buckets: dict[int, int] = {}
    for r, g, b in image.get_flattened_data():
        hue, lightness, saturation = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
        if saturation < 0.15 or lightness < 0.05 or lightness > 0.95:
            continue
        bucket = int((hue * 360.0) // 20) * 20
        buckets[bucket] = buckets.get(bucket, 0) + 1
    return buckets


def _detect_screenshot_gradient_hero(screenshot_bytes: bytes | None) -> Signal | None:
    if not screenshot_bytes:
        return None
    try:
        buckets = _screenshot_hue_buckets(screenshot_bytes)
    except Exception:
        return None
    total = sum(buckets.values())
    if total <= 0:
        return None
    ranked = sorted(buckets.items(), key=lambda item: item[1], reverse=True)[:2]
    if len(ranked) < 2 or not all(_is_blue_or_purple(bucket) for bucket, _count in ranked):
        return None
    coverage = sum(count for _bucket, count in ranked) / total
    if coverage <= 0.3:
        return None
    return Signal(
        "SCREENSHOT_GRADIENT_HERO",
        "Screenshot dominated by a blue/purple hero gradient",
        SEVERITY_MEDIUM,
        AXIS_ORIGIN,
        2.0,
        0,
        f"{coverage:.0%} of sampled pixels in blue/purple hues",
    )


@dom_check
def detect_color_signals(page: DomPageContext) -> list[Signal]:
    samples = page.dom.get("colors", []) or []
    findings: list[Signal] = []
    for detector in (
        _detect_gradient_hue_combo,
        _detect_tailwind_accent,
        _detect_signature_gradient,
        _detect_oklch_token,
        _detect_purple_glow_shadow,
        _detect_dark_mode_default,
    ):
        signal = detector(samples)
        if signal is not None:
            findings.append(signal)
    screenshot_signal = _detect_screenshot_gradient_hero(page.screenshot_bytes)
    if screenshot_signal is not None:
        findings.append(screenshot_signal)
    return findings
