"""
Tests for RSS 2.0 with Media RSS extension

Tests cover:
- Media RSS namespace declaration
- RSS enclosure element (first image only)
- Media RSS content elements (all images)
- Media RSS thumbnail (first image)
- Items without media have no media elements
- JSON Feed image field
- JSON Feed omits image when no media
"""

import pytest
from datetime import datetime, timezone
from xml.etree import ElementTree as ET
import json
import time
from pathlib import Path
from PIL import Image
import io

from starpunk import create_app
from starpunk.feeds.rss import generate_rss, generate_rss_streaming
from starpunk.feeds.json_feed import generate_json_feed, generate_json_feed_streaming
from starpunk.notes import create_note, list_notes
from starpunk.media import save_media, attach_media_to_note


def create_test_image(width=800, height=600, format='PNG'):
    """
    Generate test image using PIL

    Args:
        width: Image width in pixels
        height: Image height in pixels
        format: Image format (PNG, JPEG, GIF, WEBP)

    Returns:
        Bytes of image data
    """
    img = Image.new('RGB', (width, height), color='blue')
    buffer = io.BytesIO()
    img.save(buffer, format=format)
    buffer.seek(0)
    return buffer.getvalue()


@pytest.fixture
def app(tmp_path):
    """Create test application"""
    test_data_dir = tmp_path / "data"
    test_data_dir.mkdir(parents=True, exist_ok=True)

    # Create media directory
    test_media_dir = test_data_dir / "media"
    test_media_dir.mkdir(parents=True, exist_ok=True)

    test_config = {
        "TESTING": True,
        "DATABASE_PATH": test_data_dir / "starpunk.db",
        "DATA_PATH": test_data_dir,
        "NOTES_PATH": test_data_dir / "notes",
        "MEDIA_PATH": test_media_dir,
        "SESSION_SECRET": "test-secret-key",
        "ADMIN_ME": "https://test.example.com",
        "SITE_URL": "https://example.com",
        "SITE_NAME": "Test Blog",
        "SITE_DESCRIPTION": "A test blog",
        "DEV_MODE": False,
    }
    app = create_app(config=test_config)
    yield app


@pytest.fixture
def note_with_single_media(app):
    """Create note with single image attachment"""
    from starpunk.media import get_note_media

    with app.app_context():
        # Create note
        note = create_note(
            content="# Test Note\n\nNote with one image.",
            published=True
        )

        # Create and attach media
        image_data = create_test_image(800, 600, 'JPEG')
        media_info = save_media(image_data, 'test-image.jpg')
        attach_media_to_note(note.id, [media_info['id']], ['Test caption'])

        # Reload note and attach media
        notes = list_notes(published_only=True, limit=1)
        note = notes[0]
        media = get_note_media(note.id)
        object.__setattr__(note, 'media', media)
        return note


@pytest.fixture
def note_with_multiple_media(app):
    """Create note with multiple image attachments"""
    from starpunk.media import get_note_media

    with app.app_context():
        # Create note
        note = create_note(
            content="# Gallery Note\n\nNote with three images.",
            published=True
        )

        # Create and attach multiple media
        media_ids = []
        captions = []

        for i in range(3):
            image_data = create_test_image(800, 600, 'JPEG')
            media_info = save_media(image_data, f'image-{i}.jpg')
            media_ids.append(media_info['id'])
            captions.append(f'Caption {i}')

        attach_media_to_note(note.id, media_ids, captions)

        # Reload note and attach media
        notes = list_notes(published_only=True, limit=1)
        note = notes[0]
        media = get_note_media(note.id)
        object.__setattr__(note, 'media', media)
        return note


@pytest.fixture
def note_without_media(app):
    """Create note without media"""
    with app.app_context():
        note = create_note(
            content="# Plain Note\n\nNote without images.",
            published=True
        )
        notes = list_notes(published_only=True, limit=1)
        return notes[0]


