# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import logging
import os
import sys
import warnings

# Dictionary to match:
# key: ADR DataItem type
# value: ADR item_* field
dict_items = {
    "animation": "item_animation",
    "file": "item_file",
    "html": "item_text",
    "image": "item_image",
    "string": "item_text",
    "scene": "item_scene",
    "table": "item_table",
    "tree": "item_tree",
}

# Dictionary to match:
# key: ADR item_* field
# value: ADR DataItem type
# (inverse of dict_items
type_maps = {
    "item_text": "text",
    "item_scene": "scene",
    "item_image": "image",
    "item_table": "table",
    "item_animation": "animation",
    "item_file": "file",
    "item_tree": "tree",
}

# Table attributes. To be generated by the read_prop.py file
table_attr = (
)


def in_ipynb():
    try:
        ipy_str = str(type(get_ipython()))
        if "zmqshell" in ipy_str:
            return True
        if "terminal" in ipy_str:
            return False
    except Exception as e:  # todo: please specify the possible exceptions here.
        return False


def get_logger(
    logfile: str | os.PathLike[str] | None = None,
    *,
    log_output: str | os.PathLike[str] | None = None,
    log_level: int | str | None = None,
) -> logging.Logger:
    """
    Return the ``ansys.dynamicreporting.core`` package logger.

    ADR uses its own logger and does not change the root logger. By default ADR
    adds a ``NullHandler`` and leaves the logging level unchanged. Pass
    ``log_output`` to add a file or stdout handler, and pass ``log_level`` to
    set the shared ADR logger's level explicitly.

    Parameters
    ----------
    logfile : str or os.PathLike, optional
        Deprecated alias for ``log_output``. Passing it emits
        ``DeprecationWarning``.
    log_output : str or os.PathLike, optional
        Where to send ADR logs. ``"stdout"`` writes to standard output. Any
        other value is used as a file path. ``None`` adds no output handler.
    log_level : int or str, optional
        Level for the shared ADR logger. ``None`` leaves its current level
        unchanged.

    Returns
    -------
    logging.Logger
        The ``ansys.dynamicreporting.core`` package logger.

    Raises
    ------
    ValueError
        If both ``logfile`` and ``log_output`` are provided.

    Notes
    -----
    Repeated calls do not add duplicate ADR-owned handlers for the same
    standard-output stream or normalized file path.
    """
    if logfile is not None and log_output is not None:
        raise ValueError("Use only one of 'logfile' or 'log_output'.")
    if logfile is not None:
        warnings.warn(
            "The 'logfile' parameter is deprecated. Use 'log_output' instead.",
            DeprecationWarning,
            stacklevel=3,
        )
        log_output = logfile

    # Use ADR's own logger, not the root logger. Changing the root logger can
    # break the application's logging. ADR adds each handler only once.
    logger = logging.getLogger("ansys.dynamicreporting.core")
    if log_level is not None:
        logger.setLevel(log_level)

    if log_output is None:
        # ADR is quiet by default. A single NullHandler prevents fallback
        # warnings until the caller picks a real output handler.
        if not any(isinstance(handler, logging.NullHandler) for handler in logger.handlers):
            logger.addHandler(logging.NullHandler())
        return logger

    # Add the requested output handler once so repeated calls do not duplicate
    # log lines.
    if log_output == "stdout":
        already_present = any(
            isinstance(handler, logging.StreamHandler)
            and not isinstance(handler, logging.FileHandler)
            and getattr(handler, "stream", None) is sys.stdout
            for handler in logger.handlers
        )
        if already_present:
            return logger
        handler = logging.StreamHandler(sys.stdout)
    else:
        # Normalize the path before comparing file handlers.
        # "out.log" -> "C:\\cwd\\out.log" (Windows) / "/cwd/out.log" (POSIX)
        target_path = os.path.normcase(os.path.abspath(log_output))
        if any(
            isinstance(handler, logging.FileHandler)
            and os.path.normcase(handler.baseFilename) == target_path
            for handler in logger.handlers
        ):
            return logger
        handler = logging.FileHandler(log_output)
    formatter = logging.Formatter("[%(asctime)s] %(levelname)s %(name)s: %(message)s")
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    return logger


def check_filter(item_filter: str = ""):
    """
    Verify validity of the query string for filtering.

    Parameters
    ----------
    item_filter : str, optional
        Query string for filtering. The default is ``""``. The syntax corresponds
        to the syntax for Ansys Dynamic Reporting. For more information, see
        _Query Expressions in the documentation for Ansys Dynamic Reporting.

    Returns
    -------
    bool
        ``True`` if the query string is valid, ``False`` otherwise.
    """
    for query_stanza in item_filter.split(";"):
        if len(query_stanza) > 0:
            if len(query_stanza.split("|")) != 4:
                return False
            if query_stanza.split("|")[0] not in ["A", "O"]:
                return False
            if query_stanza.split("|")[1][0:2] not in ["i_", "s_", "d_", "t_"]:
                return False
    return True


def build_query_url(logger = None, item_filter: str = "") -> str:
    """
    Build the query section of report url.

    Parameters
    ----------
    logger: logging.logger
        The logger object.

    item_filter : str, optional
        Query string for filtering. The default is ``""``. The syntax corresponds
        to the syntax for Ansys Dynamic Reporting. For more information, see
        _Query Expressions in the documentation for Ansys Dynamic Reporting.

    Returns
    -------
    str
        query section of the report url corresponding to the query string.
    """
    valid = check_filter(item_filter)
    if valid is False:
        logger.warning("Warning: item_filter string is not valid. Will be ignored.")
        return ""
    else:
        query_str = "&query={}".format(item_filter.replace("|", "%7C").replace(";", "%3B").replace("&", "%2C"))
        return query_str
