# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
"""The Proxy implementation."""


class ProxyTypeFactory:
    """Factory for proxy types."""

    @staticmethod
    def make(ff_value, string):
        return {"ff_value": ff_value, "string": string}


class ProxyType:
    """Set of possible types of proxy.

    Each proxy type has 2 properties:    'ff_value' is value of Firefox
    profile preference,    'string' is id of proxy type.
    """

    DIRECT = ProxyTypeFactory.make(0, "DIRECT")  # Direct connection, no proxy (default on Windows).
    MANUAL = ProxyTypeFactory.make(1, "MANUAL")  # Manual proxy settings (e.g., for httpProxy).
    PAC = ProxyTypeFactory.make(2, "PAC")  # Proxy autoconfiguration from URL.
    RESERVED_1 = ProxyTypeFactory.make(3, "RESERVED1")  # Never used.
    AUTODETECT = ProxyTypeFactory.make(4, "AUTODETECT")  # Proxy autodetection (presumably with WPAD).
    SYSTEM = ProxyTypeFactory.make(5, "SYSTEM")  # Use system settings (default on Linux).
    UNSPECIFIED = ProxyTypeFactory.make(6, "UNSPECIFIED")  # Not initialized (for internal use).

    @classmethod
    def load(cls, value):
        if isinstance(value, dict) and "string" in value:
            value = value["string"]
        value = str(value).upper()
        for attr in dir(cls):
            attr_value = getattr(cls, attr)
            if isinstance(attr_value, dict) and "string" in attr_value and attr_value["string"] == value:
                return attr_value
        raise Exception(f"No proxy type is found for {value}")


class _ProxyTypeDescriptor:
    def __init__(self, name, p_type):
        self.name = name
        self.p_type = p_type

    def __get__(self, obj, cls):
        return getattr(obj, self.name)

    def __set__(self, obj, value):
        if self.name == "autodetect" and not isinstance(value, bool):
            raise ValueError("Autodetect proxy value needs to be a boolean")
        getattr(obj, "_verify_proxy_type_compatibility")(self.p_type)
        setattr(obj, "proxyType", self.p_type)
        setattr(obj, self.name, value)


