from PIL import Image
import numpy as np
import cv2
from .ImageManipulation import ImageManipulation
from utils.face_utils import detect_faces_bgr, build_soft_face_mask


class BackgroundBlur(ImageManipulation):
    """Keep detected faces sharp and blur everything else."""

    def getManipulationName(self) -> str:
        return "Background Blur"

    def getParameters(self) -> list[str]:
        return ["blur_ksize", "feather", "enlarge", "no_face_behavior", "draw_boxes"]

    def manipulateImage(self, image, parameters=None) -> None:
        if parameters is None:
            parameters = {}

        blur_ksize = int(parameters.get("blur_ksize", 41))
        blur_ksize = blur_ksize if blur_ksize % 2 == 1 else blur_ksize + 1
        feather = int(parameters.get("feather", 45))
        enlarge = float(parameters.get("enlarge", 0.45))
        no_face_behavior = parameters.get("no_face_behavior", "blur_all")
        draw_boxes = bool(parameters.get("draw_boxes", False))

        pil_img: Image.Image = image.getImage()
        if pil_img is None:
            return

        rgb = np.array(pil_img)
        if rgb.ndim == 2:
            rgb = np.stack([rgb] * 3, axis=-1)
        bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)

        faces = detect_faces_bgr(bgr)

        blurred = cv2.GaussianBlur(bgr, (blur_ksize, blur_ksize), 0)

        if len(faces) == 0:
            out_bgr = bgr if no_face_behavior == "original" else blurred
        else:
            mask = build_soft_face_mask(bgr.shape[:2], faces, enlarge=enlarge, feather=feather)
            mask_f = (mask.astype(np.float32) / 255.0)[..., None]
            out_bgr = (bgr.astype(np.float32) * mask_f +
                       blurred.astype(np.float32) * (1.0 - mask_f))
            out_bgr = np.clip(out_bgr, 0, 255).astype(np.uint8)

            if draw_boxes:
                for (x, y, w, h) in faces:
                    cv2.rectangle(out_bgr, (x, y), (x + w, y + h), (0, 255, 0), 2)

        out_rgb = cv2.cvtColor(out_bgr, cv2.COLOR_BGR2RGB)
        image.setImage(Image.fromarray(out_rgb))