class TestRSSMediaNamespace:
    """Test RSS feed has Media RSS namespace"""

    def test_rss_has_media_namespace(self, app, note_with_single_media):
        """RSS feed should declare Media RSS namespace"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            # Check for Media RSS namespace declaration
            assert 'xmlns:media="http://search.yahoo.com/mrss/"' in feed_xml

    def test_rss_streaming_has_media_namespace(self, app, note_with_single_media):
        """Streaming RSS feed should declare Media RSS namespace"""
        with app.app_context():
            generator = generate_rss_streaming(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            feed_xml = ''.join(generator)

            # Check for Media RSS namespace declaration
            assert 'xmlns:media="http://search.yahoo.com/mrss/"' in feed_xml


class TestRSSEnclosure:
    """Test RSS enclosure element for first image"""

    def test_rss_enclosure_for_single_media(self, app, note_with_single_media):
        """RSS item should include enclosure element for first image"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            # Parse XML
            root = ET.fromstring(feed_xml)
            channel = root.find("channel")
            item = channel.find("item")
            enclosure = item.find("enclosure")

            # Should have enclosure
            assert enclosure is not None

            # Check attributes
            assert enclosure.get("url") is not None
            assert "https://example.com/media/" in enclosure.get("url")
            assert enclosure.get("type") == "image/jpeg"
            assert enclosure.get("length") is not None

    def test_rss_enclosure_first_image_only(self, app, note_with_multiple_media):
        """RSS item should include only one enclosure (first image)"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_multiple_media]
            )

            # Parse XML
            root = ET.fromstring(feed_xml)
            channel = root.find("channel")
            item = channel.find("item")
            enclosures = item.findall("enclosure")

            # Should have exactly one enclosure (RSS 2.0 spec)
            assert len(enclosures) == 1

    def test_rss_no_enclosure_without_media(self, app, note_without_media):
        """RSS item without media should have no enclosure"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_without_media]
            )

            # Parse XML
            root = ET.fromstring(feed_xml)
            channel = root.find("channel")
            item = channel.find("item")
            enclosure = item.find("enclosure")

            # Should have no enclosure
            assert enclosure is None


class TestRSSMediaContent:
    """Test Media RSS content elements"""

    def test_rss_media_content_for_single_image(self, app, note_with_single_media):
        """RSS item should include media:content for image"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            # Parse XML with namespace
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")
            media_contents = item.findall("media:content", namespaces)

            # Should have one media:content element
            assert len(media_contents) == 1

            # Check attributes
            media_content = media_contents[0]
            assert media_content.get("url") is not None
            assert "https://example.com/media/" in media_content.get("url")
            assert media_content.get("type") == "image/jpeg"
            assert media_content.get("medium") == "image"

    def test_rss_media_content_for_multiple_images(self, app, note_with_multiple_media):
        """RSS item should include media:content for each image"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_multiple_media]
            )

            # Parse XML with namespace
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")
            media_contents = item.findall("media:content", namespaces)

            # Should have three media:content elements
            assert len(media_contents) == 3

    def test_rss_no_media_content_without_media(self, app, note_without_media):
        """RSS item without media should have no media:content elements"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_without_media]
            )

            # Parse XML with namespace
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")
            media_contents = item.findall("media:content", namespaces)

            # Should have no media:content elements
            assert len(media_contents) == 0


class TestRSSMediaThumbnail:
    """Test Media RSS thumbnail element"""

    def test_rss_media_thumbnail_for_first_image(self, app, note_with_single_media):
        """RSS item should include media:thumbnail for first image"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            # Parse XML with namespace
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")
            media_thumbnail = item.find("media:thumbnail", namespaces)

            # Should have media:thumbnail
            assert media_thumbnail is not None
            assert media_thumbnail.get("url") is not None
            assert "https://example.com/media/" in media_thumbnail.get("url")

    def test_rss_media_thumbnail_only_one(self, app, note_with_multiple_media):
        """RSS item should include only one media:thumbnail (first image)"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_multiple_media]
            )

            # Parse XML with namespace
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")
            media_thumbnails = item.findall("media:thumbnail", namespaces)

            # Should have exactly one media:thumbnail
            assert len(media_thumbnails) == 1

    def test_rss_no_media_thumbnail_without_media(self, app, note_without_media):
        """RSS item without media should have no media:thumbnail"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_without_media]
            )

            # Parse XML with namespace
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")
            media_thumbnail = item.find("media:thumbnail", namespaces)

            # Should have no media:thumbnail
            assert media_thumbnail is None


