# This file is part of beets.
# Copyright 2019, Rahul Ahuja.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

"""Update library's tags using Beatport.
"""

from beets.plugins import BeetsPlugin, apply_item_changes
from beets import autotag, library, ui, util

from .beatport import BeatportPlugin


class BPSyncPlugin(BeetsPlugin):
    def __init__(self):
        super().__init__()
        self.beatport_plugin = BeatportPlugin()
        self.beatport_plugin.setup()

    def commands(self):
        cmd = ui.Subcommand('bpsync', help='update metadata from Beatport')
        cmd.parser.add_option(
            '-p',
            '--pretend',
            action='store_true',
            help='show all changes but do nothing',
        )
        cmd.parser.add_option(
            '-m',
            '--move',
            action='store_true',
            dest='move',
            help="move files in the library directory",
        )
        cmd.parser.add_option(
            '-M',
            '--nomove',
            action='store_false',
            dest='move',
            help="don't move files in library",
        )
        cmd.parser.add_option(
            '-W',
            '--nowrite',
            action='store_false',
            default=None,
            dest='write',
            help="don't write updated metadata to files",
        )
        cmd.parser.add_format_option()
        cmd.func = self.func
        return [cmd]

    def func(self, lib, opts, args):
        """Command handler for the bpsync function.
        """
        move = ui.should_move(opts.move)
        pretend = opts.pretend
        write = ui.should_write(opts.write)
        query = ui.decargs(args)

        self.singletons(lib, query, move, pretend, write)
        self.albums(lib, query, move, pretend, write)

    def singletons(self, lib, query, move, pretend, write):
        """Retrieve and apply info from the autotagger for items matched by
        query.
        """
        for item in lib.items(query + ['singleton:true']):
            if not item.mb_trackid:
                self._log.info(
                    'Skipping singleton with no mb_trackid: {}', item
                )
                continue

            if not self.is_beatport_track(item):
                self._log.info(
                    'Skipping non-{} singleton: {}',
                    self.beatport_plugin.data_source,
                    item,
                )
                continue

            # Apply.
            trackinfo = self.beatport_plugin.track_for_id(item.mb_trackid)
            with lib.transaction():
                autotag.apply_item_metadata(item, trackinfo)
                apply_item_changes(lib, item, move, pretend, write)

    @staticmethod
    def is_beatport_track(item):
        return (
            item.get('data_source') == BeatportPlugin.data_source
            and item.mb_trackid.isnumeric()
        )

    def get_album_tracks(self, album):
        if not album.mb_albumid:
            self._log.info('Skipping album with no mb_albumid: {}', album)
            return False
        if not album.mb_albumid.isnumeric():
            self._log.info(
                'Skipping album with invalid {} ID: {}',
                self.beatport_plugin.data_source,
                album,
            )
            return False
        items = list(album.items())
        if album.get('data_source') == self.beatport_plugin.data_source:
            return items
        if not all(self.is_beatport_track(item) for item in items):
            self._log.info(
                'Skipping non-{} release: {}',
                self.beatport_plugin.data_source,
                album,
            )
            return False
        return items

    def albums(self, lib, query, move, pretend, write):
        """Retrieve and apply info from the autotagger for albums matched by
        query and their items.
        """
        # Process matching albums.
        for album in lib.albums(query):
            # Do we have a valid Beatport album?
            items = self.get_album_tracks(album)
            if not items:
                continue

            # Get the Beatport album information.
            albuminfo = self.beatport_plugin.album_for_id(album.mb_albumid)
            if not albuminfo:
                self._log.info(
                    'Release ID {} not found for album {}',
                    album.mb_albumid,
                    album,
                )
                continue

            beatport_trackid_to_trackinfo = {
                track.track_id: track for track in albuminfo.tracks
            }
            library_trackid_to_item = {
                int(item.mb_trackid): item for item in items
            }
            item_to_trackinfo = {
                item: beatport_trackid_to_trackinfo[track_id]
                for track_id, item in library_trackid_to_item.items()
            }

            self._log.info('applying changes to {}', album)
            with lib.transaction():
                autotag.apply_metadata(albuminfo, item_to_trackinfo)
                changed = False
                # Find any changed item to apply Beatport changes to album.
                any_changed_item = items[0]
                for item in items:
                    item_changed = ui.show_model_changes(item)
                    changed |= item_changed
                    if item_changed:
                        any_changed_item = item
                        apply_item_changes(lib, item, move, pretend, write)

                if pretend or not changed:
                    continue

                # Update album structure to reflect an item in it.
                for key in library.Album.item_keys:
                    album[key] = any_changed_item[key]
                album.store()

                # Move album art (and any inconsistent items).
                if move and lib.directory in util.ancestry(items[0].path):
                    self._log.debug('moving album {}', album)
                    album.move()
