# primitive/nodes/transform.py
"""
Transform executors: Modify single media inputs.

Primitives: SEGMENT, RESIZE, TRANSFORM
"""

import logging
import subprocess
from pathlib import Path
from typing import Any, Dict, List

from ..dag import NodeType
from ..executor import Executor, register_executor
from .encoding import get_web_encoding_args, get_web_video_args

logger = logging.getLogger(__name__)


@register_executor(NodeType.SEGMENT)
class SegmentExecutor(Executor):
    """
    Extract a time segment from media.

    Config:
        offset: Start time in seconds (default: 0)
        duration: Duration in seconds (optional, default: to end)
        precise: Use frame-accurate seeking (default: True)
    """

    def execute(
        self,
        config: Dict[str, Any],
        inputs: List[Path],
        output_path: Path,
    ) -> Path:
        if len(inputs) != 1:
            raise ValueError("SEGMENT requires exactly one input")

        input_path = inputs[0]
        offset = config.get("offset", 0)
        duration = config.get("duration")
        precise = config.get("precise", True)

        output_path.parent.mkdir(parents=True, exist_ok=True)

        if precise:
            # Frame-accurate: decode-seek (slower but precise)
            cmd = ["ffmpeg", "-y", "-i", str(input_path)]
            if offset > 0:
                cmd.extend(["-ss", str(offset)])
            if duration:
                cmd.extend(["-t", str(duration)])
            cmd.extend([*get_web_encoding_args(), str(output_path)])
        else:
            # Fast: input-seek at keyframes (may be slightly off)
            cmd = ["ffmpeg", "-y"]
            if offset > 0:
                cmd.extend(["-ss", str(offset)])
            cmd.extend(["-i", str(input_path)])
            if duration:
                cmd.extend(["-t", str(duration)])
            cmd.extend(["-c", "copy", str(output_path)])

        logger.debug(f"SEGMENT: offset={offset}, duration={duration}, precise={precise}")
        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode != 0:
            raise RuntimeError(f"Segment failed: {result.stderr}")

        return output_path


@register_executor(NodeType.RESIZE)
class ResizeExecutor(Executor):
    """
    Resize media to target dimensions.

    Config:
        width: Target width
        height: Target height
        mode: "fit" (letterbox), "fill" (crop), "stretch", "pad"
        background: Background color for pad mode (default: black)
    """

    def execute(
        self,
        config: Dict[str, Any],
        inputs: List[Path],
        output_path: Path,
    ) -> Path:
        if len(inputs) != 1:
            raise ValueError("RESIZE requires exactly one input")

        input_path = inputs[0]
        width = config["width"]
        height = config["height"]
        mode = config.get("mode", "fit")
        background = config.get("background", "black")

        output_path.parent.mkdir(parents=True, exist_ok=True)

        if mode == "fit":
            # Scale to fit, add letterboxing
            vf = f"scale={width}:{height}:force_original_aspect_ratio=decrease,pad={width}:{height}:(ow-iw)/2:(oh-ih)/2:color={background}"
        elif mode == "fill":
            # Scale to fill, crop excess
            vf = f"scale={width}:{height}:force_original_aspect_ratio=increase,crop={width}:{height}"
        elif mode == "stretch":
            # Stretch to exact size
            vf = f"scale={width}:{height}"
        elif mode == "pad":
            # Scale down only if larger, then pad
            vf = f"scale='min({width},iw)':'min({height},ih)':force_original_aspect_ratio=decrease,pad={width}:{height}:(ow-iw)/2:(oh-ih)/2:color={background}"
        else:
            raise ValueError(f"Unknown resize mode: {mode}")

        cmd = [
            "ffmpeg", "-y",
            "-i", str(input_path),
            "-vf", vf,
            *get_web_video_args(),
            "-c:a", "copy",
            str(output_path)
        ]

        logger.debug(f"RESIZE: {width}x{height} ({mode}) (web-optimized)")
        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode != 0:
            raise RuntimeError(f"Resize failed: {result.stderr}")

        return output_path


@register_executor(NodeType.TRANSFORM)
class TransformExecutor(Executor):
    """
    Apply visual effects to media.

    Config:
        effects: Dict of effect -> value
            saturation: 0.0-2.0 (1.0 = normal)
            contrast: 0.0-2.0 (1.0 = normal)
            brightness: -1.0 to 1.0 (0.0 = normal)
            gamma: 0.1-10.0 (1.0 = normal)
            hue: degrees shift
            blur: blur radius
            sharpen: sharpen amount
            speed: playback speed multiplier
    """

    def execute(
        self,
        config: Dict[str, Any],
        inputs: List[Path],
        output_path: Path,
    ) -> Path:
        if len(inputs) != 1:
            raise ValueError("TRANSFORM requires exactly one input")

        input_path = inputs[0]
        effects = config.get("effects", {})

        if not effects:
            # No effects - just copy
            import shutil
            output_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(input_path, output_path)
            return output_path

        output_path.parent.mkdir(parents=True, exist_ok=True)

        # Build filter chain
        vf_parts = []
        af_parts = []

        # Color adjustments via eq filter
        eq_parts = []
        if "saturation" in effects:
            eq_parts.append(f"saturation={effects['saturation']}")
        if "contrast" in effects:
            eq_parts.append(f"contrast={effects['contrast']}")
        if "brightness" in effects:
            eq_parts.append(f"brightness={effects['brightness']}")
        if "gamma" in effects:
            eq_parts.append(f"gamma={effects['gamma']}")
        if eq_parts:
            vf_parts.append(f"eq={':'.join(eq_parts)}")

        # Hue adjustment
        if "hue" in effects:
            vf_parts.append(f"hue=h={effects['hue']}")

        # Blur
        if "blur" in effects:
            vf_parts.append(f"boxblur={effects['blur']}")

        # Sharpen
        if "sharpen" in effects:
            vf_parts.append(f"unsharp=5:5:{effects['sharpen']}:5:5:0")

        # Speed change
        if "speed" in effects:
            speed = effects["speed"]
            vf_parts.append(f"setpts={1/speed}*PTS")
            af_parts.append(f"atempo={speed}")

        cmd = ["ffmpeg", "-y", "-i", str(input_path)]

        if vf_parts:
            cmd.extend(["-vf", ",".join(vf_parts)])
        if af_parts:
            cmd.extend(["-af", ",".join(af_parts)])

        cmd.extend([*get_web_encoding_args(), str(output_path)])

        logger.debug(f"TRANSFORM: {list(effects.keys())} (web-optimized)")
        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode != 0:
            raise RuntimeError(f"Transform failed: {result.stderr}")

        return output_path
