# -*- 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
import json
from eve.utils import ParsedRequest
from eve.versioning import resolve_document_version
from flask import request
from apps.archive.common import CUSTOM_HATEOAS, insert_into_versions, get_user, ITEM_CREATE, BROADCAST_GENRE, is_genre
from apps.packages import PackageService
from superdesk.metadata.packages import GROUPS
from superdesk.resource import Resource, build_custom_hateoas
from superdesk.services import BaseService
from superdesk.metadata.utils import item_url
from superdesk.metadata.item import CONTENT_TYPE, CONTENT_STATE, ITEM_TYPE, ITEM_STATE, PUBLISH_STATES, metadata_schema
from superdesk import get_resource_service, config
from superdesk.errors import SuperdeskApiError
from apps.archive.archive import SOURCE
from apps.publish.content.common import ITEM_CORRECT, ITEM_PUBLISH
from superdesk.utc import utcnow
from flask_babel import _


logger = logging.getLogger(__name__)
# field to be copied from item to broadcast item
FIELDS_TO_COPY = ["urgency", "priority", "anpa_category", "type", "profile", "subject", "dateline", "slugline", "place"]
ARCHIVE_BROADCAST_NAME = "archive_broadcast"


class ArchiveBroadcastResource(Resource):
    endpoint_name = ARCHIVE_BROADCAST_NAME
    resource_title = endpoint_name

    url = "archive/<{0}:item_id>/broadcast".format(item_url)
    schema = metadata_schema.copy()
    schema.update(
        {
            "desk": Resource.rel("desks", embeddable=False, required=False, nullable=True),
            "_id_document": {"type": "string"},
            "_current_version": {"type": "integer"},
            "_links": {"type": "dict"},
        }
    )

    resource_methods = ["POST"]
    item_methods = []
    privileges = {"POST": ARCHIVE_BROADCAST_NAME}


