from typing import (
    Dict,
    List,
    Optional
)

# from selenium import webdriver
from selenium.webdriver.remote.webdriver import WebDriver

from quilla.ctx import Context
from quilla.browser import drivers
from quilla.common.enums import (
    BrowserTargets,
)
from quilla.steps.steps_aggregator import StepsAggregator
from quilla.reports.base_report import BaseReport
from quilla.common.exceptions import InvalidBrowserStateException


BrowserSelector = Dict[BrowserTargets, WebDriver]


class BrowserValidations:
    '''
    A class that defines the behaviours for validating a set of steps
    for a specific browser target.

    Args:
        ctx: The runtime context for the application
        target: An enum specifying the target browser that is desired
        url_root: The initial url for the browser to navigate to
        steps: An aggregator class that manages all substeps

    Attributes:
        ctx: The runtime context for the application
    '''
    driver_selector: BrowserSelector = {
        BrowserTargets.FIREFOX: drivers.FirefoxBrowser,
        BrowserTargets.CHROME: drivers.ChromeBrowser,
        BrowserTargets.EDGE: drivers.EdgeBrowser,
    }

    def __init__(
        self,
        ctx: Context,
        target: BrowserTargets,
        url_root: str,
        steps: StepsAggregator,
    ) -> None:
        self._target = target
        self._root = url_root
        self._steps = steps
        self._driver: Optional[WebDriver] = None
        self.ctx = ctx

    @property
    def target(self):
        return self._target

    @target.setter
    def target(self, v: BrowserTargets):
        if self._driver is not None:
            raise InvalidBrowserStateException(
                'Cannot change browser target while a driver is set'
            )
        self._target = v

    def init(self):
        '''
        Creates the appropriate driver, sets the start URL to the specified root, and
        sets all steps to have the appropriate driver
        '''
        self.ctx.logger.debug('Initializing browser %s', self._target.value)
        driver: WebDriver = self.driver_selector[self._target](self.ctx)
        self._driver = driver
        driver.get(self._root)
        self._steps.driver = driver  # Set the driver for all the steps
        self.ctx.driver = driver

    def run_steps(self) -> List[BaseReport]:
        '''
        Executes all stored steps. Pass through method for the steps run_steps method

        Returns:
            A list of reports generated by the steps
        '''
        return self._steps.run_steps()

    def clean(self):
        '''
        Closes the browser instance and resets all the step drivers to None state
        '''
        self.ctx.logger.debug('Cleaning up finished browser')
        if self.ctx.close_browser:
            try:
                self._driver.close()
            except Exception as e:
                self.ctx.logger.debug('Could not close browser due to %e', e, exc_info=True)
                pass  # Browser unable to be closed or is already closed
        self._driver = None
        self._steps.driver = None
        self.ctx.driver = None

    def validate(self) -> List[BaseReport]:
        '''
        Initializes the browser, runs the execution steps, and closes up the browser while
        ensuring that any exceptions still allow for cleanup of the browser

        Returns:
            A list of reports produced by the run_steps function

        Raises:
            Exception: Any exception produced by the run_steps function
        '''
        self.init()
        reports = []
        try:
            reports = self.run_steps()
        except Exception as e:
            # Catch exception and crash the program, but clean up
            # the browser first
            self.ctx.logger.error(
                'Exception %s raised for browser %s while running steps',
                e,
                self._target.value,
                exc_info=True,
            )
            raise e
        finally:
            self.clean()

        return reports
