import re
from enum import Enum, IntEnum
from typing import Pattern, Optional, ClassVar, Iterable

__all__ = (
    "SearchType",
    "TrackType",
    "PlaylistType",
    "NodeAlgorithm",
    "LoopMode",
    "RouteStrategy",
    "RouteIPType",
    "URLRegex",
    "LogLevel",
)


class SearchType(Enum):
    """
    The enum for the different search types.
    """

    ytsearch = "ytsearch"
    ytmsearch = "ytmsearch"
    scsearch = "scsearch"

    def __str__(self) -> str:
        return self.value


class TrackType(Enum):
    """
    Flags for the origin/source of a track.
    """

    YOUTUBE = "youtube"
    SOUNDCLOUD = "soundcloud"
    SPOTIFY = "spotify"
    APPLE_MUSIC = "apple_music"
    HTTP = "http"
    LOCAL = "local"
    OTHER = "other"

    @classmethod
    def _missing_(cls, _: object) -> "TrackType":
        return cls.OTHER

    def __str__(self) -> str:
        return self.value


class PlaylistType(Enum):
    """Playlist source identifiers."""

    YOUTUBE = "youtube"
    SOUNDCLOUD = "soundcloud"
    SPOTIFY = "spotify"
    APPLE_MUSIC = "apple_music"
    OTHER = "other"

    @classmethod
    def _missing_(cls, _: object) -> "PlaylistType":
        return cls.OTHER

    def __str__(self) -> str:
        return self.value


class NodeAlgorithm(Enum):
    """Node selection algorithm identifiers."""

    by_ping = "BY_PING"
    by_players = "BY_PLAYERS"

    def __str__(self) -> str:
        return self.value


class LoopMode(Enum):
    """Queue loop modes."""

    TRACK = "track"
    QUEUE = "queue"

    def __str__(self) -> str:
        return self.value


class RouteStrategy(Enum):
    """Route planner strategies for Lavalink-like nodes."""

    ROTATE_ON_BAN = "RotatingIpRoutePlanner"
    LOAD_BALANCE = "BalancingIpRoutePlanner"
    NANO_SWITCH = "NanoIpRoutePlanner"
    ROTATING_NANO_SWITCH = "RotatingNanoIpRoutePlanner"

    def __str__(self) -> str:
        return self.value


class RouteIPType(Enum):
    """IP address family used by the route planner."""

    IPV4 = "Inet4Address"
    IPV6 = "Inet6Address"

    def __str__(self) -> str:
        return self.value


class URLRegex:
    """Container for commonly used URL regex patterns and helpers.

    Each attribute is a compiled Pattern[str]. Use the helper methods to
    test or extract information from a URL.
    """

    SPOTIFY_URL: ClassVar[Pattern[str]] = re.compile(
        r"https?://open.spotify.com/(?P<type>album|playlist|track|artist)/(?P<id>[A-Za-z0-9]+)"
    )

    DISCORD_MP3_URL: ClassVar[Pattern[str]] = re.compile(
        r"https?://cdn\.discordapp\.com/attachments/(?P<channel_id>[0-9]+)/"
        r"(?P<message_id>[0-9]+)/(?P<file>[A-Za-z0-9_.-]+)"
    )

    YOUTUBE_URL: ClassVar[Pattern[str]] = re.compile(
        r"^((?:https?:)?//)?((?:www|m)\.)?((?:youtube\.com|youtu.be))"
        r"(/(?:[\w\-]+\?v=|embed/|v/)?)([\w\-]+)(\S+)?$"
    )

    YOUTUBE_PLAYLIST_URL: ClassVar[Pattern[str]] = re.compile(
        r"^((?:https?:)?//)?((?:www|m)\.)?((?:youtube\.com|youtu.be))/playlist\?list=.*"
    )

    YOUTUBE_TIMESTAMP: ClassVar[Pattern[str]] = re.compile(r"(?P<video>^.*?)(\?t|&start)=(?P<time>\d+)?.*")

    AM_URL: ClassVar[Pattern[str]] = re.compile(
        r"https?://music.apple.com/(?P<country>[A-Za-z]{2})/"
        r"(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>[^?]+)"
    )

    AM_SINGLE_IN_ALBUM_REGEX: ClassVar[Pattern[str]] = re.compile(
        r"https?://music.apple.com/(?P<country>[A-Za-z]{2})/(?P<type>album|playlist|song|artist)/"
        r"(?P<name>.+)/(?P<id>.+)(\?i=)(?P<id2>.+)"
    )

    SOUNDCLOUD_URL: ClassVar[Pattern[str]] = re.compile(r"((?:https?:)?//)?((?:www|m)\.)?soundcloud.com/.*/.*")

    SOUNDCLOUD_PLAYLIST_URL: ClassVar[Pattern[str]] = re.compile(
        r"^(https?:\\/\\/)?(www\.)?(m\.)?soundcloud\.com\/.*/sets/.*"
    )

    SOUNDCLOUD_TRACK_IN_SET_URL: ClassVar[Pattern[str]] = re.compile(
        r"^(https?:\\/\\/)?(www\.)?(m\.)?soundcloud\.com/[A-Za-z0-9-._]+/[A-Za-z0-9-._]+(\?in)"
    )

    LAVALINK_SEARCH: ClassVar[Pattern[str]] = re.compile(r"(?P<type>ytm?|sc)search:")

    BASE_URL: ClassVar[Pattern[str]] = re.compile(r"https?://(?:www\.)?.+")

    @classmethod
    def any_match(cls, url: str, patterns: Optional[Iterable[Pattern[str]]] = None) -> bool:
        """Return True if any of the selected patterns match the URL.

        If `patterns` is None, tests against a sensible default set.
        """
        candidates = patterns or (
            cls.YOUTUBE_URL,
            cls.YOUTUBE_PLAYLIST_URL,
            cls.SPOTIFY_URL,
            cls.SOUNDCLOUD_URL,
            cls.AM_URL,
            cls.BASE_URL,
        )
        for p in candidates:
            if p.search(url):
                return True
        return False


class LogLevel(IntEnum):
    """Shorthand for logging levels.

    Use `LogLevel.from_str(...)` for robust parsing from user input.
    """

    DEBUG = 10
    INFO = 20
    WARN = 30
    ERROR = 40
    CRITICAL = 50

    @classmethod
    def from_str(cls, level_str: str) -> "LogLevel":
        """Parse a string (case-insensitive) into a LogLevel.

        Raises:
            ValueError: if the provided string does not map to a valid level.
        """
        if not isinstance(level_str, str):
            raise TypeError("level_str must be a string")

        normalized = level_str.strip().upper()
        synonyms = {
            "WARNING": "WARN",
            "WARN": "WARN",
            "INFO": "INFO",
            "DEBUG": "DEBUG",
            "ERROR": "ERROR",
            "CRITICAL": "CRITICAL",
            "FATAL": "CRITICAL",
        }

        key = synonyms.get(normalized)
        if key is None:
            try:
                return cls[normalized]
            except KeyError:
                allowed = ", ".join([m.name for m in cls])
                raise ValueError(f"No such log level: {level_str!r}. Allowed: {allowed}")

        return cls[key]

    def __str__(self) -> str:
        return self.name
