# 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.
import base64
import os
import socket
from enum import Enum
from typing import Optional
from urllib import parse

import certifi

from selenium.webdriver.common.proxy import Proxy
from selenium.webdriver.common.proxy import ProxyType


class AuthType(Enum):
    BASIC = "Basic"
    BEARER = "Bearer"
    X_API_KEY = "X-API-Key"


class ClientConfig:
    def __init__(
        self,
        remote_server_addr: str,
        keep_alive: Optional[bool] = True,
        proxy: Optional[Proxy] = Proxy(raw={"proxyType": ProxyType.SYSTEM}),
        ignore_certificates: Optional[bool] = False,
        init_args_for_pool_manager: Optional[dict] = None,
        timeout: Optional[int] = None,
        ca_certs: Optional[str] = None,
        username: Optional[str] = None,
        password: Optional[str] = None,
        auth_type: Optional[AuthType] = AuthType.BASIC,
        token: Optional[str] = None,
        user_agent: Optional[str] = None,
        extra_headers: Optional[dict] = None,
    ) -> None:
        self.remote_server_addr = remote_server_addr
        self.keep_alive = keep_alive
        self.proxy = proxy
        self.ignore_certificates = ignore_certificates
        self.init_args_for_pool_manager = init_args_for_pool_manager or {}
        self.timeout = timeout
        self.username = username
        self.password = password
        self.auth_type = auth_type
        self.token = token
        self.user_agent = user_agent
        self.extra_headers = extra_headers

        self.timeout = (
            (
                float(os.getenv("GLOBAL_DEFAULT_TIMEOUT", str(socket.getdefaulttimeout())))
                if os.getenv("GLOBAL_DEFAULT_TIMEOUT") is not None
                else socket.getdefaulttimeout()
            )
            if timeout is None
            else timeout
        )

        self.ca_certs = (
            (os.getenv("REQUESTS_CA_BUNDLE") if "REQUESTS_CA_BUNDLE" in os.environ else certifi.where())
            if ca_certs is None
            else ca_certs
        )

    @property
    def remote_server_addr(self) -> str:
        """:Returns: The address of the remote server."""
        return self._remote_server_addr

    @remote_server_addr.setter
    def remote_server_addr(self, value: str) -> None:
        """Provides the address of the remote server."""
        self._remote_server_addr = value

    @property
    def keep_alive(self) -> bool:
        """:Returns: The keep alive value."""
        return self._keep_alive

    @keep_alive.setter
    def keep_alive(self, value: bool) -> None:
        """Toggles the keep alive value.

        :Args:
         - value: whether to keep the http connection alive
        """
        self._keep_alive = value

    @property
    def proxy(self) -> Proxy:
        """:Returns: The proxy used for communicating to the driver/server."""
        return self._proxy

    @proxy.setter
    def proxy(self, proxy: Proxy) -> None:
        """Provides the information for communicating with the driver or
        server.
        For example: Proxy(raw={"proxyType": ProxyType.SYSTEM})

        :Args:
         - value: the proxy information to use to communicate with the driver or server
        """
        self._proxy = proxy

    @property
    def ignore_certificates(self) -> bool:
        """:Returns: The ignore certificate check value."""
        return self._ignore_certificates

    @ignore_certificates.setter
    def ignore_certificates(self, ignore_certificates: bool) -> None:
        """Toggles the ignore certificate check.

        :Args:
         - value: value of ignore certificate check
        """
        self._ignore_certificates = ignore_certificates

    @property
    def init_args_for_pool_manager(self) -> dict:
        """:Returns: The dictionary of arguments will be appended while
        initializing the pool manager."""
        return self._init_args_for_pool_manager

    @init_args_for_pool_manager.setter
    def init_args_for_pool_manager(self, init_args_for_pool_manager: dict) -> None:
        """Provides dictionary of arguments will be appended while initializing the pool manager.
        For example: {"init_args_for_pool_manager": {"retries": 3, "block": True}}

        :Args:
         - value: the dictionary of arguments will be appended while initializing the pool manager
        """
        self._init_args_for_pool_manager = init_args_for_pool_manager

    @property
    def timeout(self) -> int:
        """:Returns: The timeout (in seconds) used for communicating to the
        driver/server."""
        return self._timeout

    @timeout.setter
    def timeout(self, timeout: int) -> None:
        """Provides the timeout (in seconds) for communicating with the driver
        or server.

        :Args:
         - value: the timeout (in seconds) to use to communicate with the driver or server
        """
        self._timeout = timeout

    def reset_timeout(self) -> None:
        """Resets the timeout to the default value of socket."""
        self._timeout = socket.getdefaulttimeout()

    @property
    def ca_certs(self) -> str:
        """:Returns: The path to bundle of CA certificates."""
        return self._ca_certs

    @ca_certs.setter
    def ca_certs(self, ca_certs: str) -> None:
        """Provides the path to bundle of CA certificates for establishing
        secure connections.

        :Args:
         - value: the path to bundle of CA certificates for establishing secure connections
        """
        self._ca_certs = ca_certs

    @property
    def username(self) -> str:
        """Returns the username used for basic authentication to the remote
        server."""
        return self._username

    @username.setter
    def username(self, value: str) -> None:
        """Sets the username used for basic authentication to the remote
        server."""
        self._username = value

    @property
    def password(self) -> str:
        """Returns the password used for basic authentication to the remote
        server."""
        return self._password

    @password.setter
    def password(self, value: str) -> None:
        """Sets the password used for basic authentication to the remote
        server."""
        self._password = value

    @property
    def auth_type(self) -> AuthType:
        """Returns the type of authentication to the remote server."""
        return self._auth_type

    @auth_type.setter
    def auth_type(self, value: AuthType) -> None:
        """Sets the type of authentication to the remote server if it is not
        using basic with username and password.

        :Args: value - AuthType enum value. For others, please use `extra_headers` instead
        """
        self._auth_type = value

    @property
    def token(self) -> str:
        """Returns the token used for authentication to the remote server."""
        return self._token

    @token.setter
    def token(self, value: str) -> None:
        """Sets the token used for authentication to the remote server if
        auth_type is not basic."""
        self._token = value

    @property
    def user_agent(self) -> str:
        """Returns user agent to be added to the request headers."""
        return self._user_agent

    @user_agent.setter
    def user_agent(self, value: str) -> None:
        """Sets user agent to be added to the request headers."""
        self._user_agent = value

    @property
    def extra_headers(self) -> dict:
        """Returns extra headers to be added to the request."""
        return self._extra_headers

    @extra_headers.setter
    def extra_headers(self, value: dict) -> None:
        """Sets extra headers to be added to the request."""
        self._extra_headers = value

    def get_proxy_url(self) -> Optional[str]:
        """Returns the proxy URL to use for the connection."""
        proxy_type = self.proxy.proxy_type
        remote_add = parse.urlparse(self.remote_server_addr)
        if proxy_type is ProxyType.DIRECT:
            return None
        if proxy_type is ProxyType.SYSTEM:
            _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY"))
            if _no_proxy:
                for entry in map(str.strip, _no_proxy.split(",")):
                    if entry == "*":
                        return None
                    n_url = parse.urlparse(entry)
                    if n_url.netloc and remote_add.netloc == n_url.netloc:
                        return None
                    if n_url.path in remote_add.netloc:
                        return None
            return os.environ.get(
                "https_proxy" if self.remote_server_addr.startswith("https://") else "http_proxy",
                os.environ.get("HTTPS_PROXY" if self.remote_server_addr.startswith("https://") else "HTTP_PROXY"),
            )
        if proxy_type is ProxyType.MANUAL:
            return self.proxy.sslProxy if self.remote_server_addr.startswith("https://") else self.proxy.http_proxy
        return None

    def get_auth_header(self) -> Optional[dict]:
        """Returns the authorization to add to the request headers."""
        if self.auth_type is AuthType.BASIC and self.username and self.password:
            credentials = f"{self.username}:{self.password}"
            encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
            return {"Authorization": f"{AuthType.BASIC.value} {encoded_credentials}"}
        if self.auth_type is AuthType.BEARER and self.token:
            return {"Authorization": f"{AuthType.BEARER.value} {self.token}"}
        if self.auth_type is AuthType.X_API_KEY and self.token:
            return {f"{AuthType.X_API_KEY.value}": f"{self.token}"}
        return None
