# -*- 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

import logging
from apps.auth import get_user, get_user_id
from eve.versioning import resolve_document_version
from flask import request, current_app as app
from apps.archive import ArchiveSpikeService
from superdesk import get_resource_service, Service, config
from superdesk.metadata.item import (
    ITEM_STATE,
    EMBARGO,
    CONTENT_STATE,
    CONTENT_TYPE,
    ITEM_TYPE,
    PUBLISH_STATES,
    ASSOCIATIONS,
    GUID_TAG,
    PROCESSED_FROM,
    metadata_schema,
    SCHEDULE_SETTINGS,
)
from superdesk.resource import Resource, build_custom_hateoas
from apps.archive.common import (
    CUSTOM_HATEOAS,
    ITEM_CREATE,
    ARCHIVE,
    BROADCAST_GENRE,
    ITEM_REWRITE,
    ITEM_UNLINK,
    ITEM_LINK,
    insert_into_versions,
)
from superdesk.metadata.utils import item_url, generate_guid
from superdesk.workflow import is_workflow_state_transition_valid
from superdesk.errors import SuperdeskApiError, InvalidStateTransitionError
from superdesk.notification import push_notification
from superdesk.signals import item_rewrite
from apps.archive.archive import update_associations
from superdesk.editor_utils import generate_fields, copy_fields
from flask_babel import _
from superdesk.utc import utcnow

logger = logging.getLogger(__name__)


class ArchiveRewriteResource(Resource):
    endpoint_name = "archive_rewrite"
    resource_title = endpoint_name

    schema = metadata_schema.copy()
    schema.update(
        {
            "desk_id": {"type": "string", "nullable": True},
            "update": {"type": "dict", "nullable": True, "allow_unknown": True},
            "_links": {"type": "dict"},
        }
    )

    datasource = {
        "source": "archive",
        "search_backend": "elastic",
    }

    url = "archive/<{0}:original_id>/rewrite".format(item_url)
    resource_methods = ["POST", "DELETE"]
    privileges = {"POST": "rewrite", "DELETE": "rewrite"}


