import math
import base64
import logging
import io
import datetime
from pathlib import Path
from typing import Any, Dict, List, Tuple
from PIL import Image, ImageOps
import config
from tools.utils import format_relative_time, ToolError, get_file_info_list, sort_file_list, parse_indices


logger = logging.getLogger("MooseCP")

def calculate_patch_dimensions(orig_w: int, orig_h: int, target_tokens: int = None):
    """
    Calculates optimal pixel dimensions to hit a token budget of at least target_tokens.
    Validates budget based on actual rounded pixel dimensions to avoid float precision errors.
    """
    if target_tokens is None:
        target_tokens = config.PREVIEW_TOKEN_BUDGET
    
    ar = orig_w / orig_h
    
    # Case A: Width is the anchor (non-padded)
    w_anchor = 1
    while True:
        w_px = w_anchor * config.PATCH_SIZE
        h_px = round(w_px / ar)
        # Calculate actual tokens based on resulting pixel dimensions
        tokens = math.ceil(w_px / config.PATCH_SIZE) * math.ceil(h_px / config.PATCH_SIZE)
        if tokens >= target_tokens:
            res_a = {"w": w_px, "h": h_px, "tokens": tokens}
            break
        w_anchor += 1

    # Case B: Height is the anchor (non-padded)
    h_anchor = 1
    while True:
        h_px = h_anchor * config.PATCH_SIZE
        w_px = round(h_px * ar)
        # Calculate actual tokens based on resulting pixel dimensions
        tokens = math.ceil(w_px / config.PATCH_SIZE) * math.ceil(h_px / config.PATCH_SIZE)
        if tokens >= target_tokens:
            res_b = {"w": w_px, "h": h_px, "tokens": tokens}
            break
        h_anchor += 1

    # Pick the one with the smaller token count
    best = res_a if res_a["tokens"] <= res_b["tokens"] else res_b
    return max(1, best["w"]), max(1, best["h"])

async def handle(args: Dict[str, Any]):
    """
    Generates small thumbnails of images with detailed info.
    Can take a single file path, or a directory with indices/ranges from contact_sheet.
    Thumbnails are optimized for ~70 token usage.
    """
    path_str = args.get("path")
    indices_str = args.get("indices")
    sort_by = args.get("sort_by", "mtime")
    
    if not path_str:
        raise ToolError("Missing path argument")
    
    path = Path(path_str)
    # Store as (path, index_label) where index_label is None if direct path
    target_files: List[Tuple[Path, Any]] = []

    if path.is_file():
        target_files.append((path, None))
    elif path.is_dir():
        if not indices_str:
            raise ToolError("Indices are required when providing a directory path. Example: '1, 5-10'")
        
        exts = ('.png', '.jpg', '.jpeg', '.webp', '.bmp')
        file_info_list = get_file_info_list(path, extensions=exts)
        file_info_list = sort_file_list(file_info_list, sort_by)
        
        indices = parse_indices(indices_str)
        for idx in indices:
            # contact_sheet uses 1-based indexing
            if 1 <= idx <= len(file_info_list):
                target_files.append((file_info_list[idx-1]["path"], idx))
            else:
                logger.warning(f"Index {idx} out of range for directory {path_str}")
    else:
        raise ToolError(f"Path {path_str} is neither a file nor a directory.")

    if not target_files:
        return {"text": "No valid images found to preview."}

    results = []
    for file_path, index_label in target_files:
        try:
            stats = file_path.stat()
            with Image.open(file_path) as img:
                # Apply EXIF orientation to fix sideways images
                img = ImageOps.exif_transpose(img)
                orig_w, orig_h = img.size
                target_w, target_h = calculate_patch_dimensions(orig_w, orig_h)
                
                # Use thumbnail() to preserve aspect ratio and fit within the target bounding box.
                thumb = img.convert("RGB")
                thumb.thumbnail((target_w, target_h), Image.Resampling.LANCZOS)
                
                buf = io.BytesIO()
                thumb.save(buf, format='JPEG', quality=config.IMAGE_QUALITY)
                img_data = base64.b64encode(buf.getvalue()).decode("utf-8")
                
                # Gather info
                mtime_rel = format_relative_time(stats.st_mtime)
                mtime_abs = datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')
                size_kb = stats.st_size / 1024
                
                # Prepend index if this image was selected via index/range
                idx_prefix = f"[Index: {index_label}] " if index_label is not None else ""
                
                info_text = (
                    f"{idx_prefix}File: {file_path.name}\n"
                    f"Resolution: {orig_w}x{orig_h}\n"
                    f"Size: {size_kb:.1f}KB\n"
                    f"Modified: {mtime_rel} ({mtime_abs})"
                )
                
                results.append({"type": "text", "text": info_text})
                results.append({"type": "image", "data": img_data, "mimeType": "image/jpeg"})
                
        except Exception as e:
            results.append({"type": "text", "text": f"Error processing {file_path.name}: {e}"})

    return results
