import numpy as np
import pyqtgraph as pg
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QPen

from openlog.core import pint_utilities
from openlog.datamodel.assay.generic_assay import (
    AssayColumn,
    AssayDomainType,
    AssaySeriesType,
    GenericAssay,
)
from openlog.datamodel.assay.uncertainty import UncertaintyType
from openlog.gui.assay_visualization.config.assay_column_visualization_config import (
    AssayColumnVisualizationConfig,
)


def _convert_y_values(assay_column, config, y):
    y_val = []
    if assay_column.series_type == AssaySeriesType.NUMERICAL:
        y_val = np.array([float(val) if val is not None else np.nan for val in y])
    elif assay_column.series_type == AssaySeriesType.DATETIME:
        y_val = np.array([val.timestamp() if val is not None else np.nan for val in y])
    # Convert if needed
    y_val = _unit_conversion(assay_column=assay_column, config=config, val=y_val)
    return y_val


def _convert_x_values(assay, x):
    if assay.assay_definition.domain == AssayDomainType.DEPTH:
        x_val = np.array([float(val) if val is not None else np.nan for val in x])
    else:
        x_val = np.array([val.timestamp() for val in x])
    return x_val


def _unit_conversion(
    assay_column: AssayColumn,
    config: AssayColumnVisualizationConfig,
    val: np.ndarray,
) -> np.ndarray:
    if (
        pint_utilities.is_pint_unit(assay_column.unit)
        and pint_utilities.can_convert(assay_column.unit, config.unit_parameter.value())
        and assay_column.unit != config.unit_parameter.value()
    ):
        return pint_utilities.unit_conversion(
            assay_column.unit, config.unit_parameter.value(), val
        )
    else:
        return val


def convert_discrete_to_extended(x_val: list, y_val: list) -> tuple[list, list, list]:
    x_start = []
    x_end = []

    for i, val in enumerate(x_val):
        if i == 0:
            if len(x_val) > 0:
                half_width = (x_val[i + 1] - val) / 2.0
            else:
                half_width = 1
            x_start.append(val)
            x_end.append(val + half_width)
        elif i == len(x_val) - 1:
            half_width = (val - x_val[i - 1]) / 2.0
            x_start.append(val - half_width)
            x_end.append(val)
        else:
            left_width = (val - x_val[i - 1]) / 2.0
            right_width = (x_val[i + 1] - val) / 2.0
            x_start.append(val - left_width)
            x_end.append(val + right_width)

    return (x_start, x_end, y_val)


def _get_uncertainty_whisker_values(
    assay: GenericAssay,
    assay_column: AssayColumn,
    config: AssayColumnVisualizationConfig,
    y_val: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
    uncertainty = assay_column.uncertainty
    if uncertainty.get_uncertainty_type() == UncertaintyType.ONE_COLUMN:
        (_, unique_uncertainty) = assay.get_all_values(
            uncertainty.upper_whisker_column, related_column=assay_column.name
        )
        unique_uncertainty = _unit_conversion(
            assay_column=assay_column,
            config=config,
            val=unique_uncertainty.astype(float) / 2.0,
        )
        top = unique_uncertainty
        bottom = unique_uncertainty
    elif (
        uncertainty.get_uncertainty_type() == UncertaintyType.TWO_COLUMN
        or uncertainty.get_uncertainty_type() == UncertaintyType.FOUR_COLUMN
    ):
        (_, upper_whisker) = assay.get_all_values(
            uncertainty.upper_whisker_column, related_column=assay_column.name
        )

        upper_whisker = _unit_conversion(
            assay_column=assay_column,
            config=config,
            val=upper_whisker.astype(float),
        )
        (_, lower_whisker) = assay.get_all_values(
            uncertainty.lower_whisker_column, related_column=assay_column.name
        )
        lower_whisker = _unit_conversion(
            assay_column=assay_column,
            config=config,
            val=lower_whisker.astype(float),
        )
        top = upper_whisker - y_val
        bottom = y_val - lower_whisker
    else:
        top = np.ndarray((0, 0))
        bottom = np.ndarray((0, 0))

    # If only NaN values are defined, don't use data
    # pyqtgraph will define invalid item bounds for errorbox
    if np.isnan(top).all() or np.isnan(bottom).all():
        top = []
        bottom = []
    return top, bottom


def _get_uncertainty_box_values(
    assay: GenericAssay,
    assay_column: AssayColumn,
    config: AssayColumnVisualizationConfig,
    y_val: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
    uncertainty = assay_column.uncertainty
    if (
        uncertainty.get_uncertainty_type() == UncertaintyType.TWO_COLUMN
        or uncertainty.get_uncertainty_type() == UncertaintyType.FOUR_COLUMN
    ):
        (_, upper_box) = assay.get_all_values(
            uncertainty.upper_box_column, related_column=assay_column.name
        )

        upper_box = _unit_conversion(
            assay_column=assay_column, config=config, val=upper_box.astype(float)
        )
        (_, lower_box) = assay.get_all_values(
            uncertainty.lower_box_column, related_column=assay_column.name
        )
        lower_box = _unit_conversion(
            assay_column=assay_column, config=config, val=lower_box.astype(float)
        )

        top = upper_box - y_val
        bottom = y_val - lower_box
    else:
        top = np.ndarray((0, 0))
        bottom = np.ndarray((0, 0))

    # If only NaN values are defined, don't use data
    # pyqtgraph will define invalid item bounds for errorbox
    if np.isnan(top).all() or np.isnan(bottom).all():
        top = []
        bottom = []
    return top, bottom


def disable_tick_and_value_display(plot: pg.PlotWidget, axis: str) -> None:
    plot.getAxis(axis).setStyle(tickLength=0)
    pen = QPen()
    pen.setStyle(Qt.NoPen)
    plot.getAxis(axis).setTextPen(pen)
