import logging
import os
from datetime import date

from PIL import Image as PILImage, ImageDraw, ImageFont

import config

logger = logging.getLogger(__name__)


def _get_font(size: int) -> ImageFont.FreeTypeFont:
    try:
        return ImageFont.truetype(
            "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", size
        )
    except OSError:
        pass
    try:
        return ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size, index=1)
    except OSError:
        pass
    try:
        return ImageFont.truetype("/System/Library/Fonts/HelveticaNeue.ttc", size, index=1)
    except OSError:
        pass
    return ImageFont.load_default()


def _truncate_to_width(
    text: str, font: ImageFont.FreeTypeFont, max_width: int
) -> str:
    dummy = PILImage.new("RGBA", (1, 1))
    draw = ImageDraw.Draw(dummy)
    bbox = draw.textbbox((0, 0), text, font=font)
    if (bbox[2] - bbox[0]) <= max_width:
        return text
    ellipsis = "\u2026"
    for i in range(len(text) - 1, 0, -1):
        candidate = text[:i] + ellipsis
        bbox = draw.textbbox((0, 0), candidate, font=font)
        if (bbox[2] - bbox[0]) <= max_width:
            return candidate
    return ellipsis


def _draw_text_overlays(
    draw: ImageDraw.ImageDraw,
    width: int,
    height: int,
    week_start: date,
    week_end: date,
    headlines: list[str],
) -> None:
    title_font = _get_font(38)
    date_font = _get_font(20)
    headline_font = _get_font(18)

    draw.rectangle([(0, 0), (width, 90)], fill=(0, 0, 0, 204))
    draw.text(
        (width // 2, 20), "Plymouth Independent",
        fill="white", font=title_font, anchor="mt",
    )

    iso_week = week_start.isocalendar()[1]
    date_str = (
        f"Week {iso_week} \u00b7 "
        f"{week_start.strftime('%b %d')} \u2013 {week_end.strftime('%b %d, %Y')}"
    )
    draw.text(
        (width // 2, 62), date_str,
        fill=(220, 220, 220), font=date_font, anchor="mt",
    )

    if headlines:
        max_headlines = min(len(headlines), 5)
        strip_height = 28 + max_headlines * 26
        strip_top = height - strip_height
        draw.rectangle(
            [(0, strip_top), (width, height)], fill=(0, 0, 0, 170)
        )
        max_width = width - 32
        y = strip_top + 14
        for headline in headlines[:max_headlines]:
            text = f"\u2022 {headline}"
            text = _truncate_to_width(text, headline_font, max_width)
            draw.text((16, y), text, fill="white", font=headline_font)
            y += 26


def generate_programmatic_cover(
    output_dir: str,
    week_start: date,
    week_end: date,
    headlines: list[str],
    categories: list[str],
) -> str:
    os.makedirs(output_dir, exist_ok=True)
    w, h = config.COVER_SIZE
    img = PILImage.new("RGBA", (w, h), color=(128, 128, 128, 255))
    draw = ImageDraw.Draw(img)

    _draw_text_overlays(draw, w, h, week_start, week_end, headlines)

    out = img.convert("RGB")
    filename = f"cover-{week_start.isoformat()}-text.jpg"
    path = os.path.join(output_dir, filename)
    out.save(path, format="JPEG", progressive=False, quality=90)
    return path


def generate_mosaic_cover(
    output_dir: str,
    week_start: date,
    week_end: date,
    headlines: list[str],
    image_paths: list[str],
) -> str:
    os.makedirs(output_dir, exist_ok=True)
    w, h = config.COVER_SIZE
    img = PILImage.new("RGBA", (w, h), color=(128, 128, 128, 255))

    bottom_headlines_height = 0
    if headlines:
        bottom_headlines_height = 28 + min(len(headlines), 5) * 26
    
    mosaic_h = h - 90 - bottom_headlines_height
    mosaic_canvas = PILImage.new("RGBA", (w, mosaic_h), color=(0, 0, 0, 255))

    if len(image_paths) == 1:
        # Single image case
        try:
            with PILImage.open(image_paths[0]) as source_img:
                source_img = source_img.convert("RGBA")
                # Resize to fit within 480x(mosaic_h) preserving aspect ratio
                ratio = min(w / source_img.width, mosaic_h / source_img.height)
                new_size = (int(source_img.width * ratio), int(source_img.height * ratio))
                resized = source_img.resize(new_size, PILImage.Resampling.LANCZOS)
                # Center it
                x = (w - new_size[0]) // 2
                y = (mosaic_h - new_size[1]) // 2
                mosaic_canvas.paste(resized, (x, y), resized)
        except Exception as e:
            logger.error("Failed to process single image for cover: %s", e)
    elif len(image_paths) > 1:
        # Mosaic case
        col_width = (w - (3 * 4)) // 2
        col_heights = [4, 4]

        for path in image_paths:
            if col_heights[0] > mosaic_h and col_heights[1] > mosaic_h:
                break  # Both columns full

            try:
                with PILImage.open(path) as source_img:
                    source_img = source_img.convert("RGBA")
                    new_h = int(source_img.height * (col_width / source_img.width))
                    resized = source_img.resize((col_width, new_h), PILImage.Resampling.LANCZOS)

                    col_idx = 0 if col_heights[0] <= col_heights[1] else 1
                    x = 4 + col_idx * (col_width + 4)
                    y = col_heights[col_idx]

                    mosaic_canvas.paste(resized, (x, y), resized)
                    col_heights[col_idx] += new_h + 4
            except Exception as e:
                logger.error("Failed to process image %s for mosaic: %s", path, e)

    img.paste(mosaic_canvas, (0, 90))

    draw = ImageDraw.Draw(img)
    _draw_text_overlays(draw, w, h, week_start, week_end, headlines)

    out = img.convert("RGB")
    filename = f"cover-{week_start.isoformat()}-mosaic.jpg"
    path = os.path.join(output_dir, filename)
    out.save(path, format="JPEG", progressive=False, quality=90)
    return path


def generate_cover(
    method: str,
    output_dir: str,
    week_start: date,
    week_end: date,
    headlines: list[str],
    image_paths: list[str],
) -> str:
    if method == "mosaic":
        return generate_mosaic_cover(
            output_dir, week_start, week_end, headlines, image_paths
        )
    # Fallback to programmatic text cover (ignore image_paths)
    return generate_programmatic_cover(
        output_dir, week_start, week_end, headlines, []
    )

