import logging
from tempfile import NamedTemporaryFile
from typing import Dict, Optional
from urllib.request import urlopen
from uuid import uuid4

import musicbrainzngs
from django.conf import settings
from django.core.files.base import File
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_extensions.db.models import TimeStampedModel
from scrobbles.mixins import ScrobblableMixin
from scrobbles.theaudiodb import lookup_artist_from_tadb

logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}


class Artist(TimeStampedModel):
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    name = models.CharField(max_length=255)
    biography = models.TextField(**BNULL)
    theaudiodb_genre = models.CharField(max_length=255, **BNULL)
    theaudiodb_mood = models.CharField(max_length=255, **BNULL)
    musicbrainz_id = models.CharField(max_length=255, **BNULL)
    thumbnail = models.ImageField(upload_to="artist/", **BNULL)

    class Meta:
        unique_together = [['name', 'musicbrainz_id']]

    def __str__(self):
        return self.name

    @property
    def mb_link(self):
        return f"https://musicbrainz.org/artist/{self.musicbrainz_id}"

    def get_absolute_url(self):
        return reverse('music:artist_detail', kwargs={'slug': self.uuid})

    def scrobbles(self):
        from scrobbles.models import Scrobble

        return Scrobble.objects.filter(
            track__in=self.track_set.all()
        ).order_by('-timestamp')

    @property
    def tracks(self):
        return (
            self.track_set.all()
            .annotate(scrobble_count=models.Count('scrobble'))
            .order_by('-scrobble_count')
        )

    def charts(self):
        from scrobbles.models import ChartRecord

        return ChartRecord.objects.filter(track__artist=self).order_by('-year')

    def fix_metadata(self):
        tadb_info = lookup_artist_from_tadb(self.name)
        if not tadb_info:
            logger.warn(f"No response from TADB for artist {self.name}")
            return

        self.biography = tadb_info['biography']
        self.theaudiodb_genre = tadb_info['genre']
        self.theaudiodb_mood = tadb_info['mood']

        img_temp = NamedTemporaryFile(delete=True)
        img_temp.write(urlopen(tadb_info['thumb_url']).read())
        img_temp.flush()
        img_filename = f"{self.name}_{self.uuid}.jpg"
        self.thumbnail.save(img_filename, File(img_temp))


class Album(TimeStampedModel):
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    name = models.CharField(max_length=255)
    artists = models.ManyToManyField(Artist)
    year = models.IntegerField(**BNULL)
    musicbrainz_id = models.CharField(max_length=255, unique=True, **BNULL)
    musicbrainz_releasegroup_id = models.CharField(max_length=255, **BNULL)
    musicbrainz_albumartist_id = models.CharField(max_length=255, **BNULL)
    cover_image = models.ImageField(upload_to="albums/", **BNULL)

    def __str__(self):
        return self.name

    @property
    def primary_artist(self):
        return self.artists.first()

    def fix_metadata(self):
        if (
            not self.musicbrainz_albumartist_id
            or not self.year
            or not self.musicbrainz_releasegroup_id
        ):
            musicbrainzngs.set_useragent('vrobbler', '0.3.0')
            mb_data = musicbrainzngs.get_release_by_id(
                self.musicbrainz_id, includes=['artists', 'release-groups']
            )
            if not self.musicbrainz_releasegroup_id:
                self.musicbrainz_releasegroup_id = mb_data['release'][
                    'release-group'
                ]['id']
            if not self.musicbrainz_albumartist_id:
                self.musicbrainz_albumartist_id = mb_data['release'][
                    'artist-credit'
                ][0]['artist']['id']
            if not self.year:
                try:
                    self.year = mb_data['release']['date'][0:4]
                except KeyError:
                    pass
                except IndexError:
                    pass

            self.save(
                update_fields=[
                    'musicbrainz_albumartist_id',
                    'musicbrainz_releasegroup_id',
                    'year',
                ]
            )

            new_artist = Artist.objects.filter(
                musicbrainz_id=self.musicbrainz_albumartist_id
            ).first()
            if self.musicbrainz_albumartist_id and new_artist:
                self.artists.add(new_artist)
            if not new_artist:
                for t in self.track_set.all():
                    self.artists.add(t.artist)
            if (
                not self.cover_image
                or self.cover_image == 'default-image-replace-me'
            ):
                self.fetch_artwork()

    def fetch_artwork(self, force=False):
        if not self.cover_image and not force:
            if self.musicbrainz_id:
                try:
                    img_data = musicbrainzngs.get_image_front(
                        self.musicbrainz_id
                    )
                    name = f"{self.name}_{self.uuid}.jpg"
                    self.cover_image = ContentFile(img_data, name=name)
                    logger.info(f'Setting image to {name}')
                except musicbrainzngs.ResponseError:
                    logger.warning(
                        f'No cover art found for {self.name} by release'
                    )

            if (
                not self.cover_image
                or self.cover_image == "default-image-replace-me"
            ) and self.musicbrainz_releasegroup_id:
                try:
                    img_data = musicbrainzngs.get_release_group_image_front(
                        self.musicbrainz_releasegroup_id
                    )
                    name = f"{self.name}_{self.uuid}.jpg"
                    self.cover_image = ContentFile(img_data, name=name)
                    logger.info(f'Setting image to {name}')
                except musicbrainzngs.ResponseError:
                    logger.warning(
                        f'No cover art found for {self.name} by release group'
                    )
            if not self.cover_image:
                logger.debug(
                    f"No cover art found for release or release group for {self.name}, setting to default"
                )
            self.save()

    @property
    def mb_link(self):
        return f"https://musicbrainz.org/release/{self.musicbrainz_id}"


class Track(ScrobblableMixin):
    COMPLETION_PERCENT = getattr(settings, 'MUSIC_COMPLETION_PERCENT', 90)

    class Opinion(models.IntegerChoices):
        DOWN = -1, 'Thumbs down'
        NEUTRAL = 0, 'No opinion'
        UP = 1, 'Thumbs up'

    artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING)
    album = models.ForeignKey(Album, on_delete=models.DO_NOTHING, **BNULL)
    musicbrainz_id = models.CharField(max_length=255, **BNULL)

    class Meta:
        unique_together = [['album', 'musicbrainz_id']]

    def __str__(self):
        return f"{self.title} by {self.artist}"

    def get_absolute_url(self):
        return reverse('music:track_detail', kwargs={'slug': self.uuid})

    @property
    def subtitle(self):
        return self.artist

    @property
    def mb_link(self):
        return f"https://musicbrainz.org/recording/{self.musicbrainz_id}"

    @property
    def info_link(self):
        return self.mb_link

    @classmethod
    def find_or_create(
        cls, artist_dict: Dict, album_dict: Dict, track_dict: Dict
    ) -> Optional["Track"]:
        """Given a data dict from Jellyfin, does the heavy lifting of looking up
        the video and, if need, TV Series, creating both if they don't yet
        exist.

        """
        if not artist_dict.get('name') or not artist_dict.get(
            'musicbrainz_id'
        ):
            logger.warning(
                f"No artist or artist musicbrainz ID found in message from source, not scrobbling"
            )
            return

        artist, artist_created = Artist.objects.get_or_create(**artist_dict)
        album, album_created = Album.objects.get_or_create(**album_dict)

        album.fix_metadata()
        if not album.cover_image:
            album.fetch_artwork()

        track_dict['album_id'] = getattr(album, "id", None)
        track_dict['artist_id'] = artist.id

        track, created = cls.objects.get_or_create(**track_dict)

        return track
