import json
import os
from datetime import date
from unittest.mock import patch
from io import BytesIO
from PIL import Image as PILImage, ImageDraw, ImageFont

from src.cover import (
    generate_programmatic_cover,
    generate_mosaic_cover,
    generate_cover,
    _truncate_to_width,
    _get_font,
)


def test_programmatic_cover_is_portrait(tmp_path):
    path = generate_programmatic_cover(
        output_dir=str(tmp_path),
        week_start=date(2026, 4, 6),
        week_end=date(2026, 4, 12),
        headlines=["Article One", "Article Two", "Article Three"],
        categories=["Government", "Government", "Culture"],
    )

    assert os.path.exists(path)
    img = PILImage.open(path)
    assert img.format == "JPEG"
    assert img.size == (480, 800)


def test_programmatic_cover_no_headlines(tmp_path):
    path = generate_programmatic_cover(
        output_dir=str(tmp_path),
        week_start=date(2026, 4, 6),
        week_end=date(2026, 4, 12),
        headlines=[],
        categories=[],
    )

    assert os.path.exists(path)
    img = PILImage.open(path)
    assert img.size == (480, 800)


def test_generate_mosaic_cover_single_image(tmp_path):
    # Create a dummy image
    img_path = str(tmp_path / "dummy.jpg")
    img = PILImage.new("RGB", (1000, 500), color="red")
    img.save(img_path)

    output_dir = str(tmp_path)
    week_start = date(2026, 4, 6)
    week_end = date(2026, 4, 12)
    headlines = ["Test Headline"]

    cover_path = generate_mosaic_cover(output_dir, week_start, week_end, headlines, [img_path])

    assert os.path.exists(cover_path)
    with PILImage.open(cover_path) as result_img:
        assert result_img.size == (480, 800)


def test_generate_cover_dispatches(tmp_path):
    with patch("src.cover.generate_mosaic_cover") as mock_mosaic:
        mock_mosaic.return_value = "/fake/mosaic.jpg"
        result = generate_cover("mosaic", str(tmp_path), date(2026, 4, 6),
                                date(2026, 4, 12), ["A"], ["/x/a.jpg"])
        assert result == "/fake/mosaic.jpg"
        mock_mosaic.assert_called_once()

    with patch("src.cover.generate_programmatic_cover") as mock_prog:
        mock_prog.return_value = "/fake/text.jpg"
        result = generate_cover("text", str(tmp_path), date(2026, 4, 6),
                                date(2026, 4, 12), ["A"], ["Government"])
        assert result == "/fake/text.jpg"
        mock_prog.assert_called_once()


def test_truncate_to_width_respects_pixel_budget():
    font = _get_font(18)
    short = "Short text"
    result = _truncate_to_width(short, font, 400)
    assert result == short, "Short text should not be truncated"

    long = "A" * 200
    result = _truncate_to_width(long, font, 400)
    assert result.endswith("\u2026"), "Long text should end with ellipsis"
    assert len(result) < len(long), "Truncated text should be shorter"


def test_truncate_to_width_fits_within_budget():
    font = _get_font(18)
    long = "This is a really long headline that should definitely be truncated to fit"
    max_width = 448
    result = _truncate_to_width(long, font, max_width)

    dummy = PILImage.new("RGBA", (1, 1))
    draw = ImageDraw.Draw(dummy)
    bbox = draw.textbbox((0, 0), result, font=font)
    rendered_width = bbox[2] - bbox[0]
    assert rendered_width <= max_width, f"Rendered width {rendered_width} exceeds {max_width}"


def test_obituary_headlines_filtered():
    """Verify the filtering pattern used by publish/issues/scheduler call sites."""
    class FakeArticle:
        def __init__(self, title, categories):
            self.title = title
            self.categories = json.dumps(categories)

    articles = [
        FakeArticle("Town Meeting Approves Budget", ["Government"]),
        FakeArticle("John Smith, 85, beloved teacher", ["Obituaries"]),
        FakeArticle("Harbor Festival This Weekend", ["Culture"]),
        FakeArticle("Jane Doe, 92, retired nurse", ["Obituaries"]),
        FakeArticle("Panthers Win Championship", ["Sports"]),
    ]

    headlines = [
        a.title for a in articles
        if "Obituaries" not in json.loads(a.categories)
    ]

    assert len(headlines) == 3
    assert "John Smith, 85, beloved teacher" not in headlines
    assert "Jane Doe, 92, retired nurse" not in headlines
    assert "Town Meeting Approves Budget" in headlines
    assert "Harbor Festival This Weekend" in headlines
    assert "Panthers Win Championship" in headlines
