# -*- coding: utf-8 -*-
#
# Copyright (C) 2019-2025 CERN.
#
# invenio-app-ils 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 App ILS circulation stats views."""

from datetime import datetime

from flask import Blueprint, current_app, request
from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_PID_TYPE
from invenio_circulation.proxies import current_circulation
from invenio_pidstore import current_pidstore
from invenio_records_rest.query import default_search_factory
from invenio_records_rest.utils import obj_or_import_string
from invenio_rest import ContentNegotiatedMethodView
from marshmallow.exceptions import ValidationError

from invenio_app_ils.circulation.stats.api import fetch_most_loaned_documents
from invenio_app_ils.circulation.views import IlsCirculationResource
from invenio_app_ils.config import RECORDS_REST_MAX_RESULT_WINDOW
from invenio_app_ils.documents.api import DOCUMENT_PID_FETCHER, DOCUMENT_PID_TYPE
from invenio_app_ils.errors import InvalidParameterError
from invenio_app_ils.permissions import need_permissions
from invenio_app_ils.stats.histogram import (
    HistogramParamsSchema,
    create_histogram_view,
    get_record_statistics,
)


def create_most_loaned_documents_view(blueprint, app):
    """Add url rule for most loaned documents."""
    endpoints = app.config.get("RECORDS_REST_ENDPOINTS", [])
    document_endpoint = endpoints.get(DOCUMENT_PID_TYPE, {})
    default_media_type = document_endpoint.get("default_media_type", "")
    search_serializers_aliases = document_endpoint.get("search_serializers_aliases", "")
    search_serializers = document_endpoint.get("search_serializers")
    serializers = {
        mime: obj_or_import_string(func) for mime, func in search_serializers.items()
    }

    view_class = MostLoanedDocumentsResource

    blueprint.add_url_rule(
        "/circulation/stats/most-loaned",
        view_func=view_class.as_view(
            view_class.view_name,
            serializers=serializers,
            serializers_query_aliases=search_serializers_aliases,
            default_media_type=default_media_type,
        ),
        methods=["GET"],
    )


def create_circulation_stats_blueprint(app):
    """Add statistics views to the blueprint."""
    blueprint = Blueprint("invenio_app_ils_circulation_stats", __name__, url_prefix="")

    create_most_loaned_documents_view(blueprint, app)
    create_histogram_view(
        blueprint,
        app,
        CIRCULATION_LOAN_PID_TYPE,
        LoanHistogramResource,
        "/circulation/loans",
    )

    return blueprint


class MostLoanedDocumentsResource(ContentNegotiatedMethodView):
    """Statistics view for the documents with the most loans."""

    view_name = "most-loaned"
    default_bucket_size = 30

    def __init__(self, *args, **kwargs):
        """Constructor."""
        super().__init__(*args, **kwargs)
        endpoints = current_app.config.get("RECORDS_REST_ENDPOINTS", [])
        document_endpoint = endpoints.get(DOCUMENT_PID_TYPE, {})
        self.max_result_window = document_endpoint.get(
            "max_result_window", RECORDS_REST_MAX_RESULT_WINDOW
        )

    def _validate_bucket_size(self):
        """Validate bucket size parameter."""
        size_param = request.args.get("size", self.default_bucket_size)
        try:
            value = int(size_param)
        except ValueError:
            msg = "Parameter `size` is not a number: {}".format(size_param)
            raise InvalidParameterError(description=msg)

        if value > self.max_result_window:
            msg = "Parameter `size` should be lower than {}".format(
                self.max_result_window
            )
            raise InvalidParameterError(description=msg)
        return value

    def _validate_start_date_range(self):
        """Validate start date range parameters."""

        def validate_date(param, date):
            """Validate a date."""
            try:
                return datetime.strptime(date, "%Y-%m-%d")
            except ValueError:
                msg = "Parameter '{}' is invalid: {}".format(param, date)
                raise InvalidParameterError(description=msg)

        from_date = request.args.get("from_date", None)
        from_date_obj = None
        to_date = request.args.get("to_date", None)
        to_date_obj = None

        if from_date:
            from_date_obj = validate_date("from_date", from_date)
        if to_date:
            to_date_obj = validate_date("to_date", to_date)

        if from_date_obj and to_date_obj and to_date_obj < from_date_obj:
            msg = "Parameter 'to_date' cannot be before 'from_date'."
            raise InvalidParameterError(description=msg)

        return from_date, to_date

    @need_permissions("stats-most-loaned")
    def get(self, *args, **kwargs):
        """Get the most loaned documents and include the extension count.

        Params:
            {
                from_date: start date,
                to_date: end date,
                [size: maximum number of documents to return (buckets)]
            }
        """
        size = self._validate_bucket_size()
        from_date, to_date = self._validate_start_date_range()
        most_loaned_documents = fetch_most_loaned_documents(from_date, to_date, size)
        return self.make_response(
            pid_fetcher=current_pidstore.fetchers[DOCUMENT_PID_FETCHER],
            search_result=most_loaned_documents,
        )


class LoanHistogramResource(IlsCirculationResource):
    """Loan stats resource."""

    view_name = "loan_histogram"

    @need_permissions("stats-loans")
    def get(self, **kwargs):
        """Get loan statistics."""

        loan_cls = current_circulation.loan_record_cls
        loan_date_fields = (
            loan_cls.DATE_FIELDS + loan_cls.DATETIME_FIELDS + ["_created"]
        )

        schema = HistogramParamsSchema(loan_date_fields)
        try:
            parsed_args = schema.load(request.args.to_dict())
        except ValidationError as e:
            raise InvalidParameterError(description=e.messages) from e

        # Construct search to allow for filtering with the q parameter
        search_cls = current_circulation.loan_search_cls
        search = search_cls()
        search, _ = default_search_factory(self, search)

        aggregation_buckets = get_record_statistics(
            loan_date_fields,
            search,
            parsed_args["group_by"],
            parsed_args["metrics"],
        )

        response = {
            "buckets": aggregation_buckets,
        }

        return self.make_response(response, 200)
