# -*- coding: utf-8; -*-
#
# This file is part of Superdesk.
#
# Copyright 2013, 2014 Sourcefabric z.u. and contributors.
#
# For the full copyright and license information, please see the
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license

from flask import request
from eve.utils import config
from eve.methods.common import resolve_document_etag
import logging
import superdesk
from superdesk.resource import Resource
from superdesk.services import BaseService
from superdesk.utc import utcnow
from superdesk import get_backend
from superdesk import get_resource_service
from superdesk.workflow import get_privileged_actions
from superdesk.validation import ValidationError
from flask_babel import _


_preferences_key = "preferences"
_user_preferences_key = "user_preferences"
_session_preferences_key = "session_preferences"
_privileges_key = "active_privileges"
_action_key = "allowed_actions"
logger = logging.getLogger(__name__)


def init_app(app):
    endpoint_name = "preferences"
    service = PreferencesService(endpoint_name, backend=get_backend())
    PreferencesResource(endpoint_name, app=app, service=service)
    app.on_session_end -= service.on_session_end
    app.on_session_end += service.on_session_end
    app.on_role_privileges_updated -= service.on_role_privileges_updated
    app.on_role_privileges_updated += service.on_role_privileges_updated
    superdesk.intrinsic_privilege(resource_name=endpoint_name, method=["PATCH"])


def enhance_document_with_default_prefs(doc):
    user_prefs = doc.get(_user_preferences_key, {})
    available = dict(superdesk.default_user_preferences)
    available.update(user_prefs)

    def sync_field(field, dest, default):
        if not isinstance(dest, dict) or not isinstance(default, dict):
            return
        if default.get(field):
            dest[field] = default[field]
        elif dest.get(field):
            dest.pop(field, None)

    # make sure label and category are up-to-date
    for k, v in available.items():
        default = superdesk.default_user_preferences.get(k)
        if default:
            sync_field("label", v, default)
            sync_field("category", v, default)

        if isinstance(v, dict) and v.get("label"):
            v["label"] = _(v["label"])
        if isinstance(v, dict) and v.get("category"):
            v["category_label"] = _(v["category"])

    doc[_user_preferences_key] = available


class PreferencesResource(Resource):
    datasource = {
        "source": "users",
        "projection": {
            _session_preferences_key: 1,
            _user_preferences_key: 1,
            _privileges_key: 1,
            _action_key: 1,
            "_etag": 1,
            "role": 1,
            "user_type": 1,
            "privileges": 1,
        },
    }
    schema = {
        _session_preferences_key: {"type": "dict", "required": True, "allow_unknown": True},
        _user_preferences_key: {"type": "dict", "required": True, "allow_unknown": True},
        _privileges_key: {"type": "dict", "allow_unknown": True},
        _action_key: {"type": "list"},
        # we need these to get user/role info
        "role": {"type": "string"},
        "user_type": {"type": "string"},
        "privileges": {"type": "dict"},
    }
    allow_unknown = True
    resource_methods = []
    item_methods = ["GET", "PATCH"]
    merge_nested_documents = True

    superdesk.register_default_user_preference(
        "feature:preview",
        {
            "type": "bool",
            "enabled": False,
            "default": False,
            "label": _("Enable Feature Preview"),
            "category": _("feature"),
            "privileges": ["feature_preview"],
        },
    )

    superdesk.register_default_user_preference(
        "archive:view",
        {
            "type": "string",
            "allowed": ["mgrid", "compact"],
            "view": "mgrid",
            "default": "mgrid",
            "label": _("Users archive view format"),
            "category": _("archive"),
        },
    )

    superdesk.register_default_user_preference(
        "singleline:view",
        {
            "type": "bool",
            "enabled": None,
            "default": False,
            "label": _("Enable Single Line View"),
            "category": _("rows"),
        },
    )

    superdesk.register_default_user_preference(
        "editor:theme",
        {
            "type": "string",
            "theme": "",
        },
    )

    superdesk.register_default_user_preference("workqueue:items", {"items": []})

    superdesk.register_default_user_preference("dashboard:ingest", {"providers": []})

    superdesk.register_default_user_preference(
        "agg:view",
        {
            "active": {},
        },
    )

    superdesk.register_default_user_preference("templates:recent", {})

    superdesk.register_default_user_preference(
        "dateline:located", {"type": "dict", "label": _("Located"), "category": _("article_defaults")}
    )

    superdesk.register_default_user_preference(
        "categories:preferred",
        {
            "type": "dict",
            "category": _("categories"),
            "label": _("Preferred Categories"),
            "selected": {},
        },
    )

    superdesk.register_default_user_preference(
        "desks:preferred",
        {
            "type": "dict",
            "category": _("desks"),
            "label": _("Preferred Desks"),
            "selected": {},
        },
    )

    superdesk.register_default_user_preference(
        "article:default:place", {"type": "list", "label": _("Place"), "category": _("article_defaults"), "place": []}
    )

    superdesk.register_default_user_preference(
        "spellchecker:status", {"type": "bool", "enabled": True, "default": True}
    )

    superdesk.register_default_user_preference(
        "contacts:view",
        {
            "type": "string",
            "allowed": ["mgrid", "compact"],
            "view": "mgrid",
            "default": "mgrid",
            "label": _("Users contacts view format"),
            "category": _("contacts"),
        },
    )

    superdesk.register_default_user_preference("destination:active", {})

    superdesk.register_default_user_preference("extensions", {})

    superdesk.register_default_user_preference(
        "search:filters_panel_open", {"type": "bool", "enabled": True, "default": True}
    )

    superdesk.register_default_user_preference("masterdesk:desks", {})

    superdesk.register_default_user_preference("editor:char_limit_ui", {})

    superdesk.register_default_session_preference("scratchpad:items", [])
    superdesk.register_default_session_preference("desk:last_worked", "")
    superdesk.register_default_session_preference("desk:items", [])
    superdesk.register_default_session_preference("stage:items", [])
    superdesk.register_default_session_preference("pinned:items", [])


