# (C) Copyright 2005-2025 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

# Standard library imports
from datetime import datetime
import logging

# Enthought library imports.
from pyface.api import ImageResource, clipboard
from pyface.workbench.api import TraitsUIView
from traits.api import (
    Button,
    Instance,
    List,
    Property,
    Str,
    cached_property,
    observe,
)
from traitsui.api import View, Group, Item, CodeEditor, TabularEditor, spring
from traitsui.tabular_adapter import TabularAdapter

# Local imports
from apptools.logger.agent.quality_agent_view import QualityAgentView
from apptools.logger.plugin.logger_service import LoggerService

# Constants
_IMAGE_MAP = {
    logging.DEBUG: ImageResource("debug"),
    logging.INFO: ImageResource("info"),
    logging.WARNING: ImageResource("warning"),
    logging.ERROR: ImageResource("error"),
    logging.CRITICAL: ImageResource("crit_error"),
}


class LogRecordAdapter(TabularAdapter):
    """A TabularEditor adapter for logging.LogRecord objects."""

    columns = [
        ("Level", "level"),
        ("Date", "date"),
        ("Time", "time"),
        ("Message", "message"),
    ]
    column_widths = [80, 100, 120, -1]

    level_image = Property
    level_text = Property(Str)
    date_text = Property(Str)
    time_text = Property(Str)
    message_text = Property(Str)

    def get_width(self, object, trait, column):
        return self.column_widths[column]

    def _get_level_image(self):
        return _IMAGE_MAP[self.item.levelno]

    def _get_level_text(self):
        return self.item.levelname.capitalize()

    def _get_date_text(self):
        dt = datetime.fromtimestamp(self.item.created)
        return dt.date().isoformat()

    def _get_time_text(self):
        dt = datetime.fromtimestamp(self.item.created)
        return dt.time().isoformat()

    def _get_message_text(self):
        # Just display the first line of multiline messages, like stacktraces.
        msg = self.item.getMessage()
        msgs = msg.strip().split("\n")
        if len(msgs) > 1:
            suffix = "... [double click for details]"
        else:
            suffix = ""
        abbrev_msg = msgs[0] + suffix
        return abbrev_msg


class LoggerView(TraitsUIView):
    """The Workbench View showing the list of log items."""

    id = Str("apptools.logger.plugin.view.logger_view.LoggerView")
    name = Str("Logger")
    service = Instance(LoggerService)

    log_records = List(Instance(logging.LogRecord))
    formatted_records = Property(Str, observe="log_records")

    activated = Instance(logging.LogRecord)
    activated_text = Property(Str, observe="activated")
    reset_button = Button("Reset Logs")
    show_button = Button("Complete Text Log")
    copy_button = Button("Copy Log to Clipboard")

    code_editor = CodeEditor(lexer="null", show_line_numbers=False)
    log_records_editor = TabularEditor(
        adapter=LogRecordAdapter(), editable=False, activated="activated"
    )
    trait_view = View(
        Group(
            Item("log_records", editor=log_records_editor),
            Group(
                Item("reset_button"),
                spring,
                Item("show_button"),
                Item("copy_button"),
                orientation="horizontal",
                show_labels=False,
            ),
            show_labels=False,
        )
    )

    ###########################################################################
    # LogQueueHandler view interface
    ###########################################################################

    def update(self, force=False):
        """Update 'log_records' if our handler has new records or 'force' is
        set.
        """
        service = self.service
        if service.handler.has_new_records() or force:
            log_records = [
                rec
                for rec in service.handler.get()
                if rec.levelno >= service.preferences.level_
            ]
            log_records.reverse()
            self.log_records = log_records

    ###########################################################################
    # Private interface
    ###########################################################################

    @observe("service.preferences.level_")
    def _update_log_records(self, event):
        self.service.handler._view = self
        self.update(force=True)

    def _reset_button_fired(self):
        self.service.handler.reset()
        self.log_records = []

    def _show_button_fired(self):
        self.edit_traits(
            view=View(
                Item(
                    "formatted_records",
                    editor=self.code_editor,
                    style="readonly",
                    show_label=False,
                ),
                width=800,
                height=600,
                resizable=True,
                buttons=["OK"],
                title="Complete Text Log",
            )
        )

    def _copy_button_fired(self):
        clipboard.text_data = self.formatted_records

    @cached_property
    def _get_formatted_records(self):
        return "\n".join(
            [
                self.service.handler.formatter.format(record)
                for record in self.log_records
            ]
        )

    def _activated_changed(self):
        if self.activated is None:
            return
        msg = self.activated.getMessage()
        if self.service.preferences.enable_agent:
            dialog = QualityAgentView(msg=msg, service=self.service)
            dialog.open()
        else:
            self.edit_traits(
                view=View(
                    Item(
                        "activated_text",
                        editor=self.code_editor,
                        style="readonly",
                        show_label=False,
                    ),
                    width=800,
                    height=600,
                    resizable=True,
                    buttons=["OK"],
                    title="Log Message Detail",
                )
            )

    @cached_property
    def _get_activated_text(self):
        if self.activated is None:
            return ""
        else:
            return self.activated.getMessage()
