"""Synchronize information from iTunes's library"""

from __future__ import annotations

import os
import plistlib
import shutil
import tempfile
from contextlib import contextmanager
from time import mktime
from typing import TYPE_CHECKING, ClassVar
from urllib.parse import unquote, urlparse

from confuse import ConfigValueError

from beets import util
from beets.dbcore import types
from beets.util import bytestring_path, syspath
from beetsplug.metasync import MetaSource

if TYPE_CHECKING:
    from collections.abc import Iterator

    from confuse import ConfigView

    from beets.library import Item
    from beets.logging import BeetsLogger as Logger


@contextmanager
def create_temporary_copy(path: util.PathLike) -> Iterator[bytes]:
    temp_dir = bytestring_path(tempfile.mkdtemp())
    temp_path = os.path.join(temp_dir, b"temp_itunes_lib")
    shutil.copyfile(syspath(path), syspath(temp_path))
    try:
        yield temp_path
    finally:
        shutil.rmtree(syspath(temp_dir))


def _norm_itunes_path(path: bytes) -> bytes:
    # Itunes prepends the location with 'file://' on posix systems,
    # and with 'file://localhost/' on Windows systems.
    # The actual path to the file is always saved as posix form
    # E.g., 'file://Users/Music/bar' or 'file://localhost/G:/Music/bar'

    # The entire path will also be capitalized (e.g., '/Music/Alt-J')
    # Note that this means the path will always have a leading separator,
    # which is unwanted in the case of Windows systems.
    # E.g., '\\G:\\Music\\bar' needs to be stripped to 'G:\\Music\\bar'

    return util.bytestring_path(
        os.path.normpath(unquote(urlparse(path).path)).lstrip("\\")
    ).lower()


class Itunes(MetaSource):
    item_types: ClassVar[dict[str, types.Type]] = {
        "itunes_rating": types.INTEGER,  # 0..100 scale
        "itunes_playcount": types.INTEGER,
        "itunes_skipcount": types.INTEGER,
        "itunes_lastplayed": types.DATE,
        "itunes_lastskipped": types.DATE,
        "itunes_dateadded": types.DATE,
    }

    def __init__(self, config: ConfigView, log: Logger) -> None:
        super().__init__(config, log)

        config.add({"itunes": {"library": "~/Music/iTunes/iTunes Library.xml"}})

        # Load the iTunes library, which has to be the .xml one (not the .itl)
        library_path = config["itunes"]["library"].as_filename()

        try:
            self._log.debug("loading iTunes library from {}", library_path)
            with create_temporary_copy(library_path) as library_copy:
                with open(library_copy, "rb") as library_copy_f:
                    raw_library = plistlib.load(library_copy_f)
        except OSError as e:
            raise ConfigValueError(f"invalid iTunes library: {e.strerror}")
        except Exception:
            # It's likely the user configured their '.itl' library (<> xml)
            if os.path.splitext(library_path)[1].lower() != ".xml":
                hint = (
                    ": please ensure that the configured path"
                    " points to the .XML library"
                )
            else:
                hint = ""
            raise ConfigValueError(f"invalid iTunes library{hint}")

        # Make the iTunes library queryable using the path
        self.collection = {
            _norm_itunes_path(track["Location"]): track
            for track in raw_library["Tracks"].values()
            if "Location" in track
        }

    def sync_from_source(self, item: Item) -> None:
        result = self.collection.get(util.bytestring_path(item.path).lower())

        if not result:
            self._log.warning("no iTunes match found for {}", item)
            return

        item.itunes_rating = result.get("Rating")
        item.itunes_playcount = result.get("Play Count")
        item.itunes_skipcount = result.get("Skip Count")

        if result.get("Play Date UTC"):
            item.itunes_lastplayed = mktime(
                result.get("Play Date UTC").timetuple()
            )

        if result.get("Skip Date"):
            item.itunes_lastskipped = mktime(
                result.get("Skip Date").timetuple()
            )

        if result.get("Date Added"):
            item.itunes_dateadded = mktime(result.get("Date Added").timetuple())