class PreferencesService(BaseService):
    def on_session_end(self, user_id, session_id):
        service = get_resource_service("users")
        user_doc = service.find_one(req=None, _id=user_id)
        session_prefs = user_doc.get(_session_preferences_key, {}).copy()

        if not isinstance(session_id, str):
            session_id = str(session_id)

        if session_id in session_prefs:
            del session_prefs[session_id]
            service.system_update(user_id, {_session_preferences_key: session_prefs}, user_doc)

    def set_session_based_prefs(self, session_id, user_id):
        service = get_resource_service("users")
        user_doc = service.find_one(req=None, _id=user_id)

        session_prefs = user_doc.get(_session_preferences_key, {})
        available = dict(superdesk.default_session_preferences)
        if available.get("desk:last_worked") == "" and user_doc.get("desk"):
            available["desk:last_worked"] = user_doc.get("desk")

        session_prefs.setdefault(str(session_id), available)
        service.system_update(user_id, {_session_preferences_key: session_prefs}, user_doc)

    def set_user_initial_prefs(self, user_doc):
        if _user_preferences_key not in user_doc:
            orig_user_prefs = user_doc.get(_preferences_key, {})
            available = dict(superdesk.default_user_preferences)
            available.update(orig_user_prefs)
            user_doc[_user_preferences_key] = available

    def find_one(self, req, **lookup):
        session = get_resource_service("sessions").find_one(req=None, _id=lookup["_id"])
        _id = session["user"] if session else lookup["_id"]
        doc = get_resource_service("users").find_one(req, _id=_id)
        if doc:
            doc["_id"] = session["_id"] if session else _id
        return doc

    def on_fetched_item(self, doc):
        session_id = request.view_args["_id"]
        session_prefs = doc.get(_session_preferences_key, {}).get(session_id, {})
        doc[_session_preferences_key] = session_prefs
        self.enhance_document_with_user_privileges(doc)
        enhance_document_with_default_prefs(doc)
        self._filter_preferences_by_privileges(doc)

    def on_update(self, updates, original):
        existing_user_preferences = original.get(_user_preferences_key, {}).copy()
        existing_session_preferences = original.get(_session_preferences_key, {}).copy()

        self.update_user_prefs(updates, existing_user_preferences)
        session_id = request.view_args["_id"]
        self.update_session_prefs(updates, existing_session_preferences, session_id)

    def update_session_prefs(self, updates, existing_session_preferences, session_id):
        session_prefs = updates.get(_session_preferences_key)
        if session_prefs is not None:
            for k in (k for k, v in session_prefs.items() if k not in superdesk.default_session_preferences):
                raise ValidationError(_("Invalid preference: {preference}").format(preference=k))

            existing = existing_session_preferences.get(session_id, {})
            existing.update(session_prefs)
            existing_session_preferences[session_id] = existing
            updates[_session_preferences_key] = existing_session_preferences

    def update_user_prefs(self, updates, existing_user_preferences):
        user_prefs = updates.get(_user_preferences_key)
        if user_prefs is not None:
            # check if the input is validated against the default values
            for k in (k for k, v in user_prefs.items() if k not in superdesk.default_user_preferences):
                raise ValidationError(_("Invalid preference: {preference}").format(preference=k))

            existing_user_preferences.update(user_prefs)
            updates[_user_preferences_key] = existing_user_preferences

    def update(self, id, updates, original):
        session = get_resource_service("sessions").find_one(req=None, _id=original["_id"])
        original_unpatched = self.backend.find_one(self.datasource, req=None, _id=session["user"])
        updated = original_unpatched.copy()
        updated.update(updates)
        del updated["_id"]
        res = self.backend.update(self.datasource, original_unpatched["_id"], updated, original_unpatched)
        updates.update(updated)
        # Return only the patched session prefs
        session_prefs = updates.get(_session_preferences_key, {}).get(str(original["_id"]), {})
        updates[_session_preferences_key] = session_prefs
        self.enhance_document_with_user_privileges(updates)
        enhance_document_with_default_prefs(updates)
        return res

    def enhance_document_with_user_privileges(self, user_doc):
        role_doc = get_resource_service("users").get_role(user_doc)
        get_resource_service("users").set_privileges(user_doc, role_doc)
        user_doc[_action_key] = get_privileged_actions(user_doc[_privileges_key])

    def get_user_preference(self, user_id):
        """
        This function returns preferences for the user.
        """
        doc = get_resource_service("users").find_one(req=None, _id=user_id)
        prefs = doc.get(_user_preferences_key, {})
        return prefs

    def email_notification_is_enabled(self, user_id=None, preferences=None):
        """
        This function checks if email notification is enabled or not based on the preferences.
        """
        if user_id:
            preferences = self.get_user_preference(user_id)
        send_email = preferences.get("email:notification", {}) if isinstance(preferences, dict) else {}
        return send_email and send_email.get("enabled", False)

    def is_authorized(self, **kwargs):
        """
        Returns False if logged-in user is trying to update other user's or session's privileges.

        :param kwargs:
        :return: True if authorized, False otherwise
        """
        if not kwargs.get("_id") or not kwargs.get("user_id"):
            return False

        session = get_resource_service("sessions").find_one(req=None, _id=kwargs.get("_id"))
        if not session:
            return False

        return str(kwargs.get("user_id")) == str(session.get("user"))

    def on_role_privileges_updated(self, role, role_users):
        """Runs when user privilage has been updated.

        Update the session for active user so that preferences can be reloaded.

        :param dict role: role getting updated
        :param list role_users: list of user belonging to the role.
        """
        if not role_users or not role:
            return

        logger.info("On_Role_Privileges_Updated: Updating Users for Role:{}.".format(role.get(config.ID_FIELD)))
        for user in role_users:
            try:
                super().update(user[config.ID_FIELD], {}, user)
            except Exception:
                logger.warn(
                    "On_Role_Privileges_Updated:Failed to update user:{} with role:{}.".format(
                        user.get(config.ID_FIELD), role.get(config.ID_FIELD)
                    ),
                    exc_info=True,
                )

    def _filter_preferences_by_privileges(self, doc):
        privileges = doc[_privileges_key]
        preferences = doc[_user_preferences_key]

        def has_missing_privileges(prefs):
            prefs = prefs if isinstance(prefs, list) else [prefs]
            return [priv for pref in prefs for priv in pref.get("privileges", []) if not privileges.get(priv)]

        doc[_user_preferences_key] = {k: v for k, v in preferences.items() if not has_missing_privileges(v)}
