"""
Tests for Microformats2 markup in templates

Per v1.2.0 Phase 2 and developer Q&A Q31-Q33:
- Use mf2py to validate generated HTML
- Test h-entry, h-card, h-feed markup
- Ensure all required properties present
- Validate p-name only appears with explicit titles (per Q22)
"""

import mf2py
import pytest
from unittest.mock import patch


class TestNoteHEntry:
    """Test h-entry markup on individual note pages"""

    def test_note_has_hentry_markup(self, client, app, sample_note):
        """Note page has h-entry container"""
        # Sample note is already published, just get its slug
        response = client.get(f'/note/{sample_note.slug}')
        assert response.status_code == 200, f"Failed to load note at /note/{sample_note.slug}"

        # Parse microformats
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        # Should have at least one h-entry
        entries = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])]
        assert len(entries) >= 1

    def test_hentry_has_required_properties(self, client, app, sample_note):
        """h-entry has all required Microformats2 properties"""
        response = client.get(f'/note/{sample_note.slug}')
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        entry = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])][0]
        props = entry.get('properties', {})

        # Required properties per spec
        assert 'url' in props, "h-entry missing u-url"
        assert 'published' in props, "h-entry missing dt-published"
        assert 'content' in props, "h-entry missing e-content"
        assert 'author' in props, "h-entry missing p-author"

    def test_hentry_url_and_uid_match(self, client, app, sample_note):
        """u-url and u-uid are the same for notes (per Q23)"""
        response = client.get(f'/note/{sample_note.slug}')
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        entry = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])][0]
        props = entry.get('properties', {})

        # Both should exist and match
        assert 'url' in props
        assert 'uid' in props
        assert props['url'][0] == props['uid'][0], "u-url and u-uid should match"

    def test_hentry_pname_only_with_explicit_title(self, client, app, data_dir):
        """p-name only present when note has explicit title (per Q22)"""
        from starpunk.notes import create_note

        # Create note WITH heading (explicit title)
        with app.app_context():
            note_with_title = create_note(
                content="# Explicit Title\n\nThis note has a heading.",
                custom_slug="note-with-title",
                published=True
            )

        response = client.get('/note/note-with-title')
        assert response.status_code == 200, f"Failed to get note: {response.status_code}"
        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/note/note-with-title')

        entries = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])]
        assert len(entries) > 0, "No h-entry found in parsed HTML"
        entry = entries[0]
        props = entry.get('properties', {})

        # Should have p-name
        assert 'name' in props, "Note with explicit title should have p-name"
        assert props['name'][0] == 'Explicit Title'

        # Create note WITHOUT heading (no explicit title)
        with app.app_context():
            note_without_title = create_note(
                content="Just a simple note without a heading.",
                custom_slug="note-without-title",
                published=True
            )

        response = client.get('/note/note-without-title')
        assert response.status_code == 200, f"Failed to get note: {response.status_code}"
        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/note/note-without-title')

        entries = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])]
        assert len(entries) > 0, "No h-entry found in parsed HTML"
        entry = entries[0]
        props = entry.get('properties', {})

        # Should NOT have explicit p-name (or it should be implicit from content)
        # Per Q22: p-name only if has_explicit_title
        # If p-name exists, it shouldn't be set explicitly in our markup
        # (mf2py may infer it from content, but we shouldn't add class="p-name")

    def test_hentry_has_updated_if_modified(self, client, app, sample_note, data_dir):
        """dt-updated present if note was modified"""
        from starpunk.notes import update_note
        from pathlib import Path
        import time

        # Update the note
        time.sleep(0.1)  # Ensure different timestamp
        with app.app_context():
            update_note(sample_note.slug, content="Updated content")

        response = client.get(f'/note/{sample_note.slug}')
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        entry = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])][0]
        props = entry.get('properties', {})

        # Should have dt-updated
        assert 'updated' in props, "Modified note should have dt-updated"


