import logging
import urllib.parse

from detectem.utils import (
    get_url,
    get_most_complete_pm,
    get_version_via_file_hashes,
)
from detectem.settings import (
    INDICATOR_TYPE,
    HINT_TYPE,
    MAIN_ENTRY,
    RESOURCE_ENTRY,
    INLINE_SCRIPT_ENTRY,
    GENERIC_TYPE,
)
from detectem.matchers import (UrlMatcher, BodyMatcher, HeaderMatcher, XPathMatcher)
from detectem.results import Result, ResultCollection

logger = logging.getLogger('detectem')
MATCHERS = {
    'url': UrlMatcher(),
    'body': BodyMatcher(),
    'header': HeaderMatcher(),
    'xpath': XPathMatcher(),
}


class HarProcessor:
    ''' This class process the HAR list returned by Splash
        adding some markers useful for matcher application
    '''

    @staticmethod
    def _set_entry_type(entry, entry_type):
        entry.setdefault('detectem', {})['type'] = entry_type

    def _get_location(self, entry):
        ''' Return `Location` header value if it's present in ``entry`` '''
        headers = entry['response'].get('headers', [])

        for header in headers:
            if header['name'] == 'Location':
                return header['value']

        return None

    def mark_entries(self, entries):
        ''' Mark one entry as main entry and the rest as resource entry.

            Main entry is the entry that contain response's body
            of the requested URL.
        '''

        for entry in entries:
            self._set_entry_type(entry, RESOURCE_ENTRY)

        # If first entry doesn't have a redirect, set is as main entry
        main_entry = entries[0]
        main_location = self._get_location(main_entry)
        if not main_location:
            self._set_entry_type(main_entry, MAIN_ENTRY)
            return

        # Resolve redirected URL and see if it's in the rest of entries
        main_url = urllib.parse.urljoin(get_url(main_entry), main_location)
        for entry in entries[1:]:
            url = get_url(entry)
            if url == main_url:
                self._set_entry_type(entry, MAIN_ENTRY)
                break
        else:
            # In fail case, set the first entry
            self._set_entry_type(main_entry, MAIN_ENTRY)

    def _script_to_har_entry(self, script, url):
        ''' Return entry for embed script '''
        entry = {
            'request': {'url': url},
            'response': {'url': url, 'content': {'text': script}}
        }

        self._set_entry_type(entry, INLINE_SCRIPT_ENTRY)
        return entry

    def prepare(self, response, url):
        har = response.get('har', [])
        if har:
            self.mark_entries(har)

        # Detect embed scripts and add them to HAR list
        for script in response.get('scripts', []):
            har.append(self._script_to_har_entry(script, url))

        return har


class Detector():
    def __init__(self, response, plugins, requested_url):
        self.requested_url = requested_url
        self.har = HarProcessor().prepare(response, requested_url)

        self._softwares_from_splash = response['softwares']
        self._plugins = plugins
        self._results = ResultCollection()

    @staticmethod
    def _get_entry_type(entry):
        ''' Return entry type. '''
        return entry['detectem']['type']

    def get_hints(self, plugin):
        ''' Return plugin hints from ``plugin``. '''
        hints = []

        for hint_name in getattr(plugin, 'hints', []):
            hint_plugin = self._plugins.get(hint_name)
            if hint_plugin:
                hint_result = Result(
                    name=hint_plugin.name,
                    homepage=hint_plugin.homepage,
                    from_url=self.requested_url,
                    type=HINT_TYPE,
                )
                hints.append(hint_result)

                logger.debug(f'{plugin.name} & hint {hint_result.name} detected')
            else:
                logger.error(f'{plugin.name} hints an invalid plugin: {hint_name}')

        return hints

    def process_from_splash(self):
        ''' Add softwares found in the DOM '''
        for software in self._softwares_from_splash:
            plugin = self._plugins.get(software['name'])

            # Determine if it's a version or presence result
            try:
                additional_data = {'version': software['version']}
            except KeyError:
                additional_data = {'type': INDICATOR_TYPE}

            self._results.add_result(
                Result(
                    name=plugin.name,
                    homepage=plugin.homepage,
                    from_url=self.requested_url,
                    **additional_data,
                )
            )

            for hint in self.get_hints(plugin):
                self._results.add_result(hint)

    def _get_matchers_for_entry(self, plugin, entry):
        grouped_matchers = plugin.get_grouped_matchers()

        def remove_group(group):
            if group in grouped_matchers:
                del grouped_matchers[group]

        if self._get_entry_type(entry) == 1:
            remove_group('body')
            remove_group('url')
        else:
            remove_group('header')
            remove_group('xpath')

        remove_group('dom')

        return grouped_matchers

    def apply_plugin_matchers(self, plugin, entry):
        data_list = []
        grouped_matchers = self._get_matchers_for_entry(plugin, entry)

        for matcher_type, matchers in grouped_matchers.items():
            klass = MATCHERS[matcher_type]
            plugin_match = klass.get_info(entry, *matchers)
            if plugin_match.name or plugin_match.version or plugin_match.presence:
                data_list.append(plugin_match)

        return get_most_complete_pm(data_list)

    def process_har(self):
        """ Detect plugins present in the page. """
        hints = []

        version_plugins = self._plugins.with_version_matchers()
        generic_plugins = self._plugins.with_generic_matchers()

        for entry in self.har:
            for plugin in version_plugins:
                pm = self.apply_plugin_matchers(plugin, entry)
                if not pm:
                    continue

                # Set name if matchers could detect modular name
                if pm.name:
                    name = '{}-{}'.format(plugin.name, pm.name)
                else:
                    name = plugin.name

                if pm.version:
                    self._results.add_result(
                        Result(
                            name=name,
                            version=pm.version,
                            homepage=plugin.homepage,
                            from_url=get_url(entry),
                        )
                    )
                elif pm.presence:
                    # Try to get version through file hashes
                    version = get_version_via_file_hashes(plugin, entry)
                    if version:
                        self._results.add_result(
                            Result(
                                name=name,
                                version=version,
                                homepage=plugin.homepage,
                                from_url=get_url(entry),
                            )
                        )
                    else:
                        self._results.add_result(
                            Result(
                                name=name,
                                homepage=plugin.homepage,
                                from_url=get_url(entry),
                                type=INDICATOR_TYPE,
                            )
                        )
                hints += self.get_hints(plugin)

            for plugin in generic_plugins:
                pm = self.apply_plugin_matchers(plugin, entry)
                if pm:
                    plugin_data = plugin.get_information(entry)

                    # Only add to results if it's a valid result
                    if 'name' in plugin_data:
                        self._results.add_result(
                            Result(
                                name=plugin_data['name'],
                                homepage=plugin_data['homepage'],
                                from_url=get_url(entry),
                                type=GENERIC_TYPE,
                            )
                        )

        for hint in hints:
            self._results.add_result(hint)

    def get_results(self, metadata=False):
        """ Return results of the analysis. """
        results_data = []

        self.process_har()
        self.process_from_splash()

        for rt in sorted(self._results.get_results()):
            rdict = {'name': rt.name}
            if rt.version:
                rdict['version'] = rt.version

            if metadata:
                rdict['homepage'] = rt.homepage
                rdict['type'] = rt.type
                rdict['from_url'] = rt.from_url

            results_data.append(rdict)

        return results_data

@@ -5054,17 +5054,26 @@
 try) == 
-1
+MAIN_ENTRY
 :%0A      

Fix bug introduced pending testing feature