""" Functions for handling Finder tags and colors """

import logging
import pathlib
import plistlib

from osxmetadata.constants import (
    _COLORIDS,
    _COLORNAMES,
    _COLORNAMES_LOWER,
    _MAX_FINDER_COLOR,
    _MIN_FINDER_COLOR,
    FINDER_COLOR_NONE,
)


def get_tag_color_name(colorid):
    """Return name of the Finder color based on ID"""
    # TODO: need to figure out how to do this in locale/language name
    try:
        colorname = _COLORIDS[colorid]
    except KeyError:
        raise ValueError(f"Invalid colorid: {colorid}")
    return colorname


def tag_factory(tag_str):
    """creates a Tag object
    tag_str: (str) tag value in format: 'name,color'
    where name is the name of the tag and color specifies the color ID
    the comma and color are optional;
    if not provided Tag will use color assigned in Finder or FINDER_COLOR_NONE if no color
    e.g. tag_factory("foo") -> Tag("foo")
         tag_factory("test,6") -> Tag("test", 6)"""

    if not isinstance(tag_str, str):
        raise TypeError(f"tag_str must be str not {type(tag_str)}")

    values = tag_str.split(",")
    name = values[0]
    name = name.lstrip().rstrip()
    if len(values) > 2:
        raise ValueError(f"More than one value found after comma: {tag_str}")
    elif len(values) == 1:
        # got name only, check to see if name is also a color and if so assign it
        # TODO: this might not be right/desired in different locale settings
        # but I'm not sure yet how to get the non-English color names used by Finder
        try:
            colorid = _COLORNAMES_LOWER[name.lower()]
            return Tag(_COLORIDS[colorid], colorid)
        except KeyError:
            return Tag(name)
    elif len(values) == 2:
        # got a color, is it a name or number
        color = values[1].lstrip().rstrip().lower()
        try:
            colorid = _COLORNAMES_LOWER[color]
        except KeyError:
            colorid = int(color)
            if colorid not in range(_MAX_FINDER_COLOR + 1):
                raise ValueError(
                    f"color must be in range 0 to {_MAX_FINDER_COLOR} inclusive: {colorid}"
                )
        return Tag(name, colorid)


def get_finder_tags():
    """parses com.apple.finder.plist to get list of Finder tags and associated colors
    returns: dict in form {tag_name: color_id}"""

    # TODO: Is it possible to get a race condition where Finder is writing at same time
    # this code is trying to open the plist file?  If so, what happens?
    plist_file = pathlib.Path(
        str(pathlib.Path.home()) + "/Library/SyncedPreferences/com.apple.finder.plist"
    )
    tags = {}
    try:
        with open(plist_file, "rb") as fp:
            pl = plistlib.load(fp)
            try:
                finder_tags = pl["values"]["FinderTagDict"]["value"]["FinderTags"]
                for tag in finder_tags:
                    try:
                        name = tag["n"]
                        color = tag["l"]
                    except KeyError:
                        # color will not be present if no color
                        color = 0
                    tags[name] = color

            except Exception as e:
                logging.warning(f"Exception while parsing plist file: {e}")
                raise e

    except Exception as e:
        logging.warning(f"Could not open plist file: {plist_file}, exception: {e}")
        raise e

    return tags


class Tag:
    """Information about Finder tags/keywords and associated color labels"""

    def __init__(self, name, *color):
        """Create a new Tag object
        name: (str) name of the tag
        color: (int) color ID of the tag, optional
        If color name is not provided, com.apple.finder.plist will be parsed to see if
        a color is already associated with 'name' and if so, that color will be assigned
        If a color is provided and the Finder's color is different, a warning will be logged
        but the new color ID will be used"""

        # input validation
        # todo: add length validation on name? Need to determine what max tag length is for Finder
        # and if there are any illegal characters
        if type(name) is not str:
            raise TypeError(f"name must be a str not {type(name)}")
        if len(color):
            if len(color) != 1:
                raise ValueError(
                    f"only 1 argument expected for color but {len(color)} provided"
                )
            if type(color[0]) is not int:
                raise TypeError(f"color must be an int not {type(color[0])}")
            if color[0] not in range(_MAX_FINDER_COLOR + 1):
                raise ValueError(
                    f"color must be in range 0 to {_MAX_FINDER_COLOR} inclusive: {color[0]}"
                )

        self._finder_tags = get_finder_tags()
        self._name = name

        # if no color provided use color from Finder if it exists
        if len(color):
            # color was provided
            if name in self._finder_tags and color[0] != self._finder_tags[name]:
                logging.warning(
                    f"assigning color {color[0]} to tag {name} but "
                    f"color {self._finder_tags[name]} already assigned in Finder"
                )
            self._colorid = color[0]
        elif name in self._finder_tags:
            # use color Finder already using
            self._colorid = self._finder_tags[name]
        else:
            self._colorid = FINDER_COLOR_NONE  # set to no color

    @property
    def name(self):
        return self._name

    @property
    def color(self):
        return self._colorid

    def _format(self):
        """formats a tag for writing to _kMDItemUserTags"""
        return f"{self.name}\n{self.color}"

    def __str__(self):
        return f"{self.name}: {_COLORIDS[self.color]}"

    def __repr__(self):
        return f"Tag('{self.name}', {self.color})"

    def __eq__(self, other):
        return (
            isinstance(other, Tag)
            and self.name == other.name
            and self.color == other.color
        )

    def __lt__(self, other):
        if not isinstance(other, Tag):
            raise TypeError(f"< not supported between type Tag and type {type(other)}")

        return self.name < other.name

    def __gt__(self, other):
        if not isinstance(other, Tag):
            raise TypeError(f"< not supported between type Tag and type {type(other)}")

        return self.name > other.name

    def __len__(self):
        return len(self.name)