class TestAuthorHCard:
    """Test h-card markup for author"""

    def test_hentry_has_nested_hcard(self, client, app, sample_note):
        """h-entry has nested p-author h-card (per Q20)"""
        # Mock author profile in context
        mock_author = {
            'me': 'https://author.example.com',
            'name': 'Test Author',
            'photo': 'https://example.com/photo.jpg',
            'url': 'https://author.example.com',
            'note': 'Test bio',
            'rel_me_links': [],
        }

        with patch('starpunk.author_discovery.get_author_profile', return_value=mock_author):
            response = client.get(f'/note/{sample_note.slug}')

        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        entry = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])][0]
        props = entry.get('properties', {})

        # Should have p-author
        assert 'author' in props, "h-entry should have p-author"

        # Author should be h-card
        author = props['author'][0]
        if isinstance(author, dict):
            assert 'h-card' in author.get('type', []), "p-author should be h-card"
            author_props = author.get('properties', {})
            assert 'name' in author_props, "h-card should have p-name"
            assert author_props['name'][0] == 'Test Author'

    def test_hcard_not_standalone(self, client, app, sample_note):
        """h-card only within h-entry, not standalone (per Q20)"""
        response = client.get(f'/note/{sample_note.slug}')
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        # Find all h-cards at root level
        root_hcards = [item for item in parsed['items'] if 'h-card' in item.get('type', [])]

        # Should NOT have root-level h-cards (only nested in h-entry)
        # Note: This might not be strictly enforced by mf2py parsing,
        # but we can check that h-entry exists and contains h-card
        entries = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])]
        assert len(entries) > 0, "Should have h-entry"

    def test_hcard_has_required_properties(self, client, app, sample_note):
        """h-card has name and url at minimum"""
        mock_author = {
            'me': 'https://author.example.com',
            'name': 'Test Author',
            'photo': None,
            'url': 'https://author.example.com',
            'note': None,
            'rel_me_links': [],
        }

        with patch('starpunk.author_discovery.get_author_profile', return_value=mock_author):
            response = client.get(f'/note/{sample_note.slug}')

        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{sample_note.slug}')

        entry = [item for item in parsed['items'] if 'h-entry' in item.get('type', [])][0]
        props = entry.get('properties', {})
        author = props['author'][0]

        if isinstance(author, dict):
            author_props = author.get('properties', {})
            assert 'name' in author_props, "h-card must have p-name"
            assert 'url' in author_props, "h-card must have u-url"


class TestFeedHFeed:
    """Test h-feed markup on index page"""

    def test_index_has_hfeed(self, client, app):
        """Index page has h-feed container (per Q24)"""
        response = client.get('/')
        assert response.status_code == 200

        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        # Should have h-feed
        feeds = [item for item in parsed['items'] if 'h-feed' in item.get('type', [])]
        assert len(feeds) >= 1, "Index should have h-feed"

    def test_hfeed_has_name(self, client, app):
        """h-feed has p-name (feed title)"""
        response = client.get('/')
        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        feed = [item for item in parsed['items'] if 'h-feed' in item.get('type', [])][0]
        props = feed.get('properties', {})

        assert 'name' in props, "h-feed should have p-name"

    def test_hfeed_contains_hentries(self, client, app, sample_note):
        """h-feed contains h-entry children"""
        response = client.get('/')
        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        feed = [item for item in parsed['items'] if 'h-feed' in item.get('type', [])][0]

        # Should have children (h-entries)
        children = feed.get('children', [])
        entries = [child for child in children if 'h-entry' in child.get('type', [])]

        assert len(entries) > 0, "h-feed should contain h-entry children"

    def test_feed_entries_have_author(self, client, app, sample_note):
        """Each h-entry in feed has p-author h-card (per Q20)"""
        mock_author = {
            'me': 'https://author.example.com',
            'name': 'Test Author',
            'photo': None,
            'url': 'https://author.example.com',
            'note': None,
            'rel_me_links': [],
        }

        with patch('starpunk.author_discovery.get_author_profile', return_value=mock_author):
            response = client.get('/')

        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        feed = [item for item in parsed['items'] if 'h-feed' in item.get('type', [])][0]
        children = feed.get('children', [])
        entries = [child for child in children if 'h-entry' in child.get('type', [])]

        # Each entry should have author
        for entry in entries:
            props = entry.get('properties', {})
            assert 'author' in props, "Feed h-entry should have p-author"


