# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016-2021 CERN.
# Copyright (C) 2021-2022 Northwestern University.
# Copyright (C)      2022 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Invenio Communities Service API."""

from elasticsearch_dsl import Q
from elasticsearch_dsl.query import Bool
from invenio_records_resources.services.base import LinksTemplate
from invenio_records_resources.services.records import RecordService, ServiceSchemaWrapper

from invenio_records_resources.services.uow import RecordCommitOp, RecordIndexOp, unit_of_work
from marshmallow.exceptions import ValidationError
from sqlalchemy.orm.exc import NoResultFound


from invenio_communities.communities.records.models import CommunityFeatured
from invenio_communities.communities.services.uow import CommunityFeaturedCommitOp, CommunityFeaturedDeleteOp
from invenio_communities.errors import CommunityFeaturedEntryDoesNotExistError


class CommunityService(RecordService):
    """community Service."""

    def __init__(
            self, config, files_service=None, invitations_service=None,
            members_service=None):
        """Constructor for CommunityService."""
        super().__init__(config)
        self._files = files_service
        self._invitations = invitations_service
        self._members = members_service

    @property
    def files(self):
        """Community files service."""
        return self._files

    @property
    def invitations(self):
        """Community invitations service."""
        return self._invitations

    @property
    def members(self):
        """Community members service."""
        return self._members

    @property
    def schema_featured(self):
        """Returns the featured data schema instance."""
        return ServiceSchemaWrapper(self, schema=self.config.schema_featured)


    def search_user_communities(
            self, identity, params=None, es_preference=None, **kwargs):
        """Search for records matching the querystring."""
        self.require_permission(identity, 'search_user_communities')

        # Prepare and execute the search
        params = params or {}
        search_result = self._search(
            'search',
            identity,
            params,
            es_preference,
            permission_action='read',
            **kwargs).execute()

        return self.result_list(
            self,
            identity,
            search_result,
            params,
            links_tpl=LinksTemplate(self.config.links_user_search, context={
                "args": params
            }),
            links_item_tpl=self.links_item_tpl,
        )

    @unit_of_work()
    def rename(self, identity, id_, data, revision_id=None, raise_errors=True,
               uow=None):
        """Rename a community."""
        record = self.record_cls.pid.resolve(id_)

        self.check_revision_id(record, revision_id)

        # Permissions
        self.require_permission(identity, "rename", record=record)

        if 'id' not in data:
            raise ValidationError(
                'Missing data for required field.',
                field_name='id',
            )

        data, errors = self.schema.load(
            data,
            context={"identity": identity},
            raise_errors=raise_errors,     # if False, flow is continued with
            schema_args={'partial': True}  # data only containing valid data,
                                           # but errors are reported
        )                                  # (as warnings)

        # Run components
        self.run_components(
            'rename', identity, data=data, record=record, uow=uow)

        uow.register(RecordCommitOp(record, indexer=self.indexer))

        return self.result_item(
            self,
            identity,
            record,
            links_tpl=self.links_item_tpl,
        )

    def read_logo(self, identity, id_):
        """Read the community's logo."""
        record = self.record_cls.pid.resolve(id_)
        self.require_permission(identity, 'read', record=record)
        logo_file = record.files.get('logo')
        if logo_file is None:
            raise FileNotFoundError()
        return self.files.file_result_item(
            self.files,
            identity,
            logo_file,
            record,
            links_tpl=self.files.file_links_item_tpl(id_),
        )

    @unit_of_work()
    def update_logo(self, identity, id_, stream, content_length=None,
                    uow=None):
        """Update the community's logo."""
        record = self.record_cls.pid.resolve(id_)
        self.require_permission(identity, 'update', record=record)

        record.files['logo'] = stream
        uow.register(RecordCommitOp(record))

        return self.files.file_result_item(
            self.files,
            identity,
            record.files['logo'],
            record,
            links_tpl=self.files.file_links_item_tpl(id_),
        )

    @unit_of_work()
    def delete_logo(self, identity, id_, uow=None):
        """Delete the community's logo."""
        record = self.record_cls.pid.resolve(id_)
        # update permission on community is required to be able to remove logo.
        self.require_permission(identity, 'update', record=record)
        deleted_file = record.files.pop('logo', None)
        if deleted_file is None:
            raise FileNotFoundError()

        uow.register(RecordCommitOp(record))

        return self.files.file_result_item(
            self.files,
            identity,
            deleted_file,
            record,
            links_tpl=self.files.file_links_item_tpl(id_),
        )


    def _get_featured_entry(self, raise_error=True, **kwargs):
        """Retrieve featured entry based on provided arguments."""
        set = None
        errors = []
        try:
            featured_entry = CommunityFeatured.query.filter_by(**kwargs).one()
        except NoResultFound as e:
            if raise_error:
                raise CommunityFeaturedEntryDoesNotExistError(kwargs)

            errors.append(str(e))
        return featured_entry, errors


    @unit_of_work()
    def featured_search(self, identity, params=None, es_preference=None, **kwargs):
        """Search featured communities."""        
        # Prepare and execute the search
        params = params or {}
        params["sort"] = "featured"

        search_results = self._search(
            'search',
            identity,
            params,
            es_preference,
            extra_filter=Bool("must", must=[
                Q("match", **{"access.visibility": "public"}),
                Q("exists", **{"field": "featured.past"}),
            ]),
            permission_action='featured_search',
            **kwargs).execute()

        return self.result_list(
            self,
            identity,
            search_results,
            links_tpl=self.links_item_tpl,
        )


    def featured_list(self, identity, community_id):
        """List featured entries for a community."""
        record = self.record_cls.pid.resolve(community_id)

        # Permissions
        self.require_permission(identity, "featured_list", record=record)
        
        featured_entries = CommunityFeatured.query.filter(
            CommunityFeatured.community_id == record.id,
        ).paginate(
            per_page=1000,
        )

        return self.config.result_list_cls_featured(
            self,
            identity,
            featured_entries,
            schema=self.schema_featured,
        )


    @unit_of_work()
    def featured_create(self, identity, community_id, data, raise_errors=True,
               uow=None):
        """Create a featured entry for a community."""
        record = self.record_cls.pid.resolve(community_id)

        # Permissions
        self.require_permission(identity, "featured_create", record=record)
        
        data, errors = self.schema_featured.load(
            data,
            context={"identity": identity},
            raise_errors=raise_errors,
        )

        featured_entry = CommunityFeatured(**data)
        featured_entry.community_id = record.id

        # Run components
        self.run_components(
            'featured_create', identity, data=data, record=record, uow=uow)

        uow.register(CommunityFeaturedCommitOp(featured_entry))
        uow.register(RecordIndexOp(record, indexer=self.indexer, index_refresh=True))

        return self.result_item(
            self,
            identity,
            featured_entry,
            schema=self.schema_featured,
        )

    @unit_of_work()
    def featured_update(self, identity, community_id, data, featured_id,
                        raise_errors=True, uow=None):
        """Update a featured entry for a community."""
        record = self.record_cls.pid.resolve(community_id)
        featured_entry, _ = self._get_featured_entry(id=featured_id, community_id=record.id,)

        self.require_permission(identity, "featured_update", record=record)

        valid_data, errors = self.schema_featured.load(
            data,
            context={"identity": identity},
            raise_errors=raise_errors,
        )

        # Run components
        self.run_components(
            'featured_update', identity, data=valid_data, record=record, uow=uow)

        for key, value in valid_data.items():
            setattr(featured_entry, key, value)

        uow.register(CommunityFeaturedCommitOp(featured_entry))
        uow.register(RecordIndexOp(record, indexer=self.indexer, index_refresh=True))

        return self.result_item(
            self,
            identity,
            featured_entry,
            schema=self.schema_featured,
        )

    @unit_of_work()
    def featured_delete(self, identity, community_id, featured_id,
                        raise_errors=True, uow=None):
        """Delete a featured entry for a community."""
        record = self.record_cls.pid.resolve(community_id)
        featured_entry, _  = self._get_featured_entry(id=featured_id, community_id=record.id,)

        self.require_permission(identity, "featured_delete", record=record)

        # Run components
        self.run_components(
            'featured_delete', identity, data=None, record=record, uow=uow)

        uow.register(CommunityFeaturedDeleteOp(featured_entry))
        uow.register(RecordIndexOp(record, indexer=self.indexer, index_refresh=True))

        return
