# copyright 2016-2024 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact https://www.logilab.fr -- mailto:contact@logilab.fr
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""cubicweb-celerytask views/forms/actions/components for web ui"""

import logging
import json

from logilab.mtconverter import xml_escape


from cubicweb import tags
from cubicweb.entities.adapters import ITreeAdapter
from cubicweb.predicates import is_instance, match_view, adaptable

try:
    from cubicweb_web.view import EntityView
    from cubicweb_web.views import uicfg, tabs
    from cubicweb_web.views.cwsources import LogTable, LogTableLayout
    from cubicweb_web.views.json import JsonMixIn
except ImportError:  # TODO remove with CubicWeb 3 support
    from cubicweb.web.view import EntityView
    from cubicweb.web.views import uicfg, tabs
    from cubicweb.web.views.cwsources import LogTable, LogTableLayout
    from cubicweb.web.views.json import JsonMixIn

from cubicweb_celerytask import STATES

_ = str


_pvs = uicfg.primaryview_section
_pvs.tag_attribute(("CeleryTask", "task_name"), "hidden")
_pvs.tag_object_of(("CeleryTask", "parent_task", "*"), "hidden")


class CeleryTaskITreeAdapter(ITreeAdapter):
    __select__ = ITreeAdapter.__select__ & is_instance("CeleryTask")
    tree_relation = "parent_task"
    child_role = "subject"
    parent_role = "object"


class CeleryTaskTabbedPrimaryView(tabs.TabbedPrimaryView):
    """Tabbed primary view for CeleryTask"""

    __select__ = tabs.TabbedPrimaryView.__select__ & is_instance("CeleryTask")
    tabs = [
        _("celerytask.task_general_information"),
        _("celerytask.task_logs"),
    ]
    default_tab = "celerytask.task_general_information"


class CeleryTaskPrimaryTab(tabs.PrimaryTab):
    """Main tab for CeleryTask"""

    __regid__ = "celerytask.task_general_information"
    __select__ = EntityView.__select__ & is_instance("CeleryTask")

    def entity_call(self, entity):
        self._cw.add_js(
            ("cubicweb.htmlhelpers.js", "cubicweb.ajax.js", "cubes.celerytask.js")
        )
        self._cw.add_onload("cw.celerytask.autorefreshprimary()")
        if not entity.cw_adapt_to("ICeleryTask").finished:
            entity.view("celerytask.task_progress_bar", w=self.w)
        super().entity_call(entity)

        if entity.reverse_parent_task:
            self.wview(
                "treeview",
                rset=entity.related("parent_task", role="object"),
                subvid="outofcontext",
            )


class CeleryTaskLogs(tabs.TabsMixin, EntityView):
    """View for CeleryTask logs, only displayed if there are some"""

    __regid__ = "celerytask.task_logs"
    __select__ = EntityView.__select__ & is_instance("CeleryTask")
    default_level = "Debug"

    def entity_call(self, entity):
        logs = entity.cw_adapt_to("ICeleryTask").logs
        if logs:
            logs = xml_escape(logs)
            if isinstance(logs, bytes):
                logs = logs.decode("utf-8", errors="ignore")
            self._cw.view(
                "celerytask.task_logs.table",
                pyvalue=log_to_table(logs),
                paginate=True,
                default_level=self.default_level,
                w=self.w,
            )


class CeleryTaskLogTableLayout(LogTableLayout):
    __select__ = match_view("celerytask.task_logs.table")


class CeleryTaskLogTable(LogTable):
    __regid__ = "celerytask.task_logs.table"
    headers = [_("severity"), _("date"), _("time"), _("message")]


SEVERITY_LVL = {
    "DEBUG": logging.DEBUG,
    "INFO": logging.INFO,
    "WARNING": logging.WARNING,
    "ERROR": logging.ERROR,
    "FATAL": logging.FATAL,
    "CRITICAL": logging.CRITICAL,
}


def log_to_table(logs):
    rows = []
    for line in logs.splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            severity, date, time, info = line.split(None, 3)
            severity = SEVERITY_LVL[severity]
            rows.append([severity, date, time, info])
        except (ValueError, KeyError):
            if rows:
                rows[-1][-1] += "\n" + line
            else:
                rows.append([logging.INFO, "", "", line])
    return rows


class CeleryTaskProgressBarView(EntityView):
    __select__ = EntityView.__select__ & adaptable("ICeleryTask")
    __regid__ = "celerytask.task_progress_bar"

    def entity_call(self, entity, **kwargs):
        adapted = entity.cw_adapt_to("ICeleryTask")
        if not adapted.finished:
            self._cw.add_js(
                ("cubicweb.htmlhelpers.js", "cubicweb.ajax.js", "cubes.celerytask.js")
            )
            self._cw.add_onload(
                f'cw.celerytask.autorefreshprogress({entity.eid}, "{adapted.state}");'
            )
        progratio = adapted.progress
        if isinstance(progratio, (list, tuple)):
            step, total = progratio
            progress = f"{100.0 * step / total:.0f} %"
        else:
            step = progratio
            total = 1.0
            progress = f"{100.0 * float(progratio):.0f} %"
        self.w(
            tags.tag("progress")(
                progress,
                min="0",
                max=str(total),
                value=str(step),
                id=f"js-cw-celerytask-{entity.eid}",
            )
        )


class CeleryTaskJsonView(JsonMixIn, EntityView):
    __select__ = EntityView.__select__ & adaptable("ICeleryTask")
    __regid__ = "celerytask.jsonexport"

    def entity_call(self, entity, **kwargs):
        adapted = entity.cw_adapt_to("ICeleryTask")
        state = adapted.state
        if state == STATES.PENDING:
            progress = None
            total = 1
        elif adapted.finished:
            progress = 1
            total = 1
        else:
            progress = adapted.progress
            if isinstance(progress, (list, tuple)):
                progress, total = progress
            else:
                total = 1
        result = {
            "eid": entity.eid,
            "task_id": adapted.task_id,
            "task_name": adapted.task_name,
            "state": state,
            "progress": progress,
            "total": total,
        }
        self.wdata(result)


class CeleryTaskInContextView(EntityView):
    __regid__ = "incontext"
    __select__ = EntityView.__select__ & adaptable("ICeleryTask")

    def entity_content(self, entity, state):
        return self._cw._(state)

    def entity_call(self, entity):
        adapted = entity.cw_adapt_to("ICeleryTask")
        state, finished = adapted.state, adapted.finished
        html = [
            tags.a(
                self.entity_content(entity, state),
                klass=f"celerytask-{state.lower()}",
                href=entity.absolute_url(),
            )
        ]
        if not finished:
            self._cw.add_js(
                ("cubicweb.htmlhelpers.js", "cubicweb.ajax.js", "cubes.celerytask.js")
            )
            self._cw.add_onload(
                "cw.celerytask.setup({}, {}, '{}');".format(
                    entity.eid, json.dumps(state), self.__regid__
                )
            )
            html.append(entity.view("celerytask.task_progress_bar", w=None))
        self.w(tags.div("\n".join(html), id=f"js-celerytask-{entity.eid}"))


class CeleryTaskOutOfContextView(CeleryTaskInContextView):
    __regid__ = "outofcontext"
    __select__ = EntityView.__select__ & adaptable("ICeleryTask")

    def entity_content(self, entity, state):
        return entity.dc_long_title()
