"""
Gloda Video Factory - Utility Functions
Handles font management, Auto-QC, and helper effects.
"""

import os
import logging
from pathlib import Path
from typing import Optional, Tuple
import urllib.request
import math

import numpy as np
from PIL import Image
from moviepy.editor import ImageClip, VideoFileClip, AudioFileClip

import config

logger = logging.getLogger(__name__)

# Google Fonts CDN URL
ROBOTO_BOLD_URL = "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Bold.ttf"
NOTO_SC_BOLD_URL = "https://raw.githubusercontent.com/google/fonts/main/ofl/notosanssc/NotoSansSC-Bold.ttf"

FONT_PATH_EN = config.FONTS_DIR / "Roboto-Bold.ttf"
FONT_PATH_CN = config.FONTS_DIR / "NotoSansSC-Bold.ttf"


def ensure_fonts() -> Path:
    """Ensure required fonts (EN & CN) are available."""
    config.FONTS_DIR.mkdir(parents=True, exist_ok=True)
    
    # English Font
    if not FONT_PATH_EN.exists():
        logger.info(f"Downloading Roboto-Bold font...")
        try:
            urllib.request.urlretrieve(ROBOTO_BOLD_URL, FONT_PATH_EN)
        except Exception as e:
            logger.error(f"Failed to download EN font: {e}")

    # Chinese Font
    if not FONT_PATH_CN.exists():
        logger.info(f"Downloading NotoSansSC-Bold font...")
        try:
            # Using a reliable mirror or source if Github raw is flaky, but trying Github first
            urllib.request.urlretrieve(NOTO_SC_BOLD_URL, FONT_PATH_CN)
        except Exception as e:
            logger.error(f"Failed to download CN font: {e}")
    
    # Return CN font as default for mixed text
    if FONT_PATH_CN.exists():
        return FONT_PATH_CN
    return FONT_PATH_EN


def check_imagemagick() -> bool:
    """Check if ImageMagick is installed."""
    import shutil
    if shutil.which("convert"):
        return True
    else:
        logger.warning("ImageMagick not found. Text overlays may fail.")
        return False


def verify_assets(video_path: str, audio_path: str) -> Tuple[bool, str]:
    """
    Auto-QC: Verify generated assets quality.
    
    Checks:
    1. File size sanity check
    2. Duration matching (+/- 2s tolerance)
    3. Audio silence check
    
    Returns:
        (Passed: bool, Reason: str)
    """
    logger.info(f"Running Auto-QC on:\nVideo: {video_path}\nAudio: {audio_path}")
    
    try:
        # 1. File Size Check
        vid_size = os.path.getsize(video_path)
        if vid_size < 50 * 1024:  # < 50KB
            return False, f"Video file too small ({vid_size/1024:.1f}KB). Likely error/black screen."
            
        aud_size = os.path.getsize(audio_path)
        if aud_size < 5 * 1024:  # < 5KB
            return False, f"Audio file too small ({aud_size/1024:.1f}KB)."

        # 2. Duration Check
        try:
            v_clip = VideoFileClip(video_path)
            a_clip = AudioFileClip(audio_path)
            
            v_dur = v_clip.duration
            a_dur = a_clip.duration
            
            # Check for silence (RMS)
            # Read first 2 seconds of audio
            chunk = a_clip.to_soundarray(fps=44100, nbytes=2, buffersize=1000)
            if chunk is not None:
                rms = np.sqrt(np.mean(chunk**2))
                if rms < 0.001:
                    v_clip.close()
                    a_clip.close()
                    return False, "Audio appears to be silent (RMS < 0.001)"
            
            v_clip.close()
            a_clip.close()
            
            # Tolerance check
            if abs(v_dur - a_dur) > 2.0:
                return False, f"Duration mismatch: Video={v_dur:.1f}s, Audio={a_dur:.1f}s"
                
        except Exception as e:
            return False, f"Media analysis failed: {str(e)}"

        return True, "QC Passed"
        
    except Exception as e:
        logger.error(f"Auto-QC Error: {e}")
        return False, f"QC System Error: {e}"


def apply_ken_burns(
    image_path: str,
    duration: float = 5.0,
    zoom_ratio: float = 1.2,
    output_path: Optional[str] = None
) -> str:
    """Apply Ken Burns effect (slow zoom in) to a static image."""
    if output_path is None:
        base_name = Path(image_path).stem
        output_path = str(config.OUTPUT_DIR / f"{base_name}_ken_burns.mp4")
    
    logger.info(f"Applying Ken Burns effect to {image_path}")
    
    img = Image.open(image_path)
    img_width, img_height = img.size
    target_width = config.VIDEO_SETTINGS["width"]
    target_height = config.VIDEO_SETTINGS["height"]
    fps = config.VIDEO_SETTINGS["fps"]
    
    scale_w = (target_width * zoom_ratio) / img_width
    scale_h = (target_height * zoom_ratio) / img_height
    base_scale = max(scale_w, scale_h)
    
    new_width = int(img_width * base_scale)
    new_height = int(img_height * base_scale)
    img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
    img_array = np.array(img_resized)
    
    def make_frame(t):
        progress = t / duration
        eased_progress = 0.5 - 0.5 * np.cos(np.pi * progress)
        current_zoom = 1 + (zoom_ratio - 1) * eased_progress
        
        crop_width = int(target_width / current_zoom * (new_width / target_width))
        crop_height = int(target_height / current_zoom * (new_height / target_height))
        
        crop_width = min(crop_width, new_width)
        crop_height = min(crop_height, new_height)
        
        x_start = (new_width - crop_width) // 2
        y_start = (new_height - crop_height) // 2
        
        cropped = img_array[y_start:y_start + crop_height, x_start:x_start + crop_width]
        cropped_pil = Image.fromarray(cropped)
        resized = cropped_pil.resize((target_width, target_height), Image.Resampling.LANCZOS)
        return np.array(resized)
    
    clip = ImageClip(make_frame, duration=duration)
    clip = clip.set_fps(fps)
    clip.write_videofile(output_path, fps=fps, codec=config.VIDEO_SETTINGS["codec"], audio=False, logger=None)
    clip.close()
    
    return output_path