class ArchiveBroadcastService(BaseService):
    packageService = PackageService()

    def create(self, docs):
        service = get_resource_service(SOURCE)
        item_id = request.view_args["item_id"]
        item = service.find_one(req=None, _id=item_id)
        doc = docs[0]

        self._valid_broadcast_item(item)

        desk_id = doc.get("desk")
        desk = None

        if desk_id:
            desk = get_resource_service("desks").find_one(req=None, _id=desk_id)

        doc.pop("desk", None)
        doc["task"] = {}
        if desk:
            doc["task"]["desk"] = desk.get(config.ID_FIELD)
            doc["task"]["stage"] = desk.get("working_stage")

        doc["task"]["user"] = get_user().get("_id")
        genre_list = get_resource_service("vocabularies").find_one(req=None, _id="genre") or {}
        broadcast_genre = [
            {"qcode": genre.get("qcode"), "name": genre.get("name")}
            for genre in genre_list.get("items", [])
            if genre.get("qcode") == BROADCAST_GENRE and genre.get("is_active")
        ]

        if not broadcast_genre:
            raise SuperdeskApiError.badRequestError(
                message=_("Cannot find the {genre} genre.").format(genre=BROADCAST_GENRE)
            )

        doc["broadcast"] = {"status": "", "master_id": item_id, "rewrite_id": item.get("rewritten_by")}

        doc["genre"] = broadcast_genre
        doc["family_id"] = item.get("family_id")

        for key in FIELDS_TO_COPY:
            doc[key] = item.get(key)

        resolve_document_version(document=doc, resource=SOURCE, method="POST")
        service.post(docs)
        insert_into_versions(id_=doc[config.ID_FIELD])
        build_custom_hateoas(CUSTOM_HATEOAS, doc)
        return [doc[config.ID_FIELD]]

    def _valid_broadcast_item(self, item):
        """Validates item for broadcast.

        Broadcast item can only be created for Text or Pre-formatted item.
        Item state needs to be Published or Corrected

        :param dict item: Item from which the broadcast item will be created
        """
        if not item:
            raise SuperdeskApiError.notFoundError(message=_("Cannot find the requested item id."))

        if not item.get(ITEM_TYPE) in [CONTENT_TYPE.TEXT, CONTENT_TYPE.PREFORMATTED]:
            raise SuperdeskApiError.badRequestError(message=_("Invalid content type."))

        if item.get(ITEM_STATE) not in [CONTENT_STATE.CORRECTED, CONTENT_STATE.PUBLISHED]:
            raise SuperdeskApiError.badRequestError(message=_("Invalid content state."))

    def _get_broadcast_items(self, ids, include_archived_repo=False):
        """Returns list of broadcast items.

        Get the broadcast items for the master_id

        :param list ids: list of item ids
        :param include_archived_repo True if archived repo needs to be included in search, default is False
        :return list: list of broadcast items
        """
        query = {
            "query": {
                "bool": {
                    "filter": [
                        {"term": {"genre.name": BROADCAST_GENRE}},
                        {"terms": {"broadcast.master_id": ids}},
                    ],
                }
            }
        }

        req = ParsedRequest()
        repos = "archive,published"
        if include_archived_repo:
            repos = "archive,published,archived"

        req.args = {"source": json.dumps(query), "repo": repos}
        return get_resource_service("search").get(req=req, lookup=None)

    def get_broadcast_items_from_master_story(self, item, include_archived_repo=False):
        """Get the broadcast items from the master story.

        :param dict item: master story item
        :param include_archived_repo True if archived repo needs to be included in search, default is False
        :return list: returns list of broadcast items
        """
        if is_genre(item, BROADCAST_GENRE):
            return []

        ids = [str(item.get(config.ID_FIELD))]
        return list(self._get_broadcast_items(ids, include_archived_repo))

    def on_broadcast_master_updated(self, item_event, item, rewrite_id=None):
        """Runs when master item is updated.

        This event is called when the master story is corrected, published, re-written

        :param str item_event: Item operations
        :param dict item: item on which operation performed.
        :param str rewrite_id: re-written story id.
        """
        status = ""

        if not item or is_genre(item, BROADCAST_GENRE):
            return

        elif item_event == ITEM_CREATE and rewrite_id:
            status = "Master Story Re-written"
        elif item_event == ITEM_PUBLISH:
            status = "Master Story Published"
        elif item_event == ITEM_CORRECT:
            status = "Master Story Corrected"

        broadcast_items = self.get_broadcast_items_from_master_story(item)

        if not broadcast_items:
            return

        processed_ids = set()
        for broadcast_item in broadcast_items:
            try:
                if broadcast_item.get("lock_user"):
                    continue

                updates = {
                    "broadcast": broadcast_item.get("broadcast"),
                }

                if status:
                    updates["broadcast"]["status"] = status

                if not updates["broadcast"]["rewrite_id"] and rewrite_id:
                    updates["broadcast"]["rewrite_id"] = rewrite_id

                if not broadcast_item.get(config.ID_FIELD) in processed_ids:
                    self._update_broadcast_status(broadcast_item, updates)
                    # list of ids that are processed.
                    processed_ids.add(broadcast_item.get(config.ID_FIELD))
            except Exception:
                logger.exception(
                    "Failed to update status for the broadcast item {}".format(broadcast_item.get(config.ID_FIELD))
                )

    def _update_broadcast_status(self, item, updates):
        """Update the status of the broadcast item.

        :param dict item: broadcast item to be updated
        :param dict updates: broadcast updates
        """
        # update the published collection as well as archive.
        if item.get(ITEM_STATE) in {
            CONTENT_STATE.PUBLISHED,
            CONTENT_STATE.CORRECTED,
            CONTENT_STATE.KILLED,
            CONTENT_STATE.RECALLED,
        }:
            get_resource_service("published").update_published_items(
                item.get(config.ID_FIELD), "broadcast", updates.get("broadcast")
            )

        archive_item = get_resource_service(SOURCE).find_one(req=None, _id=item.get(config.ID_FIELD))
        get_resource_service(SOURCE).system_update(archive_item.get(config.ID_FIELD), updates, archive_item)

    def remove_rewrite_refs(self, item):
        """Remove the rewrite references from the broadcast item if the re-write is spiked.

        :param dict item: Re-written article of the original story
        """
        if is_genre(item, BROADCAST_GENRE):
            return

        query = {
            "query": {
                "bool": {
                    "filter": [
                        {"term": {"genre.name": BROADCAST_GENRE}},
                        {"term": {"broadcast.rewrite_id": item.get(config.ID_FIELD)}},
                    ]
                }
            }
        }

        req = ParsedRequest()
        req.args = {"source": json.dumps(query)}
        broadcast_items = list(get_resource_service(SOURCE).get(req=req, lookup=None))

        for broadcast_item in broadcast_items:
            try:
                updates = {"broadcast": broadcast_item.get("broadcast", {})}

                updates["broadcast"]["rewrite_id"] = None

                if "Re-written" in updates["broadcast"]["status"]:
                    updates["broadcast"]["status"] = ""

                self._update_broadcast_status(broadcast_item, updates)
            except Exception:
                logger.exception(
                    "Failed to remove rewrite id for the broadcast item {}".format(broadcast_item.get(config.ID_FIELD))
                )

    def reset_broadcast_status(self, updates, original):
        """Reset the broadcast status if the broadcast item is updated.

        :param dict updates: updates to the original document
        :param dict original: original document
        """
        if original.get("broadcast") and original.get("broadcast").get("status", ""):
            broadcast_updates = {
                "broadcast": original.get("broadcast"),
            }

            broadcast_updates["broadcast"]["status"] = ""
            self._update_broadcast_status(original, broadcast_updates)
            updates.update(broadcast_updates)

    def spike_item(self, original):
        """If Original item is re-write then it will remove the reference from the broadcast item.

        :param: dict original: original document
        """
        broadcast_items = [
            item
            for item in self.get_broadcast_items_from_master_story(original)
            if item.get(ITEM_STATE) not in PUBLISH_STATES
        ]
        spike_service = get_resource_service("archive_spike")

        for item in broadcast_items:
            id_ = item.get(config.ID_FIELD)
            try:
                self.packageService.remove_spiked_refs_from_package(id_)
                updates = {ITEM_STATE: CONTENT_STATE.SPIKED}
                resolve_document_version(updates, SOURCE, "PATCH", item)
                spike_service.patch(id_, updates)
                insert_into_versions(id_=id_)
            except Exception:
                logger.exception(message="Failed to spike the related broadcast item {}.".format(id_))

        if original.get("rewrite_of") and original.get(ITEM_STATE) not in PUBLISH_STATES:
            self.remove_rewrite_refs(original)

    def kill_broadcast(self, updates, original, operation):
        """Kill the broadcast items

        :param dict updates: Updates to the item
        :param dict original: original item
        :param str operation: Kill or Takedown operation
        :return:
        """
        broadcast_items = [
            item
            for item in self.get_broadcast_items_from_master_story(original)
            if item.get(ITEM_STATE) in PUBLISH_STATES
        ]

        correct_service = get_resource_service("archive_correct")
        kill_service = get_resource_service("archive_{}".format(operation))

        for item in broadcast_items:
            item_id = item.get(config.ID_FIELD)
            packages = self.packageService.get_packages(item_id)

            processed_packages = set()
            for package in packages:
                if (
                    str(package[config.ID_FIELD]) in processed_packages
                    or package.get(ITEM_STATE) == CONTENT_STATE.RECALLED
                ):
                    continue
                try:
                    if package.get(ITEM_STATE) in {CONTENT_STATE.PUBLISHED, CONTENT_STATE.CORRECTED}:
                        package_updates = {
                            config.LAST_UPDATED: utcnow(),
                            GROUPS: self.packageService.remove_group_ref(package, item_id),
                        }

                        refs = self.packageService.get_residrefs(package_updates)
                        if refs:
                            correct_service.patch(package.get(config.ID_FIELD), package_updates)
                        else:
                            package_updates["body_html"] = updates.get("body_html", "")
                            kill_service.patch(package.get(config.ID_FIELD), package_updates)

                        processed_packages.add(package.get(config.ID_FIELD))
                    else:
                        package_list = self.packageService.remove_refs_in_package(package, item_id, processed_packages)

                        processed_packages = processed_packages.union(set(package_list))
                except Exception:
                    logger.exception(
                        "Failed to remove the broadcast item {} from package {}".format(
                            item_id, package.get(config.ID_FIELD)
                        )
                    )

            kill_service.kill_item(updates, item)
