import json
import os
import logging as _logging
from requests.structures import CaseInsensitiveDict


class Config:
    def __init__(self) -> None:
        # defining init, so IDE will think that all
        # Config instances, sub-instances has such inner values set
        self._init_config_values()

    def _init_config_values(self) -> None:
        self.PACKAGE_NAME = "dphelper"
        "Update this name after changing name in pyproject.toml"

        self.REQUEST_DROP_ON_FAILURE_COUNT = 4
        "How many `didnt connect` / `status >= 500` should occur sequentially before giving-up-on-request"

        self.REQUEST_INITIAL_SLEEP_AFTER_FAILURE_IN_S = 3
        "How many seconds to sleep after first request failure"

        self.REQUEST_SLEEP_INCREASE_POWER = 2
        "sleep time will be increased by this power. time ^ power"

        self.DP_URL = "https://dphelper.dataplatform.lt/"
        "url of server that stores snapshots, challenges, code runs. do not include ending slash"

        self.BACKEND_URL = "https://api.dataplatform.lt"
        "url of server that stores snapshots, challenges, code runs. do not include ending slash"

        self.BACKEND_SNAPSHOT_PATH = f"snapshots/"
        "backend url root for snapshots. it might be prepended in actual usage"

        self.BACKEND_UTILS_PATH = f"v1/utils/"
        "backend url root for utils. it might be appended in actual usage"

        self.IS_VERBOSE = False
        "verbose errors, warnings, etc.?"

        self.DEFAULT_HEADERS = {
            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
            "accept-language": "en-GB,en-US;q=0.9,en;q=0.8,lt;q=0.7",
            "cache-control": "no-cache",
            "pragma": "no-cache",
            "sec-ch-ua": '"Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"macOS"',
            "sec-fetch-dest": "document",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "none",
            "sec-fetch-user": "?1",
            "upgrade-insecure-requests": "1",
            "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
        }

        self.HEADERS_UPDATE_ON_EMPTY = {
            "Referer": "https://google.com",
            "Cache-Control": "max-age=0",
            "Upgrade-Insecure-Requests": "1",
        }
        "what headers to append when generating auto-headers? auto-headers already include user agent, accept, connection"

        self.PROXY_PROVIDER = None
        "what proxy provider to use for outter requests? Snapshots would still be fetched directly"

        self.PROXY_PARAMS = {}
        "what params to use for proxying"

        self.RETRYABLE_STATUS_CODE_RANGES = [(429, 429), (500, 599)]
        "what status code ranges are retryable?"

        self.IS_RETRY_ON_BLOCKED = False
        "retry on blocked requests? Something like 'Checking your browser'"

        self.IS_CRASH_ON_BLOCKED = False
        "raise exception on blocked request? Something like 'Checking your browser'"

        self.BLOCKED_KEYWORDS = [
            x.lower()
            for x in [
                "(encodeURI('challenge='",
                ",jsChallengeUrl)",
                "/jschallenge",
                "Checking your browser",
                "Just a moment",
                "blocked",
            ]
        ]
        "what keywords to look for in response to detect blocked request"

        self.BLOCKED_TRESHOLD = 50
        "rating varies from 0 to 100. From what value and above to consider request blocked?"

        self.SECRETS_FILE_PATH = "secrets.dphelper.json"
        'where to look for secrets file. Format: json. Example content: {"something": "value", "smth2": "value2"}'

        self.SECRET = dict()
        "dict with secrets. It is populated from secrets file"

    def set_backend_url(self, new_backend_url: str) -> None:
        """Sets backend url. Do not forget http, https thingies.

        :param new_backend_url: the url where backend is located
        """
        if new_backend_url is None or new_backend_url == "":
            raise ValueError("Cannot set backend url to None or empty string")

        self.BACKEND_URL = new_backend_url

    def set_dp_url(self, new_dp_url: str) -> None:
        """Sets dp url. Do not forget http, https thingies.

        :param new_backend_url: the url where backend is located
        """
        if new_dp_url is None or new_dp_url == "":
            raise ValueError("Cannot set dp url to None or empty string")

        self.DP_URL = new_dp_url

    def populate_secrets_from_file(
        self, *, is_environ_prioritized=False, is_load_environ=True
    ) -> None:
        """Populates secrets dict from file. File should be in json format. Example content: {"something": "value", "smth2": "value2"}

        :param is_environ_prioritized: on True order: loads secrets, loads environ (overwrites some secrets)
        """

        try:
            environ_dict = CaseInsensitiveDict(os.environ if is_load_environ else {})
            if not os.path.exists(self.SECRETS_FILE_PATH):
                if self.IS_VERBOSE:
                    _logging.warning(
                        f"Secrets file not found at '{os.path.abspath(os.path.join(os.getcwd(), self.SECRETS_FILE_PATH))}'.{' Thus DPHelper will initialize SECRET from <os.environ> only.' if is_load_environ else ''}"
                    )
                self.SECRET = environ_dict
                return
            with open(self.SECRETS_FILE_PATH, "r") as f:
                secret_dict = {
                    str(key): str(value) for key, value in json.load(f).items()
                }
                main_dict, updater_dict = (
                    (secret_dict, environ_dict)
                    if is_environ_prioritized
                    else (environ_dict, secret_dict)
                )
                self.SECRET = CaseInsensitiveDict(main_dict)
                self.SECRET.update(updater_dict)
        except Exception as e:
            raise Exception(
                f"Failed to load secrets from file {self.SECRETS_FILE_PATH}. Error: {str(e)}"
            )

    def export_into_environ(self):
        for k, v in self.SECRET.items():
            os.environ[str(k)] = str(v)
