from collections import abc

from scrapgo.lib import select_kwargs, filter_params, find_function, curl2requests, assingles, pluralize
from .base import UrlRenderAction, UrlPatternAction, FileAction, CurlAction
from .exceptions import *



class ReduceMixin:

    def get_action(self, name):
        for action in self.urlorders:
            if action.name == name:
                return action    

    def dispatch_response(self, action, **kwargs):
        if isinstance(action, CurlAction):
            command = kwargs.get('url')
            request_kwargs = curl2requests(command)
            for key in ['headers', 'cookies', 'proxies']:
                curlval = request_kwargs.get(key)
                cmnval = kwargs.get(key)
                if curlval is not None or cmnval is not None:
                    curlval = curlval or {}
                    curlval.update(cmnval or {})
                    request_kwargs[key] = curlval

            return self.fetch(refresh=action.refresh, delay=action.delay, **request_kwargs)

        method = action.method or ('post' if kwargs.get('data') else 'get')
        return self.fetch(
            method=method, delay=action.delay,
            ignore_status_codes=action.ignore_status_codes,
            allowable_status_codes=action.allowable_status_codes,
            refresh=action.refresh,
            force_refresh=action.force_refresh,
            only_if_cached=action.only_if_cached,
            expire_after=action.expire_after,
            **kwargs
        )

    @assingles
    def dispatch_renderer(self, action, prev_results, parent_response, context):
        if isinstance(action, UrlRenderAction):
            if urlrenderer := action.urlrenderer:
                return self.dispatch(
                    'urlrenderer', urlrenderer,
                    url=action.url, prev=prev_results, context=context, **context
                )                
            return action.url

        elif isinstance(action, UrlPatternAction):
            if urlpattern_renderer := action.urlpattern_renderer:
                urlpattern = self.dispatch(
                    'urlpattern_renderer', urlpattern_renderer, pattern=action.urlpattern, prev=prev_results,
                    context=context, **context
                )
                if urlpattern is None:
                    raise UrlRendererError(f"{urlpattern_renderer} must return regex pattern of url, not {urlpattern}")
            else:
                urlpattern = action.urlpattern
            kwargs = {
                **action.as_kwargs(),
                'urlpattern': urlpattern
            }
            return self.parse_linkpattern(parent_response.content, **kwargs)
    
        elif isinstance(action, CurlAction):
            if command_renderer:= action.command_renderer:
                return self.dispatch(
                    'curlrenderer', command_renderer,
                    command=action.command,
                    prev=prev_results,
                    context=context,
                    **context
                )
            return action.command
        elif isinstance(action, FileAction):
            if path_renderer := action.path_renderer:
                return self.dispatch(
                    'path_renderer', path_renderer, path=action.path,
                    prev=prev_results, context=context, **context
                )
            return action.path
        else:
            raise CannotFindAction(f'{action} is not memeber of renderer action')

    @assingles
    def dispatch_payloader(self, action, prev_results, context):
        if not hasattr(action, 'payloader') or not action.payloader:
            return {}
        else:
            return self.dispatch(
                'payloader', action.payloader,
                prev=prev_results, context=context, **context
            )
            
    def dispatch_fields(self, action, url):
        if not hasattr(action, 'fields'):
            return url
        url = filter_params(url, action.fields)
        return url

    def dispatch_headers(self, action):
        headers = self.get_headers()
        if hasattr(action, 'headers'):
            headers.update(action.headers or {})
        return headers
    
    def dispatch_cookies(self, action):
        cookies = self.get_cookies()
        if hasattr(action, 'cookies'):
            cookies.update(action.cookies or {})
        return cookies

    def dispatch_extractor(self, action, soup, response, prev_results, results_cache, context):
        extractset = {}
        if module := action.extractor:
            pat = r'^extract_(?P<ext>\w+)$'
            for g, func in find_function(module, pat):
                extracted = select_kwargs(
                    func,
                    response=response,
                    soup=soup,
                    prev=prev_results,
                    results_set=results_cache, context=context, **context
                )
                extractset[g('ext')] = self.validate_extracted(extracted, func, soup)
        return extractset

    def dispatch_parser(self, action, response, extracted, soup, prev_results, results_cache, context):
        results = self.dispatch(
            'parser', action.parser,
            response=response, parsed=extracted, soup=soup,
            prev=prev_results,
            results_set=results_cache, context=context, **context
        )

        resultset = []

        if isinstance(results, abc.Generator):
            for result in results:
                if isinstance(result, tuple):
                    if len(result) != 2:
                        raise ParsedResultsError('Parsed results consist of two parts tuple: results, context.')
                    res, ctx = result
                    resultset.append((res, {**context, **ctx}))
                else:
                    resultset.append((result, context))
        else:
            if results is None:
                resultset.append(([], context))
            if isinstance(results, tuple):
                if len(results) != 2:
                    raise ParsedResultsError('Parsed results consist of two parts tuple: results, context.')
                res, ctx = results
                resultset.append((res, {**context, **ctx}))
            else:
                resultset.append((results, context))

        return resultset

            
            
            






        # if not isinstance(results, abc.Generator):
        #     if results is None:
        #         results = []
            
        #     if isinstance(results, tuple):
        #         if len(results) != 2:
        #             raise ParsedResultsError('Parsed results consist of two parts: results and context.')
        #         results, ctx = results
        #         context.update(ctx)

        # records = []
        # contexts = []
        # for result in results:        
        #     if isinstance(result, tuple):
        #         if len(result) != 2:
        #             raise ParsedResultsError('Parsed results consist of two parts: results and context.')
        #         result, sub_context = result
        #         if not isinstance(context, abc.Mapping):
        #             raise ParsedResultsError('context must be a dictionary object')
        #         # context.update(sub_context)
        #         contexts.append(sub_context)
        #         records.append(result)
        #         continue
        #     contexts.append(context)
        #     records.append(result)
        # records = pluralize(records)
        # return records, contexts

    def dispatch_urlfilter(self, action, url, responsemap, prev_results, context):
        if not hasattr(action, 'urlfilter'):
            return True

        return self.dispatch(
            'urlfilter', action.urlfilter,
            url=url, responsemap=responsemap, prev=prev_results, context=context, **context
        )
    
    # def dispatch_referer(self, action, response):
    #     headers = self.get_headers()
    #     if not hasattr(action, 'referer'):
    #         return headers
        
    #     if action.referer and response:
    #         try:
    #             referer = response.crawler.responsemap[action.referer]
    #         except KeyError:
    #             raise CannotFindAction(f"An action named {action.referer} could not be found at urlorders.")
    #         else:
    #             headers['Referer'] = referer.url
    #     return headers

    def dispatch_compose(self, results_set):
        if hasattr(self, 'compose'):
            for action in self.urlorders:
                results_set.setdefault(action.name, [])
            composed = self.dispatch(
                'compose', self.compose,
                **results_set
            )
            return composed

    def dispatch_onfailure(self, action, exception, response, prev_results, context, **requests_kwargs):
        self.dispatch(
            'onfailure', action.onfailure,
            response=response,
            exception=exception,
            prev=prev_results,
            context=context,
            **context,
            **requests_kwargs
        )

    def dispatch_ignore_status_codes(self, action, response):
        if hasattr(action, 'ignore_status_codes'):
            ignore_codes =  action.ignore_status_codes or []
            return response.status_code in ignore_codes
        return False
    
    def dispatch_allowable_status_codes(self, action, response):
        if hasattr(action, 'allowable_status_codes'):
            alloable_codes = action.allowable_status_codes or []
            return response.status_code in alloable_codes
        return False

    def dispatch(self, type, func, *args, **kwargs):
        if callable(func):
            f = func
        elif isinstance(func, str):
            if hasattr(self, func):
                f = getattr(self, func)
            else:
                raise NotImplementedError(f"The method {func} is not implemented!")
        else:
            f = {
                'urlfilter': self.default_urlfilter,
                'parser': self.default_parser,
                'urlrenderer': self.default_urlrenderer,
                'urlpattern_renderer': self.default_pattern_renderer,
                'breaker': self.default_breaker,
                'onfailure': self.onfailure,
                'payloader': self.default_payloader,
                'curlrenderer': self.default_curlrenderer,
                'path_renderer': self.default_path_renderer,
            }[type]

        return select_kwargs(f, *args, **kwargs)
    

    def default_urlfilter(self, url, **kwargs):
        return True

    def onfailure(self, exception, **kwargs):
        raise
    
    def default_breaker(self, response, **kwargs):
        return False

    def default_parser(self, response, **kwargs):
        return
    
    def default_urlrenderer(self, url, response, **kwargs):
        yield url
    
    def default_curlrenderer(self, curl, resposne, **kwargs):
        return curl
    
    def default_path_renderer(self, path, response, **kwargs):        
        return path
        
    def default_pattern_renderer(self, pattern, resposne, **kwargs):
        return pattern
    
    def default_breaker(self, response, **kwargs):
        return False
    
    def default_payloader(self):
        yield