class ArchiveRewriteService(Service):
    def get(self, req, lookup):
        if lookup.get("original_id"):
            return super().get(req, {"_id": lookup["original_id"]})
        return super().get(req, lookup)

    def create(self, docs, **kwargs):
        doc = docs[0] if len(docs) > 0 else {}
        original_id = request.view_args["original_id"]
        update_document = doc.get("update")

        archive_service = get_resource_service(ARCHIVE)
        original = archive_service.find_one(req=None, _id=original_id)
        self._validate_rewrite(original, update_document)

        rewrite = self._create_rewrite_article(original, existing_item=update_document, desk_id=doc.get("desk_id"))

        # sync editor state
        copy_fields(original, rewrite, ignore_empty=True)

        if update_document:
            # copy editor state from existing item to preserve those
            copy_fields(update_document, rewrite, ignore_empty=True)

        if rewrite.get("fields_meta"):
            generate_fields(rewrite, force=True)

        update_associations(rewrite)

        # signal
        item_rewrite.send(self, item=rewrite, original=original)

        if update_document:
            # process the existing story
            archive_service.patch(update_document[config.ID_FIELD], rewrite)
            app.on_archive_item_updated(rewrite, update_document, ITEM_LINK)
            rewrite[config.ID_FIELD] = update_document[config.ID_FIELD]
            ids = [update_document[config.ID_FIELD]]
        else:
            # Set the version.
            resolve_document_version(rewrite, ARCHIVE, "POST")
            ids = archive_service.post([rewrite])
            insert_into_versions(doc=rewrite)
            build_custom_hateoas(CUSTOM_HATEOAS, rewrite)

            app.on_archive_item_updated({"rewrite_of": rewrite.get("rewrite_of")}, rewrite, ITEM_LINK)

        self._add_rewritten_flag(original, rewrite)
        get_resource_service("archive_broadcast").on_broadcast_master_updated(
            ITEM_CREATE, item=original, rewrite_id=ids[0]
        )

        doc.clear()
        doc.update(rewrite)
        return ids

    def _validate_rewrite(self, original, update):
        """Validates the article to be rewritten.

        :param original: article to be rewritten
        :param update: article as the rewrite
        :raises: SuperdeskApiError
        """
        if not original:
            raise SuperdeskApiError.notFoundError(message=_("Cannot find the article"))

        embargo = original.get(SCHEDULE_SETTINGS, {}).get("utc_{}".format(EMBARGO)) if original.get(EMBARGO) else None
        if embargo is not None and embargo > utcnow():
            raise SuperdeskApiError.badRequestError(_("Rewrite of an Item having embargo isn't possible"))

        if not original.get("event_id"):
            raise SuperdeskApiError.notFoundError(message=_("Event id does not exist"))

        if original.get("rewritten_by"):
            raise SuperdeskApiError.badRequestError(message=_("Article has been rewritten before !"))

        if (
            not is_workflow_state_transition_valid("rewrite", original[ITEM_STATE])
            and not config.ALLOW_UPDATING_SCHEDULED_ITEMS
        ):
            raise InvalidStateTransitionError()

        if (
            original.get("rewrite_of")
            and not (original.get(ITEM_STATE) in PUBLISH_STATES)
            and not app.config["WORKFLOW_ALLOW_MULTIPLE_UPDATES"]
        ):
            raise SuperdeskApiError.badRequestError(
                message=_("Rewrite is not published. Cannot rewrite the story again.")
            )

        if update:
            # in case of associate as update
            if update.get("rewrite_of"):
                raise SuperdeskApiError.badRequestError(_("Rewrite story has been used as update before !"))

            if update.get(ITEM_STATE) in [
                CONTENT_STATE.PUBLISHED,
                CONTENT_STATE.CORRECTED,
                CONTENT_STATE.KILLED,
                CONTENT_STATE.RECALLED,
                CONTENT_STATE.SCHEDULED,
                CONTENT_STATE.SPIKED,
            ]:
                raise InvalidStateTransitionError()

            if update.get(ITEM_TYPE) not in [CONTENT_TYPE.TEXT, CONTENT_TYPE.PREFORMATTED]:
                raise SuperdeskApiError.badRequestError(_("Rewrite story can only be text or pre-formatted !"))

            if update.get("genre") and any(
                genre.get("value", "").lower() == BROADCAST_GENRE.lower() for genre in update.get("genre")
            ):
                raise SuperdeskApiError.badRequestError(_("Broadcast cannot be a update story !"))

            if original.get("profile") and update.get("profile") and original.get("profile") != update.get("profile"):
                raise SuperdeskApiError.badRequestError(
                    _("Rewrite item content profile does " "not match with Original item.")
                )

    def _create_rewrite_article(self, original, existing_item=None, desk_id=None):
        """Creates a new story and sets the metadata from original.

        :param dict original: original story
        :param dict existing_item: existing story that is being re-written
        :return:new story
        """
        rewrite = dict()

        fields = [
            "family_id",
            "event_id",
            "flags",
            "language",
            ASSOCIATIONS,
            "extra",
            "place",
            "organisation",
            "person",
            "uri",
        ]
        existing_item_preserve_fields = (ASSOCIATIONS, "flags", "extra")

        if app.config.get("COPY_ON_REWRITE_FIELDS"):
            fields.extend(app.config["COPY_ON_REWRITE_FIELDS"])

        if existing_item:
            # for associate an existing file as update merge subjects
            subjects = original.get("subject", [])
            unique_subjects = {subject.get("qcode") for subject in subjects}
            rewrite["subject"] = [
                subject for subject in existing_item.get("subject", []) if subject.get("qcode") not in unique_subjects
            ]
            rewrite["subject"].extend(subjects)
            rewrite["flags"] = original.get("flags") or {}

            # preserve flags
            for key in rewrite.get("flags").keys():
                rewrite["flags"][key] = original["flags"][key] or existing_item.get("flags", {}).get(key, False)

            original_associations = original.get(ASSOCIATIONS) or {}
            existing_associations = existing_item.get(ASSOCIATIONS) or {}
            rewrite[ASSOCIATIONS] = existing_associations

            # if the existing item has association then preserve the association
            for key, assoc in original_associations.items():
                if not existing_associations.get(key):
                    rewrite[ASSOCIATIONS][key] = assoc

            if existing_item.get("extra"):
                rewrite.setdefault("extra", {})
                for key, val in existing_item["extra"].items():
                    if val:
                        rewrite["extra"][key] = val
        else:
            # ingest provider and source to be retained for new item
            fields.extend(["ingest_provider", "source"])
            # system fields needed.
            fields.extend(["profile", "keywords", "target_regions", "target_types", "target_subscribers"])
            content_schema = get_resource_service("content_types").get_schema(original)
            if content_schema:
                extended_fields = list(content_schema.keys())
                fields.extend(extended_fields)

        for field in fields:
            if original.get(field):
                # don't overwrite some fields in existing items
                if existing_item and field in existing_item_preserve_fields:
                    continue

                rewrite[field] = original[field]

        # populate any missing extra fields from original
        if original.get("extra"):
            for key, val in original["extra"].items():
                if val:
                    rewrite.setdefault("extra", {}).setdefault(key, val)

        # if the original was flagged for SMS the rewrite should not be.
        if not existing_item and rewrite.get("flags", {}).get("marked_for_sms", False):
            rewrite["flags"]["marked_for_sms"] = False

        # SD-4595 - Default value for the update article to be set based on the system config.
        if config.RESET_PRIORITY_VALUE_FOR_UPDATE_ARTICLES:
            # if True then reset to the default priority value.
            rewrite["priority"] = int(config.DEFAULT_PRIORITY_VALUE_FOR_MANUAL_ARTICLES)

        rewrite["rewrite_of"] = original[config.ID_FIELD]
        rewrite["rewrite_sequence"] = (original.get("rewrite_sequence") or 0) + 1
        rewrite.pop(PROCESSED_FROM, None)

        if not existing_item:
            from apps.tasks import send_to

            # send the document to the desk only if a new rewrite is created
            send_to(
                doc=rewrite,
                desk_id=(desk_id or original["task"]["desk"]),
                default_stage="working_stage",
                user_id=get_user_id(),
            )

            # if we are rewriting a published item then copy the body_html
            if original.get("state", "") in (CONTENT_STATE.PUBLISHED, CONTENT_STATE.CORRECTED, CONTENT_STATE.SCHEDULED):
                rewrite["body_html"] = original.get("body_html", "")

        rewrite[ITEM_STATE] = CONTENT_STATE.PROGRESS
        self._set_take_key(rewrite)
        return rewrite

    def _add_rewritten_flag(self, original, rewrite):
        """Adds rewritten_by field to the existing published items.

        :param dict original: item on which rewrite is triggered
        :param dict rewrite: rewritten document
        """
        get_resource_service("published").update_published_items(
            original[config.ID_FIELD], "rewritten_by", rewrite[config.ID_FIELD]
        )

        # modify the original item as well.
        get_resource_service(ARCHIVE).system_update(
            original[config.ID_FIELD], {"rewritten_by": rewrite[config.ID_FIELD]}, original
        )
        app.on_archive_item_updated({"rewritten_by": rewrite[config.ID_FIELD]}, original, ITEM_REWRITE)

    def _set_take_key(self, rewrite):
        """Sets the anpa take key of the rewrite with ordinal.

        :param rewrite: rewrite story
        """
        rewrite_sequence = rewrite.get("rewrite_sequence") or 0
        if rewrite_sequence > 1:
            ordinal = self._get_ordinal(rewrite_sequence)
            rewrite["anpa_take_key"] = "{} update".format(ordinal)
        else:
            rewrite["anpa_take_key"] = "update"

    def _get_ordinal(self, n):
        """Returns the ordinal value of the given int."""
        if 10 <= n % 100 < 20:
            return str(n) + "th"
        else:
            return str(n) + {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")

    def delete(self, lookup):
        target_id = request.view_args["original_id"]
        archive_service = get_resource_service(ARCHIVE)
        target = archive_service.find_one(req=None, _id=target_id)
        updates = {}

        if target.get("rewrite_of"):
            # remove the rewrite info
            ArchiveSpikeService().update_rewrite(target)

        if not target.get("rewrite_of"):
            # there is nothing to do
            raise SuperdeskApiError.badRequestError(_("Only updates can be unlinked!"))

        if target.get("rewrite_of"):
            updates["rewrite_of"] = None

        if target.get("anpa_take_key"):
            updates["anpa_take_key"] = None

        if target.get("rewrite_sequence"):
            updates["rewrite_sequence"] = None

        if target.get("sequence"):
            updates["sequence"] = None

        updates["event_id"] = generate_guid(type=GUID_TAG)

        archive_service.system_update(target_id, updates, target)
        user = get_user(required=True)
        push_notification("item:unlink", item=target_id, user=str(user.get(config.ID_FIELD)))
        app.on_archive_item_updated(updates, target, ITEM_UNLINK)