class TestRelMe:
    """Test rel-me links in HTML head"""

    def test_relme_links_in_head(self, client, app):
        """rel=me links present in HTML head (per Q20)"""
        mock_author = {
            'me': 'https://author.example.com',
            'name': 'Test Author',
            'photo': None,
            'url': 'https://author.example.com',
            'note': None,
            'rel_me_links': [
                'https://github.com/testuser',
                'https://mastodon.social/@testuser',
            ],
        }

        with patch('starpunk.author_discovery.get_author_profile', return_value=mock_author):
            response = client.get('/')

        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        # Check rel=me in rels
        rels = parsed.get('rels', {})
        assert 'me' in rels, "Should have rel=me links"
        assert 'https://github.com/testuser' in rels['me']
        assert 'https://mastodon.social/@testuser' in rels['me']

    def test_no_relme_without_author(self, client, app):
        """No rel=me links if no author profile"""
        with patch('starpunk.author_discovery.get_author_profile', return_value=None):
            response = client.get('/')

        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        rels = parsed.get('rels', {})
        # Should not have rel=me, or it should be empty
        assert len(rels.get('me', [])) == 0, "Should not have rel=me without author"


class TestV130Microformats:
    """v1.3.0 microformats2 compliance tests"""

    def test_hfeed_has_required_properties(self, client, app):
        """h-feed has name, author, url per spec (v1.3.0)"""
        mock_author = {
            'me': 'https://author.example.com',
            'name': 'Test Author',
            'photo': 'https://example.com/photo.jpg',
            'url': 'https://author.example.com',
            'note': 'Test bio',
            'rel_me_links': [],
        }

        with patch('starpunk.author_discovery.get_author_profile', return_value=mock_author):
            response = client.get('/')

        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        hfeed = [i for i in parsed['items'] if 'h-feed' in i.get('type', [])][0]
        props = hfeed.get('properties', {})

        assert 'name' in props, "h-feed must have p-name"
        assert 'author' in props, "h-feed must have p-author"
        assert 'url' in props, "h-feed must have u-url"

    def test_hfeed_author_is_valid_hcard(self, client, app):
        """h-feed author is valid h-card (v1.3.0)"""
        mock_author = {
            'me': 'https://author.example.com',
            'name': 'Test Author',
            'photo': 'https://example.com/photo.jpg',
            'url': 'https://author.example.com',
            'note': 'Test bio',
            'rel_me_links': [],
        }

        with patch('starpunk.author_discovery.get_author_profile', return_value=mock_author):
            response = client.get('/')

        parsed = mf2py.parse(doc=response.data.decode(), url='http://localhost/')

        hfeed = [i for i in parsed['items'] if 'h-feed' in i.get('type', [])][0]
        author = hfeed.get('properties', {}).get('author', [{}])[0]

        assert isinstance(author, dict), "author should be parsed as dict"
        assert 'h-card' in author.get('type', []), "author must be h-card"
        assert 'name' in author.get('properties', {}), "h-card must have name"
        assert 'url' in author.get('properties', {}), "h-card must have url"

    def test_hentry_has_pcategory_for_tags(self, client, app, published_note_with_tags):
        """h-entry has p-category for each tag (v1.3.0)"""
        response = client.get(f'/note/{published_note_with_tags.slug}')
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{published_note_with_tags.slug}')

        hentry = [i for i in parsed['items'] if 'h-entry' in i.get('type', [])][0]
        categories = hentry.get('properties', {}).get('category', [])

        assert len(categories) > 0, "h-entry with tags must have p-category"
        # Check that our test tags are present
        assert 'Python' in categories or 'python' in categories
        assert 'IndieWeb' in categories or 'indieweb' in categories

    def test_uphoto_outside_econtent(self, client, app, published_note_with_media):
        """u-photo is direct child of h-entry, not inside e-content (v1.3.0)"""
        response = client.get(f'/note/{published_note_with_media.slug}')
        parsed = mf2py.parse(doc=response.data.decode(), url=f'http://localhost/note/{published_note_with_media.slug}')

        hentry = [i for i in parsed['items'] if 'h-entry' in i.get('type', [])][0]
        props = hentry.get('properties', {})

        # u-photo should be at h-entry level
        assert 'photo' in props, "h-entry with media must have u-photo"

        # Verify it's not nested in e-content
        content = props.get('content', [{}])[0]
        if isinstance(content, dict):
            content_html = content.get('html', '')
            # u-photo class should NOT appear inside content
            assert 'u-photo' not in content_html, "u-photo should not be inside e-content"
