"""
Tests for feed content negotiation

This module tests the content negotiation functionality for determining
which feed format to serve based on HTTP Accept headers.
"""

import pytest
from starpunk.feeds.negotiation import (
    negotiate_feed_format,
    get_mime_type,
    _parse_accept_header,
    _score_format,
    MIME_TYPES,
)


class TestParseAcceptHeader:
    """Tests for Accept header parsing"""

    def test_single_type(self):
        """Parse single media type without quality"""
        result = _parse_accept_header('application/json')
        assert result == [('application/json', 1.0)]

    def test_multiple_types(self):
        """Parse multiple media types"""
        result = _parse_accept_header('application/json, text/html')
        assert len(result) == 2
        assert ('application/json', 1.0) in result
        assert ('text/html', 1.0) in result

    def test_quality_factors(self):
        """Parse quality factors correctly"""
        result = _parse_accept_header('application/json;q=0.9, text/html;q=0.8')
        assert result == [('application/json', 0.9), ('text/html', 0.8)]

    def test_quality_sorting(self):
        """Media types sorted by quality (highest first)"""
        result = _parse_accept_header('text/html;q=0.5, application/json;q=0.9')
        assert result[0] == ('application/json', 0.9)
        assert result[1] == ('text/html', 0.5)

    def test_default_quality_1_0(self):
        """Media type without quality defaults to 1.0"""
        result = _parse_accept_header('application/json;q=0.8, text/html')
        assert result[0] == ('text/html', 1.0)
        assert result[1] == ('application/json', 0.8)

    def test_wildcard(self):
        """Parse wildcard */* correctly"""
        result = _parse_accept_header('*/*')
        assert result == [('*/*', 1.0)]

    def test_wildcard_with_quality(self):
        """Parse wildcard with quality factor"""
        result = _parse_accept_header('application/json, */*;q=0.1')
        assert result == [('application/json', 1.0), ('*/*', 0.1)]

    def test_whitespace_handling(self):
        """Handle whitespace around commas and semicolons"""
        result = _parse_accept_header('application/json ; q=0.9 , text/html')
        assert len(result) == 2
        assert ('application/json', 0.9) in result
        assert ('text/html', 1.0) in result

    def test_empty_string(self):
        """Handle empty Accept header"""
        result = _parse_accept_header('')
        assert result == []

    def test_invalid_quality(self):
        """Invalid quality factor defaults to 1.0"""
        result = _parse_accept_header('application/json;q=invalid')
        assert result == [('application/json', 1.0)]

    def test_quality_clamping(self):
        """Quality factors clamped to 0-1 range"""
        result = _parse_accept_header('application/json;q=1.5')
        assert result == [('application/json', 1.0)]

    def test_type_wildcard(self):
        """Parse type wildcard application/* correctly"""
        result = _parse_accept_header('application/*')
        assert result == [('application/*', 1.0)]


class TestScoreFormat:
    """Tests for format scoring"""

    def test_exact_match(self):
        """Exact MIME type match gets full quality"""
        media_types = [('application/atom+xml', 1.0)]
        score = _score_format('atom', media_types)
        assert score == 1.0

    def test_wildcard_match(self):
        """Wildcard */* matches any format"""
        media_types = [('*/*', 0.8)]
        score = _score_format('rss', media_types)
        assert score == 0.8

    def test_type_wildcard_match(self):
        """Type wildcard application/* matches application types"""
        media_types = [('application/*', 0.9)]
        score = _score_format('atom', media_types)
        assert score == 0.9

    def test_no_match(self):
        """No matching media type returns 0"""
        media_types = [('text/html', 1.0)]
        score = _score_format('rss', media_types)
        assert score == 0.0

    def test_best_quality_wins(self):
        """Return highest quality among matches"""
        media_types = [
            ('*/*', 0.5),
            ('application/*', 0.8),
            ('application/rss+xml', 1.0),
        ]
        score = _score_format('rss', media_types)
        assert score == 1.0

    def test_invalid_format(self):
        """Invalid format name returns 0"""
        media_types = [('*/*', 1.0)]
        score = _score_format('invalid', media_types)
        assert score == 0.0


