# import from third parties
from dateutil.parser import parse
import json
import logging
from multiline_formatter.formatter import MultilineMessagesFormatter
import os
from routes.mapper import SubMapper
import sys
from collections import OrderedDict
from flask import Blueprint

# imports from ckan
import ckan.lib.helpers as helpers
from ckan.lib.plugins import DefaultTranslation, DefaultDatasetForm
from ckan.logic.validators import tag_string_convert
import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit
import ckan.model as model

# imports from this extension
import ckanext.odsh.helpers as odsh_helpers
import ckanext.odsh.helpers_tpsh as helpers_tpsh
import ckanext.odsh.helper_pkg_dict as helper_pkg_dict
from .helper_pkg_dict import HelperPgkDict
import ckanext.odsh.logic.action as action
import ckanext.odsh.validation as validation
import ckanext.odsh.search as search
from ckanext.odsh.odsh_logger import OdshLogger
import ckanext.odsh.tools as tools
from ckanext.odsh.views import default
from ckanext.odsh.views import package
from ckanext.odsh.views import user
from ckanext.odsh.views import dashboard
from ckanext.odsh.views import harvest
from ckanext.dcat import blueprints as dcat_view

log = logging.getLogger(__name__)

_ = toolkit._


class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm):
    plugins.implements(plugins.IActions)
    plugins.implements(plugins.IConfigurer)
    plugins.implements(plugins.IDatasetForm)
    plugins.implements(plugins.IFacets)
    plugins.implements(plugins.IPackageController, inherit=True)
    plugins.implements(plugins.IRoutes, inherit=True)
    plugins.implements(plugins.ITemplateHelpers)
    plugins.implements(plugins.ITranslation)
    plugins.implements(plugins.IValidators)
    plugins.implements(plugins.IResourceController, inherit=True)
    plugins.implements(plugins.IBlueprint)

    # IBlueprint
    def get_blueprint(self):
        log.info("OdshPlugin::get_blueprint")

        # Default
        bp_default = default.blueprint
        rules = [
            ('/info_page', 'info_page', default.info_page),
            ('/home', 'start', default.start),
            ('/not_found', 'not_found', default.not_found), ]
        for rule in rules:
            bp_default.add_url_rule(*rule)

        # DCAT
        # if toolkit.asbool(toolkit.config.get('ckanext.dcat.enable_rdf_endpoints', True)):
        #     odsh_helpers.odsh_remove_route(map, 'dcat_catalog')
        #     bp_default.add_url_rule('/catalog.<any("xml", "rdf", "n3", "ttl", "jsonld"):_format>', view_func=dcat_view.read_catalog, defaults={'_format': 'xml'}, methods=['GET'])

        # Package
        bp_package = package.blueprint
        rules = [
            ('/dataset/<id>/resource/<resource_id>', 'redirect_dataset_resource', package.redirect_dataset_resource), ]
        for rule in rules:
            bp_package.add_url_rule(*rule)

        # User
        bp_user = user.blueprint
        bp_user.add_url_rule(u'/user', endpoint='user_index',
                             view_func=user.index, strict_slashes=False)
        # bp_user.add_url_rule(u'/user/register', view_func=user.register)
        bp_user.add_url_rule(u'/user/activity/<id>', view_func=user.activity)
        bp_user.add_url_rule(u'/user/activity/<id>/<int:offset>', view_func=user.activity)
        
        # Dashboard
        bp_dashboard = dashboard.blueprint
        bp_dashboard.add_url_rule(
            u'/dashboard', view_func=dashboard.dashboard, strict_slashes=False, defaults={
                u'offset': 0
            })
        bp_dashboard.add_url_rule(
            u'/dashboard/<int:offset>', view_func=dashboard.dashboard)
        bp_dashboard.add_url_rule(
            u'/dashboard/datasets', view_func=dashboard.dashboard_datasets)

        #
        bp_harvest = harvest.blueprint
        bp_harvest.add_url_rule(u'/harvest/new', view_func=dashboard.new)
        return [bp_default, bp_package, bp_user, bp_dashboard, bp_harvest]

    # IActions

    def get_actions(self):
        return {'package_create': action.odsh_package_create,
                'user_update': action.tpsh_user_update,
                'user_create': action.odsh_user_create,
                'resource_create': action.odsh_resource_create, }

    # IConfigurer

    def update_config(self, config_):
        toolkit.add_template_directory(config_, 'templates')
        toolkit.add_public_directory(config_, 'public')
        toolkit.add_resource('fanstatic', 'ckanext-odsh')

    # IDatasetForm

    def package_types(self):
        # This plugin doesn't handle any special package types, it just
        # registers itself as the default (above).
        return []

    def is_fallback(self):
        # Return True to register this plugin as the default handler for
        # package types not handled by any other IDatasetForm plugin.
        return True

    def create_package_schema(self):
        schema = super(OdshPlugin, self).create_package_schema()
        self._update_schema(schema)
        self._tpsh_update_create_or_update_package_schema(schema)
        return schema

    def update_package_schema(self):
        schema = super(OdshPlugin, self).update_package_schema()
        self._update_schema(schema)
        self._tpsh_update_create_or_update_package_schema(schema)
        return schema

    def show_package_schema(self):
        schema = super(OdshPlugin, self).show_package_schema()
        self._tpsh_update_show_package_schema(schema)
        return schema

    def _update_schema(self, schema):
        for field in ['title', 'license_id']:
            schema.update({field: [toolkit.get_converter('not_empty')]})

        schema.update({
            'reference': [
                toolkit.get_validator('ignore_missing'),
                toolkit.get_converter('convert_to_extras')
            ]
        })

        for i, item in enumerate(schema['tags']['name']):
            if item == toolkit.get_validator('tag_name_validator'):
                schema['tags']['name'][i] = toolkit.get_validator(
                    'odsh_tag_name_validator')
        for i, item in enumerate(schema['tag_string']):
            if item == tag_string_convert:
                schema['tag_string'][i] = validation.tag_string_convert

        schema['resources'].update({
            'url': [toolkit.get_converter('not_empty')],
            'format': [toolkit.get_converter('odsh_validate_format')],
        })

        schema['extras'].update({
            'key': [
                toolkit.get_converter('known_spatial_uri'),
                toolkit.get_converter('validate_licenseAttributionByText'),
            ]
        })
        schema.update(
            {'__extras':  [toolkit.get_converter('odsh_validate_extras')]})

    def _tpsh_update_create_or_update_package_schema(self, schema):
        schema.update({
            'language': [
                toolkit.get_validator('ignore_missing'),
                toolkit.get_converter('convert_to_extras')
            ],
            'thumbnail': [
                toolkit.get_validator('ignore_missing'),
                toolkit.get_converter('convert_to_extras')
            ],
            'relatedPackage': [
                toolkit.get_validator('tpsh_validate_relatedPackage'),
                toolkit.get_converter('convert_to_extras')
            ],
            'accessibility': [
                toolkit.get_validator('ignore_missing'),
                toolkit.get_converter('convert_to_extras')
            ]
        })
        return schema

    def _tpsh_update_show_package_schema(self, schema):
        schema.update({
            'language': [
                toolkit.get_converter('convert_from_extras'),
                toolkit.get_validator('ignore_missing')
            ],
            'thumbnail': [
                toolkit.get_converter('convert_from_extras'),
                toolkit.get_validator('ignore_missing')
            ],
            'relatedPackage': [
                toolkit.get_converter('convert_from_extras'),
                toolkit.get_validator('ignore_missing')
            ],
            'accessibility': [
                toolkit.get_converter('convert_from_extras'),
                toolkit.get_validator('ignore_missing')
            ],
        })
        return schema

    # IFacets

    def dataset_facets(self, facets_dict, package_type):
        return OrderedDict({'organization': _('Organizations'),
                            'groups': _('Category'),
                            'res_format': _('File format'),
                            'license_title': _('License'),
                            #                            'tags': _('Tags'),
                            'openness': _('Open-Data-Eigenschaften')
                            })

    def group_facets(self, facets_dict, group_type, package_type):
        return OrderedDict({'organization': _('Organizations'),
                            'res_format': _('File format'),
                            'license_title': _('License'),
                            'groups': _('Category')})

    def organization_facets(self, facets_dict, organization_type, package_type):
        return OrderedDict({'organization': _('Organizations'),
                            'res_format': _('File format'),
                            'license_title': _('License'),
                            'groups': _('Category')})

    # IPackageController

    def after_show(self, context, pkg_dict):
        '''
        corrects missing relationships in pkg dict
        adds the following key-value-pairs to pkg_dict:
        # key: 'is_new', value: True if the dataset has been created within the last month
        '''
        pkg_dict = helpers_tpsh.correct_missing_relationship(
            pkg_dict,
            helpers_tpsh.get_pkg_relationships_from_model(pkg_dict)
        )
        self._update_is_new_in_pkg_dict(pkg_dict)

        return pkg_dict

    def before_view(self, pkg_dict):
        '''
        adds the following key-value-pairs to pkg_dict:
        # key: 'is_new', value: True if the dataset has been created within the last month
        '''
        self._update_is_new_in_pkg_dict(pkg_dict)
        return pkg_dict

    def after_create(self, context, resource):
        if resource.get('package_id'):
            tools.add_attributes_resources(context, resource)

    def after_update(self, context, resource):
        if resource.get('package_id'):
            tools.add_attributes_resources(context, resource)

    @staticmethod
    def _update_is_new_in_pkg_dict(pkg_dict):
        is_new = HelperPgkDict(pkg_dict).is_package_new()
        pkg_dict.update({'is_new': is_new})

    def before_index(self, dict_pkg):
        # make special date fields solr conform
        fields = ["issued", "temporal_start", "temporal_end"]
        for field in fields:
            field = 'extras_' + field
            if field in dict_pkg and dict_pkg[field]:
                d = parse(dict_pkg[field])
                dict_pkg[field] = '{0.year:04d}-{0.month:02d}-{0.day:02d}T00:00:00Z'.format(
                    d)

        self.map_qa_score(dict_pkg)

        return dict_pkg

    # IRoutes

        map.redirect('/dataset/{id}/resource/{resource_id}', '/dataset/{id}')

        with SubMapper(map, controller='ckanext.odsh.controller:OdshFeedController') as m:
            m.connect('/feeds/custom.atom', action='custom')


        # redirect all user routes to custom controller
        # with SubMapper(map, controller='ckanext.odsh.controller:OdshUserController') as m:
        #     m.connect('/user/edit', action='edit')
        #     m.connect(
        #         'user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog')
        #     m.connect('user_delete', '/user/delete/{id}', action='delete')
        #     m.connect('/user/reset/{id:.*}', action='perform_reset')
        #     m.connect('/user/reset', action='request_reset')
        #     m.connect('register', '/user/register', action='register')
        #     m.connect('login', '/user/login', action='login')
        #     m.connect('/user/_logout', action='logout')
        #     m.connect('/user/logged_in', action='logged_in')
        #     m.connect('/user/logged_out', action='logged_out')
        #     m.connect('/user/logged_out_redirect', action='logged_out_page')
        #     m.connect('user_datasets', '/user/{id:(?!(generate_key|activity)).*}', action='read',
        #               ckan_icon='sitemap')

        map.connect(
            'comment_datarequest',
            '/datarequest/new',
            controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI',
            action='new',
            conditions=dict(method=['GET', 'POST']),
            ckan_icon='comment'
        )
        map.connect(
            'comment_datarequest',
            '/datarequest/{id}',
            controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI',
            action='comment',
            conditions=dict(method=['GET', 'POST']),
            ckan_icon='comment'
        )
        return map

    def after_map(self, map):
        return map

    # ITemplateHelpers

    def get_helpers(self):
        # Template helper function names should begin with the name of the
        # extension they belong to, to avoid clashing with functions from
        # other extensions.
        return {
	        'odsh_main_groups': odsh_helpers.odsh_main_groups,
                'odsh_now': odsh_helpers.odsh_now,
                'odsh_group_id_selected': odsh_helpers.odsh_group_id_selected,
                'odsh_get_facet_items_dict': odsh_helpers.odsh_get_facet_items_dict,
                'odsh_openness_score_dataset_html': odsh_helpers.odsh_openness_score_dataset_html,
                'odsh_get_resource_details': odsh_helpers.odsh_get_resource_details,
                'odsh_get_resource_views': odsh_helpers.odsh_get_resource_views,
                'odsh_get_bounding_box': odsh_helpers.odsh_get_bounding_box,
                'odsh_get_spatial_text': odsh_helpers.odsh_get_spatial_text,
                'odsh_render_datetime': odsh_helpers.odsh_render_datetime,
                'odsh_upload_known_formats': odsh_helpers.odsh_upload_known_formats,
                'odsh_encodeurl': odsh_helpers.odsh_encodeurl,
                'odsh_extract_error': odsh_helpers.odsh_extract_error,
                'odsh_extract_error_new': odsh_helpers.odsh_extract_error_new,
                'odsh_extract_value_from_extras': odsh_helpers.odsh_extract_value_from_extras,
                'odsh_create_checksum': odsh_helpers.odsh_create_checksum,
                'presorted_license_options': odsh_helpers.presorted_license_options,
                'odsh_has_more_facets': odsh_helpers.odsh_has_more_facets,
                'odsh_public_url': odsh_helpers.odsh_public_url,
                'odsh_spatial_extends_available': odsh_helpers.spatial_extends_available,
                'odsh_public_resource_url': odsh_helpers.odsh_public_resource_url,
                'odsh_get_version_id': odsh_helpers.odsh_get_version_id,
                'odsh_show_testbanner': odsh_helpers.odsh_show_testbanner,
                'odsh_is_slave': odsh_helpers.odsh_is_slave,
                'tpsh_get_daterange_prettified': helper_pkg_dict.get_daterange_prettified,
                'tpsh_get_language_of_package': helpers_tpsh.get_language_of_package,
                'get_language_icon': helpers_tpsh.get_language_icon,
                'short_name_for_category': odsh_helpers.short_name_for_category,
                'get_spatial_for_selection': helpers_tpsh.get_spatial_for_selection,
                'get_subject_for_selection': helpers_tpsh.get_subject_for_selection,
                'get_language_for_selection': helpers_tpsh.get_language_for_selection,
                'tpsh_get_resource_size': helpers_tpsh.get_resource_size,
                'tpsh_get_address_org':helpers_tpsh.get_address_org,
                'tpsh_get_body_mail':helpers_tpsh.get_body_mail,
                'odsh_load_mdk_sample_dataset': odsh_helpers.odsh_load_mdk_sample_dataset,
                'odsh_load_raw_mdk_sample_dataset': odsh_helpers.odsh_load_raw_mdk_sample_dataset,
         }

    
    # IValidators

    def get_validators(self):
        return validation.get_validators()

    # Add the custom parameters to Solr's facet queries
    # use several daterange queries agains temporal_start and temporal_end field
    # TODO: use field of type date_range in solr index instead
    def before_search(self, search_params):
        return search.before_search(search_params)

    scores = [['0OL'], ['0OL', '1RE'], ['0OL', '1RE', '2OF'], [
        '0OL', '1RE', '2OF', '3URI'], ['0OL', '1RE', '2OF', '3URI', '4LD']]

    def map_qa_score(self, dict_pkg):
        if 'validated_data_dict' in dict_pkg and 'openness_score' in dict_pkg['validated_data_dict']:
            d = json.loads(dict_pkg['validated_data_dict'])
            score = -1
            for r in d['resources']:
                if 'qa' in r:
                    if 'openness_score' in r['qa']:
                        s = int(r['qa']['openness_score'])
                        if s > score:
                            score = s
            if score > 0:
                dict_pkg['openness'] = OdshPlugin.scores[score-1]