class TestRSSStreamingMedia:
    """Test streaming RSS generation includes media elements"""

    def test_rss_streaming_includes_enclosure(self, app, note_with_single_media):
        """Streaming RSS should include enclosure element"""
        with app.app_context():
            generator = generate_rss_streaming(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            feed_xml = ''.join(generator)

            # Parse and check for enclosure
            root = ET.fromstring(feed_xml)
            channel = root.find("channel")
            item = channel.find("item")
            enclosure = item.find("enclosure")

            assert enclosure is not None

    def test_rss_streaming_includes_media_elements(self, app, note_with_single_media):
        """Streaming RSS should include media:content and media:thumbnail (v1.4.0 with variants)"""
        with app.app_context():
            generator = generate_rss_streaming(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            feed_xml = ''.join(generator)

            # Parse and check for media elements
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")

            # v1.4.0: media:content is now inside media:group for images with variants
            # Use // to search descendants, not direct children
            media_content = item.find(".//media:content", namespaces)
            media_thumbnail = item.find(".//media:thumbnail", namespaces)

            assert media_content is not None
            assert media_thumbnail is not None


class TestJSONFeedImage:
    """Test JSON Feed image field"""

    def test_json_feed_has_image_field(self, app, note_with_single_media):
        """JSON Feed item should include image field for first image"""
        with app.app_context():
            feed_json = generate_json_feed(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            feed = json.loads(feed_json)
            item = feed["items"][0]

            # Should have image field
            assert "image" in item
            assert item["image"] is not None
            assert "https://example.com/media/" in item["image"]

    def test_json_feed_image_uses_first_media(self, app, note_with_multiple_media):
        """JSON Feed image field should use first media item URL"""
        from starpunk.media import get_note_media

        with app.app_context():
            feed_json = generate_json_feed(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_multiple_media]
            )

            feed = json.loads(feed_json)
            item = feed["items"][0]

            # Should have image field with first image
            assert "image" in item

            # Verify it's the first media item from the note
            # (Media is saved with UUID filenames, so we can't check for "image-0.jpg")
            media = note_with_multiple_media.media
            first_media_path = media[0]['path']
            assert first_media_path in item["image"]

    def test_json_feed_no_image_field_without_media(self, app, note_without_media):
        """JSON Feed item without media should not have image field"""
        with app.app_context():
            feed_json = generate_json_feed(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_without_media]
            )

            feed = json.loads(feed_json)
            item = feed["items"][0]

            # Should NOT have image field (per Q7: absent, not null)
            assert "image" not in item

    def test_json_feed_streaming_has_image_field(self, app, note_with_single_media):
        """Streaming JSON Feed should include image field"""
        with app.app_context():
            generator = generate_json_feed_streaming(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            feed_json = ''.join(generator)
            feed = json.loads(feed_json)
            item = feed["items"][0]

            # Should have image field
            assert "image" in item
            assert "https://example.com/media/" in item["image"]

    def test_json_feed_streaming_no_image_without_media(self, app, note_without_media):
        """Streaming JSON Feed without media should omit image field"""
        with app.app_context():
            generator = generate_json_feed_streaming(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_without_media]
            )

            feed_json = ''.join(generator)
            feed = json.loads(feed_json)
            item = feed["items"][0]

            # Should NOT have image field
            assert "image" not in item


class TestFeedMediaIntegration:
    """Integration tests for media in feeds"""

    def test_rss_media_and_html_both_present(self, app, note_with_single_media):
        """RSS should include both media elements AND HTML img tags"""
        with app.app_context():
            feed_xml = generate_rss(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            # Parse XML
            root = ET.fromstring(feed_xml)
            namespaces = {'media': 'http://search.yahoo.com/mrss/'}
            channel = root.find("channel")
            item = channel.find("item")

            # Should have media:content
            media_content = item.find("media:content", namespaces)
            assert media_content is not None

            # Should also have HTML img in description
            description = item.find("description").text
            assert '<img' in description

    def test_json_feed_image_and_attachments_both_present(self, app, note_with_single_media):
        """JSON Feed should include both image field AND attachments array"""
        with app.app_context():
            feed_json = generate_json_feed(
                site_url="https://example.com",
                site_name="Test Blog",
                site_description="A test blog",
                notes=[note_with_single_media]
            )

            feed = json.loads(feed_json)
            item = feed["items"][0]

            # Should have image field
            assert "image" in item

            # Should also have attachments array
            assert "attachments" in item
            assert len(item["attachments"]) > 0
