"""Utils methods for file and asset manipulation in the context of batch processing."""
from __future__ import annotations

import re
from datetime import datetime as dt

import numpy as np
from anyascii import anyascii
from matplotlib import pyplot as plt
from matplotlib.axes import Axes
from matplotlib.colors import to_rgba


def format_description(description: str) -> str:
    """Format a name to be accepted as a Task description.

    The rule is:
    The description must contain only the following characters: a..z, A..Z,0..9, ".", ",", ":", ";",
    "_" or "-". The description must be at most 100 characters long.

    Args:
        description: The description to format.

    Returns:
        The formatted description.
    """
    replacements = [
        [[" "], "_"],
        [["/"], "-"],
        [["?", "!", "¿", "*"], "."],
        [["(", ")", "[", "]", "{", "}"], ":"],
    ]

    desc = anyascii(description)
    for chars, rep in replacements:
        pattern = "|".join(re.escape(c) for c in chars)
        desc = re.sub(pattern, rep, desc)  # type: ignore

    return desc[:100]


def format_asset_id(description: str) -> str:
    """Format a name to be accepted as an asset Id.

    The rule is:
    Each segment must contain only the following characters: a..z, A..Z, 0..9, "_" or "-".
    Each segment must be at least 1 character long and at most 100 characters long.

    Args:
        description: The description to format.

    Returns:
        The formatted description.
    """
    replacements = [
        [[" "], "_"],
        [["/"], "-"],
        [["?", "!", "¿", "*"], "."],
        [["(", ")", "[", "]", "{", "}", ";", ":", ",", "."], "_"],
    ]

    desc = anyascii(description)
    for chars, rep in replacements:
        pattern = "|".join(re.escape(c) for c in chars)
        desc = re.sub(pattern, rep, desc)  # type: ignore

    return desc


