import cv2
import numpy as np
from utils.face_utils import detect_faces_bgr, build_soft_face_mask
from utils.overlay_utils import overlay_rgba, load_rgba


def face_blur(frame, p=None, ctx=None):
    p = p or {}
    enlarge = float(p.get("enlarge", 0.45))
    feather = int(p.get("feather", 45))
    k = int(p.get("blur_ksize", 41)); k = k if k % 2 == 1 else k + 1

    faces = (ctx or {}).get("faces")
    if faces is None:
        faces = detect_faces_bgr(frame)

    blurred = cv2.GaussianBlur(frame, (k, k), 0)
    if not len(faces):
        return blurred

    mask = build_soft_face_mask(frame.shape[:2], faces, enlarge=enlarge, feather=feather)
    m = (mask.astype(np.float32) / 255.0)[..., None]
    return np.clip(frame * m + blurred * (1 - m), 0, 255).astype(np.uint8)

def grayscale(frame, p=None, ctx=None):
    g = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    return cv2.cvtColor(g, cv2.COLOR_GRAY2BGR)

def hat_overlay(frame, p=None, ctx=None):
    p = p or {}
    asset_path = p.get("asset_path", "assets/cowboy_hat.png")
    scale = float(p.get("scale", 1.15))
    head_clearance = float(p.get("head_clearance", 0.12))

    try:
        hat = load_rgba(asset_path)
    except Exception:
        return frame

    faces = (ctx or {}).get("faces")
    if faces is None:
        faces = detect_faces_bgr(frame)

    H, W = frame.shape[:2]
    for (x, y, w, h) in faces:
        hat_w = int(w * scale)
        ar = hat.shape[0] / hat.shape[1]
        hat_h = max(1, int(hat_w * ar))

        xh = x + w // 2 - hat_w // 2


        bottom_y = y + int(head_clearance * h)
        yh = bottom_y - hat_h

        if yh < -int(0.25 * hat_h):
            yh = -int(0.25 * hat_h)

        frame = overlay_rgba(frame, hat, xh, yh, target_w=hat_w)
    return frame


def glasses_overlay(frame, p=None, ctx=None):
    p = p or {}
    asset_path = p.get("asset_path", "assets/glasses.png")
    scale = float(p.get("scale", 1.05))
    y_factor = float(p.get("y_factor", 0.38))

    try:
        glasses = load_rgba(asset_path)
    except Exception:
        return frame

    faces = (ctx or {}).get("faces")
    if faces is None:
        faces = detect_faces_bgr(frame)

    for (x, y, w, h) in faces:
        g_w = int(w * scale)
        ar = glasses.shape[0] / glasses.shape[1]
        g_h = max(1, int(g_w * ar))
        xg = x + w // 2 - g_w // 2
        yg = y + int(y_factor * h) - g_h // 2
        frame = overlay_rgba(frame, glasses, xg, yg, target_w=g_w)
    return frame

def _overlay_face_anchor(frame, p=None, ctx=None):
    p = p or {}
    asset_path = p.get("asset_path")
    if not asset_path:
        return frame
    scale = float(p.get("scale", 1.0))
    y_factor = float(p.get("y_factor", 0.5))
    center_y = bool(p.get("center_y", True))
    x_offset = int(p.get("x_offset", 0))
    y_offset_frac = float(p.get("y_offset", 0.0))

    try:
        overlay = load_rgba(asset_path)
    except Exception:
        return frame

    faces = (ctx or {}).get("faces")
    if faces is None:
        faces = detect_faces_bgr(frame)

    for (x, y, w, h) in faces:
        t_w = int(max(1, w * scale))
        ar = overlay.shape[0] / max(1, overlay.shape[1])
        t_h = max(1, int(t_w * ar))
        x_pos = x + w // 2 - t_w // 2 + x_offset
        y_anchor = y + int(y_factor * h)
        y_pos = y_anchor - (t_h // 2 if center_y else 0) + int(y_offset_frac * h)
        frame = overlay_rgba(frame, overlay, x_pos, y_pos, target_w=t_w)
    return frame

def mustache_overlay(frame, p=None, ctx=None):
    p = p or {}
    if "asset_path" not in p:
        p = {**p, "asset_path": "assets/moustache.png"}
    p = {"scale": 0.8, "y_factor": 0.72, "center_y": True, **p}
    return _overlay_face_anchor(frame, p, ctx)

FILTERS = {
    "face_blur": face_blur,
    "grayscale": grayscale,
    "hat": hat_overlay,
    "glasses": glasses_overlay,
    "mustache": mustache_overlay,
}

def apply_filters(frame, names, params=None, ctx=None):
    params = params or {}
    ctx = ctx or {}
    if any(n in ("face_blur", "hat", "glasses", "mustache") for n in names):
        ctx["faces"] = detect_faces_bgr(frame)

    for name in names:
        fn = FILTERS.get(name)
        if fn:
            frame = fn(frame, params.get(name), ctx)
    return frame
