"""General functions and classes used by other modules."""

import argparse
import enum
import os
import unittest
import requests
from types import TracebackType
from typing import Any, Iterable, List, TypeVar, Union

from scenery import config

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

import yaml



###################
# TYPES
###################

# NOTE mad: this is here to prevent circular import and still use those types
# either to check isinstance(x, cls) (see reponse_checker for instance),
# and type checking



class RemoteBackendTestCase(unittest.TestCase):
    """A TestCase for backend testing on a remote server."""
    mode: str
    session: requests.Session
    base_url: str
    headers: dict[str, str]

class RemoteFrontendTestCase(unittest.TestCase):
    """A TestCase for backend testing on a remote server."""
    mode: str
    driver: webdriver.Chrome
    # session: requests.Session
    base_url: str
    headers: dict[str, str]

class LoadTestCase(unittest.TestCase):
    """A TestCase for load testing on a remote server."""
    mode: str
    session: requests.Session
    headers: dict[str, str]
    base_url: str
    data: dict[str, List[dict[str, int|float]]]
    users:int
    requests_per_user:int


# NOTE mad: proper implementation is in django utils
class DjangoBackendTestCase:
    def __init__(self, *args, **kwargs):
            ImportError("django required")

class DjangoFrontendTestCase:
    def __init__(self, *args, **kwargs):
            ImportError("django required")


class Framework(enum.Enum):
    DJANGO = "django"


SceneryTestCaseTypes = Union[RemoteBackendTestCase, RemoteFrontendTestCase, LoadTestCase]

SceneryTestCase = TypeVar("SceneryTestCase", bound=SceneryTestCaseTypes)



###################
# SELENIUM
###################

def build_selenium_chrome_options_and_service(
        chrome_options_arguments=None, 
        page_load_strategy="eager",
        capabilities=None,
        binary_location=None,
        chromedriver_path=None,
    ) -> Options:


    # Configuration du service
    if chromedriver_path:
        print(f"SETTING CHROMEDRIVER PATH {chromedriver_path}")
        service = Service(
            chromedriver_path= chromedriver_path
        )
    else:
        service = None

    chrome_options = Options()

    # NOTE mad: used to wait until domcontent loaded (default is eager)
    # see: https://www.selenium.dev/documentation/webdriver/drivers/options/
    chrome_options.page_load_strategy = page_load_strategy

    if binary_location:
        print(f"SETTING BINARY {binary_location}")
        # Important pour les environnements containerisés
        chrome_options.binary_location = binary_location

    if capabilities:
        for key, val in capabilities:
            print(f"SETTING CAPABILITY {key} : {val}")

            # Set desired capabilities explicitly
            chrome_options.set_capability(key, val)

    if chrome_options_arguments:
        for argument in chrome_options_arguments:
            print(f"ADDING ARGUMENT {argument}")
            chrome_options.add_argument(f"--{argument}")

    return chrome_options, service
    

def get_selenium_driver(
        implicitly_wait: int = 10,  
        page_load_timeout: int = 30, 
        chrome_options_arguments=None,
        page_load_strategy="eager",
        capabilities=None,
        binary_location=None,
        chromedriver_path=None,

        ) -> webdriver.Chrome:
    """Return a Selenium WebDriver instance configured for Chrome.

    Args:
        page_load_timeout (int): Maximum time in seconds to wait for the page to load.
            If the timeout is exceeded, a TimeoutException will be thrown.

    Returns:
        webdriver.Chrome: Configured Chrome WebDriver instance.
    """


    chrome_options, service = build_selenium_chrome_options_and_service(
        chrome_options_arguments=chrome_options_arguments,
        page_load_strategy=page_load_strategy,
        capabilities=capabilities,
        binary_location=binary_location,
        chromedriver_path=chromedriver_path,
        )
    
    print("LA", binary_location, chromedriver_path)


    try:
        if service:
            driver = webdriver.Chrome(options=chrome_options, service=service)
        else:
            driver = webdriver.Chrome(options=chrome_options)
            
    except Exception as e:

        print("Could not initiate selenium properly")
        print(e)
        import sys
        sys.exit(1)

    driver.implicitly_wait(implicitly_wait)
    driver.set_page_load_timeout(page_load_timeout)

    
    return driver





########
# YAML #
########


def read_yaml(filename: str) -> Any:
    """Read and parse a YAML file.

    Args:
        filename (str): The path to the YAML file to be read.

    Returns:
        Any: The parsed content of the YAML file.

    Raises:
        yaml.YAMLError: If there's an error parsing the YAML file.
        IOError: If there's an error reading the file.
    """
    with open(filename, "r") as f:
        return yaml.safe_load(f)


def iter_on_manifests(args: argparse.Namespace) -> Iterable[str]:
    for filename in os.listdir(config.manifests_folder):
        if args.manifest is not None and filename.replace(".yml", "") != args.manifest:
            continue

        yield filename



##################
# UNITTEST
##################

class CustomTestResult(unittest.TestResult):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.all_tests = []
        self.successes = []
    
    def startTest(self, test):
        super().startTest(test)
        self.all_tests.append(test)
    
    def addSuccess(self, test):
        super().addSuccess(test)
        self.successes.append(test)

    def addError(
        self,
        test: unittest.TestCase,
        err: (tuple[type[BaseException], BaseException, TracebackType] | tuple[None, None, None]),
    ) -> None:
        super().addError(test, err)
        self.caught_exception = err


    def as_dict(self):

        d = {}

        for test, traceback in self.errors:
            d[test.id()] = {
                "passed": False,
                "status": "error",
                "traceback": traceback
            }
            

        for test, traceback in self.failures:
            d[test.id()]  = {
                "passed": False,
                "status": "failure",
                "traceback": traceback
            }
            
        for test in self.successes:
            d[test.id()] = {
                "passed": True,
                "status": "sucess",
                "traceback": None
            }
        
        return d