def plot_data(
    type: str,
    data: dict,
    label_name: str,
    colors: list = [],
    ax: Axes | None = None,
    **kwargs,
) -> Axes:
    """Plotting mechanism used in all the plotting functions.

    It binds the matplotlib capabilities with the data aggregated by different xes.
    the shape of the data should as follows:

    .. code-block::

        {
            "label1": {"properties1": value1, "properties2": value2, ...}
            "label2": {"properties1": value1, "properties2": value2, ...},
            ...
        }

    Args:
        type: The type of plot to use. can be any type of plot from the python lib `matplotlib.pyplot`. If the one you need is missing open an issue!
        data: the data to use as inputs of the graph. please follow the fomrmat specified in the documentation.
        label_name: The name of the property that was used to generate the labels
        property_names: The list of names that was used to name the values. They will be used to order the keys of the data dictionary.
        colors: A list of colors to use for the plot. If not provided, the default colors from the matplotlib library will be used.
        ax: The matplotlib axes to use. If not provided, the plot will be send to a new figure.
        kwargs: Additional arguments from the ``pyplot`` chat type selected.
    """
    # define the ax if not provided by the user
    if ax is None:
        _, ax = plt.subplots()

    # gather the data from parameters
    labels = list(data.keys())
    props = list(data[labels[0]].keys())
    colors = colors if colors else plt.get_cmap("tab10").colors

    # draw the chart based on the type
    if type == "plot":
        for i, label in enumerate(labels):
            kwargs["color"] = colors[i]
            name = props[0] if len(props) == 1 else "Properties values"
            values = list(data[label].values())
            ax.plot(props, values, label=label, **kwargs)
            ax.set_ylabel(name)
            ax.set_xlabel(f"Features (labeled by {label_name})")
            grid_axis = "y"

    elif type == "scatter":
        for i, label in enumerate(labels):
            kwargs["color"] = colors[i]
            name = props[0] if len(props) == 1 else "Properties values"
            values = list(data[label].values())
            ax.scatter(props, values, label=label, **kwargs)
            ax.set_ylabel(name)
            ax.set_xlabel(f"Features (labeled by {label_name})")
            grid_axis = "y"

    elif type == "fill_between":
        for i, label in enumerate(labels):
            kwargs["facecolor"] = to_rgba(colors[i], 0.2)
            kwargs["edgecolor"] = to_rgba(colors[i], 1)
            name = props[0] if len(props) == 1 else "Properties values"
            values = list(data[label].values())
            ax.fill_between(props, values, label=label, **kwargs)
            ax.set_ylabel(name)
            ax.set_xlabel(f"Features (labeled by {label_name})")
            grid_axis = "y"

    elif type == "bar":
        x = np.arange(len(props))
        width = 1 / (len(labels) + 0.8)
        margin = width / 10
        kwargs["width"] = width - margin
        ax.set_xticks(x + width * len(labels) / 2, props)
        for i, label in enumerate(labels):
            kwargs["color"] = colors[i]
            values = list(data[label].values())
            ax.bar(x + width * i, values, label=label, **kwargs)
        grid_axis = "y"

    elif type == "barh":
        y = np.arange(len(props))
        height = 1 / (len(labels) + 0.8)
        margin = height / 10
        kwargs["height"] = height - margin
        ax.set_yticks(y + height * len(labels) / 2, props)
        for i, label in enumerate(labels):
            kwargs["color"] = colors[i]
            values = list(data[label].values())
            ax.barh(y + height * i, values, label=label, **kwargs)
        grid_axis = "x"

    elif type == "stacked":
        x = np.arange(len(props))
        bottom = np.zeros(len(props))
        ax.set_xticks(x, props)
        for i, label in enumerate(labels):
            kwargs.update(color=colors[i], bottom=bottom)
            values = list(data[label].values())
            ax.bar(x, values, label=label, **kwargs)
            bottom += values
        grid_axis = "y"

    elif type == "pie":
        if len(labels) != 1:
            raise ValueError("Pie chart can only be used with one property")
        kwargs["autopct"] = kwargs.get("autopct", "%1.1f%%")
        kwargs["normalize"] = kwargs.get("normalize", True)
        kwargs["labeldistance"] = kwargs.get("labeldistance", None)
        kwargs["wedgeprops"] = kwargs.get("wedgeprops", {"edgecolor": "w"})
        kwargs["textprops"] = kwargs.get("textprops", {"color": "w"})
        kwargs.update(autopct="%1.1f%%", colors=colors)
        values = [data[labels[0]][p] for p in props]
        ax.pie(values, labels=props, **kwargs)
        grid_axis = "y"

    elif type == "donut":
        if len(labels) != 1:
            raise ValueError("Pie chart can only be used with one property")
        kwargs["autopct"] = kwargs.get("autopct", "%1.1f%%")
        kwargs["normalize"] = kwargs.get("normalize", True)
        kwargs["labeldistance"] = kwargs.get("labeldistance", None)
        kwargs["wedgeprops"] = kwargs.get("wedgeprops", {"width": 0.6, "edgecolor": "w"})
        kwargs["textprops"] = kwargs.get("textprops", {"color": "w"})
        kwargs["pctdistance"] = kwargs.get("pctdistance", 0.7)
        kwargs.update(autopct="%1.1f%%", colors=colors)
        values = [data[labels[0]][p] for p in props]
        ax.pie(values, labels=props, **kwargs)
        grid_axis = "y"

    elif type == "date":
        for i, label in enumerate(labels):
            kwargs["color"] = colors[i]
            x, y = list(data[label].keys()), list(data[label].values())
            ax.plot(x, y, label=label, **kwargs)
        ax.set_xlabel("Date")
        grid_axis = "both"

    elif type == "doy":
        for i, label in enumerate(labels):
            kwargs["color"] = colors[i]
            x, y = list(data[label].keys()), list(data[label].values())
            ax.plot(x, y, label=label, **kwargs)
            ax.set_xlabel("Day of year")
            grid_axis = "both"
            dates = [dt(2023, i + 1, 1) for i in range(12)]
            idates = [int(d.strftime("%j")) - 1 for d in dates]
            ndates = [d.strftime("%B")[:3] for d in dates]
            ax.set_xticks(idates, ndates)

    else:
        raise ValueError(f"Type {type} is not (yet?) supported")

    # customize the layout of the axis
    ax.grid(axis=grid_axis)
    ax.set_axisbelow(True)
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.legend(bbox_to_anchor=(1.02, 1), loc="upper left")

    # make sure the canvas is only rendered once.
    ax.figure.canvas.draw_idle()

    return ax
