# -*- coding: utf-8 -*-

"""Test for music_services/data_structures"""

from __future__ import unicode_literals, print_function

from collections import OrderedDict
import pytest
from mock import PropertyMock, Mock, patch
from pysonos.music_services import data_structures
from pysonos.data_structures import DidlResource

# DATA
RESPONSES = []
RESPONSES.append(OrderedDict([('searchResult',
    OrderedDict([('index', '0'),
                 ('count', '2'),
                 ('total', '17230'),
                 ('mediaCollection',
        [OrderedDict([('id', 'album/43820695'),
                      ('itemType', 'album'),
                      ('title', 'Black Mosque'),
                      ('artist', 'Black Mosque'),
                      ('artistId', 'artist/6689314'),
                      ('canPlay', 'true'),
                      ('canEnumerate', 'true'),
                      ('canAddToFavorites', 'true'),
                      ('canSkip', 'true'),
                      ('albumArtURI', 'http://resources.wimpmusic.com/images/'
                       '2238a5cd/ed4d/4ad0/848d/40356f11bda0/640x640.jpg'),
                      ('canAddToFavorite', 'true')]),
         OrderedDict([('id', 'album/50340580'),
                      ('itemType', 'album'),
                      ('title', 'Black Hippy 2'),
                      ('artist', 'Black Hippy'),
                      ('artistId', 'artist/3882538'),
                      ('canPlay', 'true'),
                      ('canEnumerate', 'true'),
                      ('canAddToFavorites', 'true'),
                      ('canSkip', 'true'),
                      ('albumArtURI',
                       'http://resources.wimpmusic.com/images/eefb1532/dfe9/'
                       '46bd/8775/c583844bc098/640x640.jpg'),
                      ('canAddToFavorite', 'true')])
        ])
    ]))
]))
RESPONSES.append(
OrderedDict([('getMetadataResult',
    OrderedDict([('mediaMetadata',
        OrderedDict([('id', 'Track@catalog:/tracks/104655624'),
                     ('title', 'Take Me Into Your Skin'),
                     ('itemType', 'track'),
                     ('mimeType', 'audio/aac'),
                     ('trackMetadata',
            OrderedDict([('albumArtURI', 'http://artwork.cdn.247e.com/covers/104655587/256x256'),
                         ('artistId', 'Artist@catalog:/artists/219591'),
                         ('artist', 'Trentemøller'),
                         ('album', 'The Last Resort'),
                         ('duration', '464'),
                         ('canPlay', 'true'),
                         ('canSkip', 'true'),
                         ('canAddToFavorites', 'true'),
                         ('trackNumber', '1')])
                     )])),
                 ('count', '1'),
                 ('index', '0'),
                 ('total', '13')])
)])
)
PARSE_RESULTS = (
    {
        'number_of_results': 2,
        'type': 'searchResult',
        'class_key': 'MediaCollectionAlbum',
        
    },
    {
        'number_of_results': 1,
        'type': 'getMetadataResult',
        'class_key': 'MediaMetadataTrack',
    },
)


def test_get_class():
    """Test the get_class function"""
    # Test core functionality for base class MediaMetadata
    cls = data_structures.get_class('MediaMetadataTrack')
    assert cls.__name__ == 'MSTrack'
    assert issubclass(cls, data_structures.MediaMetadata)

    # Test core functionality for base class Mediacolection
    cls = data_structures.get_class('MediaCollectionArtist')
    assert cls.__name__ == 'MSArtist'
    assert issubclass(cls, data_structures.MediaCollection)

    # Test the caching function
    cls2 = data_structures.get_class('MediaCollectionArtist')
    assert cls is cls2

    # Asking for bad class should raise KeyError
    with pytest.raises(KeyError):
        cls = data_structures.get_class('Nonsense')


@pytest.mark.parametrize("response, correct",
                         zip(RESPONSES, PARSE_RESULTS))
def test_parse_response(response, correct):
    """Test the parse_response function"""
    music_service = Mock()
    music_service.desc = 'DESC'
    results = data_structures.parse_response(music_service, response, 'albums')

    # Check the search result metadata
    response_data = response[correct['type']]
    assert results.number_returned == response_data['count']
    assert results.search_type == 'albums'

    # Check the result
    assert len(results) == correct['number_of_results']
    klass = data_structures.get_class(correct['class_key'])
    for result in results:
        assert isinstance(result, klass)
        assert result.music_service is music_service


def test_parse_response_bad_type():
    """Test parse reponse bad code"""
    with pytest.raises(ValueError) as exp:
        data_structures.parse_response(None, {}, 'albums')
    print(exp)


