import re
import typing as t
import warnings

from .user_agent import UserAgent as _BaseUserAgent

if t.TYPE_CHECKING:
    from wsgiref.types import WSGIEnvironment


class _UserAgentParser:
    platform_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = (
        (" cros ", "chromeos"),
        ("iphone|ios", "iphone"),
        ("ipad", "ipad"),
        (r"darwin\b|mac\b|os\s*x", "macos"),
        ("win", "windows"),
        (r"android", "android"),
        ("netbsd", "netbsd"),
        ("openbsd", "openbsd"),
        ("freebsd", "freebsd"),
        ("dragonfly", "dragonflybsd"),
        ("(sun|i86)os", "solaris"),
        (r"x11\b|lin(\b|ux)?", "linux"),
        (r"nintendo\s+wii", "wii"),
        ("irix", "irix"),
        ("hp-?ux", "hpux"),
        ("aix", "aix"),
        ("sco|unix_sv", "sco"),
        ("bsd", "bsd"),
        ("amiga", "amiga"),
        ("blackberry|playbook", "blackberry"),
        ("symbian", "symbian"),
    )
    browser_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = (
        ("googlebot", "google"),
        ("msnbot", "msn"),
        ("yahoo", "yahoo"),
        ("ask jeeves", "ask"),
        (r"aol|america\s+online\s+browser", "aol"),
        (r"opera|opr", "opera"),
        ("edge|edg", "edge"),
        ("chrome|crios", "chrome"),
        ("seamonkey", "seamonkey"),
        ("firefox|firebird|phoenix|iceweasel", "firefox"),
        ("galeon", "galeon"),
        ("safari|version", "safari"),
        ("webkit", "webkit"),
        ("camino", "camino"),
        ("konqueror", "konqueror"),
        ("k-meleon", "kmeleon"),
        ("netscape", "netscape"),
        (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"),
        ("lynx", "lynx"),
        ("links", "links"),
        ("Baiduspider", "baidu"),
        ("bingbot", "bing"),
        ("mozilla", "mozilla"),
    )

    _browser_version_re = r"(?:{pattern})[/\sa-z(]*(\d+[.\da-z]+)?"
    _language_re = re.compile(
        r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|"
        r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)"
    )

    def __init__(self) -> None:
        self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platform_rules]
        self.browsers = [
            (b, re.compile(self._browser_version_re.format(pattern=a), re.I))
            for a, b in self.browser_rules
        ]

    def __call__(
        self, user_agent: str
    ) -> t.Tuple[t.Optional[str], t.Optional[str], t.Optional[str], t.Optional[str]]:
        platform: t.Optional[str]
        browser: t.Optional[str]
        version: t.Optional[str]
        language: t.Optional[str]

        for platform, regex in self.platforms:  # noqa: B007
            match = regex.search(user_agent)
            if match is not None:
                break
        else:
            platform = None

        # Except for Trident, all browser key words come after the last ')'
        last_closing_paren = 0
        if (
            not re.compile(r"trident/.+? rv:", re.I).search(user_agent)
            and ")" in user_agent
            and user_agent[-1] != ")"
        ):
            last_closing_paren = user_agent.rindex(")")

        for browser, regex in self.browsers:  # noqa: B007
            match = regex.search(user_agent[last_closing_paren:])
            if match is not None:
                version = match.group(1)
                break
        else:
            browser = version = None
        match = self._language_re.search(user_agent)
        if match is not None:
            language = match.group(1) or match.group(2)
        else:
            language = None
        return platform, browser, version, language


# It wasn't public, but users might have imported it anyway, show a
# warning if a user created an instance.
class UserAgentParser(_UserAgentParser):
    """A simple user agent parser.  Used by the `UserAgent`.

    .. deprecated:: 2.0
        Will be removed in Werkzeug 2.1. Use a dedicated parser library
        instead.
    """

    def __init__(self) -> None:
        warnings.warn(
            "'UserAgentParser' is deprecated and will be removed in"
            " Werkzeug 2.1. Use a dedicated parser library instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        super().__init__()


class _deprecated_property(property):
    def __init__(self, fget: t.Callable[["_UserAgent"], t.Any]) -> None:
        super().__init__(fget)
        self.message = (
            "The built-in user agent parser is deprecated and will be"
            f" removed in Werkzeug 2.1. The {fget.__name__!r} property"
            " will be 'None'. Subclass 'werkzeug.user_agent.UserAgent'"
            " and set 'Request.user_agent_class' to use a different"
            " parser."
        )

    def __get__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
        warnings.warn(self.message, DeprecationWarning, stacklevel=3)
        return super().__get__(*args, **kwargs)


# This is what Request.user_agent returns for now, only show warnings on
# attribute access, not creation.
class _UserAgent(_BaseUserAgent):
    _parser = _UserAgentParser()

    def __init__(self, string: str) -> None:
        super().__init__(string)
        info = self._parser(string)
        self._platform, self._browser, self._version, self._language = info

    @_deprecated_property
    def platform(self) -> t.Optional[str]:  # type: ignore
        return self._platform

    @_deprecated_property
    def browser(self) -> t.Optional[str]:  # type: ignore
        return self._browser

    @_deprecated_property
    def version(self) -> t.Optional[str]:  # type: ignore
        return self._version

    @_deprecated_property
    def language(self) -> t.Optional[str]:  # type: ignore
        return self._language


# This is what users might be importing, show warnings on create.
class UserAgent(_UserAgent):
    """Represents a parsed user agent header value.

    This uses a basic parser to try to extract some information from the
    header.

    :param environ_or_string: The header value to parse, or a WSGI
        environ containing the header.

    .. deprecated:: 2.0
        Will be removed in Werkzeug 2.1. Subclass
        :class:`werkzeug.user_agent.UserAgent` (note the new module
        name) to use a dedicated parser instead.

    .. versionchanged:: 2.0
        Passing a WSGI environ is deprecated and will be removed in 2.1.
    """

    def __init__(self, environ_or_string: "t.Union[str, WSGIEnvironment]") -> None:
        if isinstance(environ_or_string, dict):
            warnings.warn(
                "Passing an environ to 'UserAgent' is deprecated and"
                " will be removed in Werkzeug 2.1. Pass the header"
                " value string instead.",
                DeprecationWarning,
                stacklevel=2,
            )
            string = environ_or_string.get("HTTP_USER_AGENT", "")
        else:
            string = environ_or_string

        warnings.warn(
            "The 'werkzeug.useragents' module is deprecated and will be"
            " removed in Werkzeug 2.1. The new base API is"
            " 'werkzeug.user_agent.UserAgent'.",
            DeprecationWarning,
            stacklevel=2,
        )
        super().__init__(string)