class Proxy:
    """Proxy contains information about proxy type and necessary proxy
    settings."""

    proxyType = ProxyType.UNSPECIFIED
    autodetect = False
    ftpProxy = ""
    httpProxy = ""
    noProxy = ""
    proxyAutoconfigUrl = ""
    sslProxy = ""
    socksProxy = ""
    socksUsername = ""
    socksPassword = ""
    socksVersion = None

    # create descriptor type objects
    auto_detect = _ProxyTypeDescriptor("autodetect", ProxyType.AUTODETECT)
    """Gets and Sets `auto_detect`

    Usage:
    ------
    - Get
        - `self.auto_detect`
    - Set
        - `self.auto_detect` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    ftp_proxy = _ProxyTypeDescriptor("ftpProxy", ProxyType.MANUAL)
    """Gets and Sets `ftp_proxy`

    Usage:
    ------
    - Get
        - `self.ftp_proxy`
    - Set
        - `self.ftp_proxy` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    http_proxy = _ProxyTypeDescriptor("httpProxy", ProxyType.MANUAL)
    """Gets and Sets `http_proxy`

    Usage:
    ------
    - Get
        - `self.http_proxy`
    - Set
        - `self.http_proxy` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    no_proxy = _ProxyTypeDescriptor("noProxy", ProxyType.MANUAL)
    """Gets and Sets `no_proxy`

    Usage:
    ------
    - Get
        - `self.no_proxy`
    - Set
        - `self.no_proxy` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    proxy_autoconfig_url = _ProxyTypeDescriptor("proxyAutoconfigUrl", ProxyType.PAC)
    """Gets and Sets `proxy_autoconfig_url`

    Usage:
    ------
    - Get
        - `self.proxy_autoconfig_url`
    - Set
        - `self.proxy_autoconfig_url` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    ssl_proxy = _ProxyTypeDescriptor("sslProxy", ProxyType.MANUAL)
    """Gets and Sets `ssl_proxy`

    Usage:
    ------
    - Get
        - `self.ssl_proxy`
    - Set
        - `self.ssl_proxy` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    socks_proxy = _ProxyTypeDescriptor("socksProxy", ProxyType.MANUAL)
    """Gets and Sets `socks_proxy`

    Usage:
    ------
    - Get
        - `self.sock_proxy`
    - Set
        - `self.socks_proxy` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    socks_username = _ProxyTypeDescriptor("socksUsername", ProxyType.MANUAL)
    """Gets and Sets `socks_password`

    Usage:
    ------
    - Get
        - `self.socks_password`
    - Set
        - `self.socks_password` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    socks_password = _ProxyTypeDescriptor("socksPassword", ProxyType.MANUAL)
    """Gets and Sets `socks_password`

    Usage:
    ------
    - Get
        - `self.socks_password`
    - Set
        - `self.socks_password` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    socks_version = _ProxyTypeDescriptor("socksVersion", ProxyType.MANUAL)
    """Gets and Sets `socks_version`

    Usage:
    ------
    - Get
        - `self.socks_version`
    - Set
        - `self.socks_version` = `value`

    Parameters:
    -----------
    `value`: `str`
    """

    def __init__(self, raw=None):
        """Creates a new Proxy.

        :Args:
         - raw: raw proxy data. If None, default class values are used.
        """
        if raw:
            if "proxyType" in raw and raw["proxyType"]:
                self.proxy_type = ProxyType.load(raw["proxyType"])
            if "ftpProxy" in raw and raw["ftpProxy"]:
                self.ftp_proxy = raw["ftpProxy"]
            if "httpProxy" in raw and raw["httpProxy"]:
                self.http_proxy = raw["httpProxy"]
            if "noProxy" in raw and raw["noProxy"]:
                self.no_proxy = raw["noProxy"]
            if "proxyAutoconfigUrl" in raw and raw["proxyAutoconfigUrl"]:
                self.proxy_autoconfig_url = raw["proxyAutoconfigUrl"]
            if "sslProxy" in raw and raw["sslProxy"]:
                self.sslProxy = raw["sslProxy"]
            if "autodetect" in raw and raw["autodetect"]:
                self.auto_detect = raw["autodetect"]
            if "socksProxy" in raw and raw["socksProxy"]:
                self.socks_proxy = raw["socksProxy"]
            if "socksUsername" in raw and raw["socksUsername"]:
                self.socks_username = raw["socksUsername"]
            if "socksPassword" in raw and raw["socksPassword"]:
                self.socks_password = raw["socksPassword"]
            if "socksVersion" in raw and raw["socksVersion"]:
                self.socks_version = raw["socksVersion"]

    @property
    def proxy_type(self):
        """Returns proxy type as `ProxyType`."""
        return self.proxyType

    @proxy_type.setter
    def proxy_type(self, value) -> None:
        """Sets proxy type.

        :Args:
         - value: The proxy type.
        """
        self._verify_proxy_type_compatibility(value)
        self.proxyType = value

    def _verify_proxy_type_compatibility(self, compatible_proxy):
        if self.proxyType not in (ProxyType.UNSPECIFIED, compatible_proxy):
            raise ValueError(
                f"Specified proxy type ({compatible_proxy}) not compatible with current setting ({self.proxyType})"
            )

    def to_capabilities(self):
        proxy_caps = {"proxyType": self.proxyType["string"].lower()}
        proxies = [
            "autodetect",
            "ftpProxy",
            "httpProxy",
            "proxyAutoconfigUrl",
            "sslProxy",
            "noProxy",
            "socksProxy",
            "socksUsername",
            "socksPassword",
            "socksVersion",
        ]
        for proxy in proxies:
            attr_value = getattr(self, proxy)
            if attr_value:
                proxy_caps[proxy] = attr_value
        return proxy_caps