class TestNegotiateFeedFormat:
    """Tests for feed format negotiation"""

    def test_rss_exact_match(self):
        """Exact match for RSS"""
        result = negotiate_feed_format('application/rss+xml', ['rss', 'atom', 'json'])
        assert result == 'rss'

    def test_atom_exact_match(self):
        """Exact match for ATOM"""
        result = negotiate_feed_format('application/atom+xml', ['rss', 'atom', 'json'])
        assert result == 'atom'

    def test_json_feed_exact_match(self):
        """Exact match for JSON Feed"""
        result = negotiate_feed_format('application/feed+json', ['rss', 'atom', 'json'])
        assert result == 'json'

    def test_json_generic_match(self):
        """Generic application/json matches JSON Feed"""
        result = negotiate_feed_format('application/json', ['rss', 'atom', 'json'])
        assert result == 'json'

    def test_wildcard_defaults_to_rss(self):
        """Wildcard */* defaults to RSS"""
        result = negotiate_feed_format('*/*', ['rss', 'atom', 'json'])
        assert result == 'rss'

    def test_quality_factor_selection(self):
        """Higher quality factor wins"""
        result = negotiate_feed_format(
            'application/atom+xml;q=0.9, application/rss+xml;q=0.5',
            ['rss', 'atom', 'json']
        )
        assert result == 'atom'

    def test_tie_prefers_rss(self):
        """On quality tie, prefer RSS"""
        result = negotiate_feed_format(
            'application/atom+xml;q=0.9, application/rss+xml;q=0.9',
            ['rss', 'atom', 'json']
        )
        assert result == 'rss'

    def test_tie_prefers_atom_over_json(self):
        """On quality tie, prefer ATOM over JSON"""
        result = negotiate_feed_format(
            'application/atom+xml;q=0.9, application/feed+json;q=0.9',
            ['atom', 'json']
        )
        assert result == 'atom'

    def test_no_acceptable_format_raises(self):
        """No acceptable format raises ValueError"""
        with pytest.raises(ValueError, match="No acceptable format found"):
            negotiate_feed_format('text/html', ['rss', 'atom', 'json'])

    def test_only_rss_available(self):
        """Negotiate when only RSS is available"""
        result = negotiate_feed_format('application/rss+xml', ['rss'])
        assert result == 'rss'

    def test_wildcard_with_limited_formats(self):
        """Wildcard picks RSS even if not first in list"""
        result = negotiate_feed_format('*/*', ['atom', 'json', 'rss'])
        assert result == 'rss'

    def test_complex_accept_header(self):
        """Complex Accept header with multiple types and qualities"""
        result = negotiate_feed_format(
            'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',
            ['rss', 'atom', 'json']
        )
        # application/xml doesn't match, so falls back to */* which gives RSS
        assert result == 'rss'

    def test_browser_like_accept(self):
        """Browser-like Accept header defaults to RSS"""
        result = negotiate_feed_format(
            'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            ['rss', 'atom', 'json']
        )
        assert result == 'rss'

    def test_feed_reader_accept(self):
        """Feed reader requesting ATOM"""
        result = negotiate_feed_format(
            'application/atom+xml, application/rss+xml;q=0.9',
            ['rss', 'atom', 'json']
        )
        assert result == 'atom'

    def test_json_api_client(self):
        """JSON API client requesting JSON"""
        result = negotiate_feed_format(
            'application/json, */*;q=0.1',
            ['rss', 'atom', 'json']
        )
        assert result == 'json'

    def test_type_wildcard_application(self):
        """application/* matches all feed formats, prefers RSS"""
        result = negotiate_feed_format(
            'application/*',
            ['rss', 'atom', 'json']
        )
        assert result == 'rss'

    def test_empty_accept_header(self):
        """Empty Accept header raises ValueError"""
        with pytest.raises(ValueError, match="No acceptable format found"):
            negotiate_feed_format('', ['rss', 'atom', 'json'])


class TestGetMimeType:
    """Tests for get_mime_type helper"""

    def test_rss_mime_type(self):
        """Get MIME type for RSS"""
        assert get_mime_type('rss') == 'application/rss+xml'

    def test_atom_mime_type(self):
        """Get MIME type for ATOM"""
        assert get_mime_type('atom') == 'application/atom+xml'

    def test_json_mime_type(self):
        """Get MIME type for JSON Feed"""
        assert get_mime_type('json') == 'application/feed+json'

    def test_invalid_format(self):
        """Invalid format raises ValueError"""
        with pytest.raises(ValueError, match="Unknown format"):
            get_mime_type('invalid')


class TestMimeTypeConstants:
    """Tests for MIME type constant mappings"""

    def test_mime_types_defined(self):
        """All expected MIME types are defined"""
        assert 'rss' in MIME_TYPES
        assert 'atom' in MIME_TYPES
        assert 'json' in MIME_TYPES

    def test_mime_type_values(self):
        """MIME type values are correct"""
        assert MIME_TYPES['rss'] == 'application/rss+xml'
        assert MIME_TYPES['atom'] == 'application/atom+xml'
        assert MIME_TYPES['json'] == 'application/feed+json'