def test_form_uri():
    """Test the form uri function"""
    music_service = Mock()
    music_service.sonos_uri_from_id.return_value = '99'

    # Test non track uri
    non_track_uri = data_structures.form_uri('dummy_id', None, False)
    assert non_track_uri == 'x-rincon-cpcontainer:dummy_id'

    # Test track uri
    track_uri = data_structures.form_uri('dummy_id', music_service, True)
    music_service.sonos_uri_from_id.assert_called_once_with('dummy_id')


def test_bool_str():
    """Test the bool_str function"""
    assert data_structures.bool_str('true') is True
    assert data_structures.bool_str('false') is False
    with pytest.raises(ValueError):
        data_structures.bool_str('dummy')


class TestMetadataDictBase(object):
    """Tests for the MetadataDictBase class"""

    def test_init(self):
        """Test normal __init__ functionality; metadata stored and camel case
        names coverted

        """
        metadata_dict = {
            'superTitle': 'Dummy Title',
        }
        metadata = data_structures.MetadataDictBase(metadata_dict)
        # Assert only metadata element, name conversion and value
        assert len(metadata.metadata) == 1
        assert metadata.super_title == 'Dummy Title'

    def test_conversion(self):
        """Test the type conversion of fields in metadata"""
        conversion_mock = Mock()
        conversion_mock.return_value = 47

        # MetadataDictBase is meant to ve overwritten, to supply
        # valid_fields and conversion functions
        class MyClass(data_structures.MetadataDictBase):
            _types = {'trackDuration': conversion_mock}

        metadata_dict = {
            'title': 'Dummy Title',
            'trackDuration': '47',
        }
        metadata = MyClass(metadata_dict)
        # Check that the duration has been properly type converted
        conversion_mock.assert_called_once_with('47')
        assert metadata.track_duration == 47
        # And that title has been left unchanged
        assert metadata.title == 'Dummy Title'

    def test_get_attr(self):
        """Test the __getattr__ method"""
        metadata_dict = {
            'superTitle': 'Dummy Title',
        }
        metadata = data_structures.MetadataDictBase(metadata_dict)
        # Test normal lookup
        assert metadata.super_title == 'Dummy Title'
        # Test raise attribute error when the key is not in metadata
        with pytest.raises(AttributeError):
            metadata.nonexistent_key


class TestMusicServiceItem(object):
    """Test the MusicServiceItem class"""

    @patch('pysonos.music_services.data_structures.MetadataDictBase.__init__')
    def test_init(self, metadata_dict_base_init):
        """Test the __init__ method"""
        kwargs = {
            'item_id': 'some_item_id', 'desc': 'the_desc',
            'resources': 'the ressources', 'uri': 'https://the_uri',
            'metadata_dict': {'some': 'dict'}, 'music_service': 'the music service',

            }

        music_service_item = data_structures.MusicServiceItem(**kwargs)
        # Test call to super class init
        metadata_dict_base_init.assert_called_once_with({'some': 'dict'})
        # Test that all but the metadata_dict arg have been set as
        # attributes with the same names as the arguments
        kwargs.pop('metadata_dict')
        for arg_name, arg_value in kwargs.items():
            assert getattr(music_service_item, arg_name) == arg_value

    @patch('pysonos.music_services.data_structures.MusicServiceItem.__init__')
    @patch('pysonos.music_services.data_structures.form_uri')
    @patch('pysonos.music_services.data_structures.DidlResource')
    def test_from_music_service(self, didl_resource, form_uri, music_service_init):
        """Test th from music service class method"""
        # Setup mock music service with mocked desc property
        ms = Mock()
        desc = PropertyMock(return_value='fake_desc')
        type(ms).desc = desc

        # Setup content dict
        id_ = 'fakeid1234'
        item_id = '0fffffff' + id_
        content_dict = {'id': id_, 'title': 'fake title'}

        # Setup return values of mocks
        didl_resource.return_value = Mock()
        form_uri.return_value = 'x-rincon-whatever:' + id_
        music_service_init.return_value = None

        # Call the class method and assert init called
        data_structures.MusicServiceItem.from_music_service(ms, content_dict)
        music_service_init.assert_called_once_with(
            item_id, 'fake_desc', [didl_resource.return_value],
            form_uri.return_value, content_dict, music_service=ms
        )
        form_uri.assert_called_once_with(item_id, ms, False)

    def test_str_(self):
        """Test the __str__ method"""
        content_dict = {'title': 'fake title'}
        item = data_structures.MusicServiceItem('fake_id', 'desc', 'ressources', 'uri', content_dict)
        assert item.__str__() == '<MusicServiceItem title="fake title">'

    @patch('pysonos.music_services.data_structures.DidlItem.to_element')
    def test_to_element(self, didl_item_to_element):
        """Test the to_element method"""
        didl_item_to_element.return_value = object()
        content_dict = {'title': 'fake title'}
        item = data_structures.MusicServiceItem('fake_id', 'desc', 'ressources', 'uri', content_dict)
        assert item.to_element() == didl_item_to_element.return_value
        
        
