import bson
import bson.errors
import superdesk

from flask import abort, request, json
from superdesk.utc import utcnow
from superdesk.utils import ListCursor
from apps.search_providers.registry import registered_search_providers
from apps.io.search_ingest import SearchIngestService
from superdesk.metadata.item import get_schema
from superdesk.users.services import current_user_has_item_privilege


PROXY_ENDPOINT = "search_providers_proxy"


class SearchProviderProxyResource(superdesk.Resource):
    schema = {
        "guid": {"type": "string", "required": True},
        "desk": superdesk.Resource.rel("desks", False, nullable=True),
        "repo": superdesk.Resource.rel("search_providers", False, nullable=True),
        "stage": superdesk.Resource.rel("stages", False, nullable=True),
        "fetch_endpoint": {"type": "string", "readonly": True},
        "search_provider": {"type": "string", "readonly": True},
        "_fetchable": {"type": "boolean", "readonly": True},
    }
    schema.update(get_schema())

    resource_methods = ["GET", "POST"]
    privileges = {"POST": "archive"}


class SearchProviderProxyService(SearchIngestService):
    """Search Provider Proxy.

    It will forward requests to specific SearchProvider instance.

    Old implementations will get forwarded to its service.
    """

    def get_provider(self, provider_id=None, req=None):
        if not provider_id:
            provider_id = req.args.get("repo") if req else request.args.get("repo")
        if provider_id is None:
            abort(400)
        try:
            bson.ObjectId(provider_id)
        except bson.errors.InvalidId:
            return provider_id
        provider = superdesk.get_resource_service("search_providers").find_one(req=None, _id=provider_id)
        if not provider:
            abort(400)
        if provider.get("is_closed"):
            abort(400)
        if not current_user_has_item_privilege("search_providers", provider):
            abort(403)
        return provider

    def _get_service(self, provider):
        if isinstance(provider, str):
            return provider if "," not in provider and provider else "search"
        provider_data = registered_search_providers[provider["search_provider"]]
        try:
            return provider_data["endpoint"]
        except KeyError:
            return provider_data["class"](provider)

    def get(self, req, lookup):
        """Search using provider."""
        provider = self.get_provider(req=req)
        service = self._get_service(provider)
        if isinstance(service, str):
            return superdesk.get_resource_service(service).get(req, lookup)
        query = self._get_query(req)
        params = json.loads(req.args["params"]) if req.args.get("params") else {}
        try:
            items = service.find(query, params)
        except TypeError:  # BC
            items = service.find(query)
        if isinstance(items, list):
            items = ListCursor(items)
        for item in items:
            self._set_item_defaults(item, provider)
        return items

    def create(self, docs, **kwargs):
        """Archive items from provider."""
        provider = self.get_provider()
        service = self._get_service(provider)
        if isinstance(service, str):
            return superdesk.get_resource_service(service).create(docs, **kwargs)
        return super().create(docs, **kwargs)

    def fetch(self, guid, provider_id=None):
        """Fetch single item from provider to archive it."""
        provider = self.get_provider(provider_id)
        service = self._get_service(provider)
        if isinstance(service, str):
            return superdesk.get_resource_service(service).fetch(guid)
        item = service.fetch(guid)
        self._set_item_defaults(item, provider)
        return item

    def fetch_rendition(self, rendition, item):
        """Fetch binary from provider."""
        provider = self.get_provider(provider_id=item.get("ingest_provider"))
        service = self._get_service(provider)
        if isinstance(service, str):
            return superdesk.get_resource_service(service).fetch_rendition(self, rendition)
        return service.fetch_file(rendition.get("href"), rendition=rendition, item=item)

    def _set_item_defaults(self, item, provider):
        """Add default values to external items."""
        now = utcnow()
        item.setdefault("_id", item.get("guid") or bson.ObjectId())
        item.setdefault("_type", "externalsource")
        item.setdefault("type", "picture")
        item.setdefault("pubstatus", "usable")
        item.setdefault("firstcreated", now)
        item.setdefault("versioncreated", now)
        item.setdefault("fetch_endpoint", PROXY_ENDPOINT)
        item.setdefault("ingest_provider", str(provider["_id"]))
