from typing import Iterable, Dict, List, Union

import pandas as pd

from onboard.client.models import PointData


def points_df_from_timeseries(timeseries, points=[]) -> pd.DataFrame:
    """Returns a pandas dataframe from the results of a timeseries query"""

    # 'type' is from the point_type.display_name column
    point_names = {str(p['id']): p.get('type') for p in points}

    columns = ['timestamp']
    dates = set()
    data_by_point = {}

    for point in timeseries:
        point_id = point['tags']['point_id']
        columns.append(point_id)
        col_indexes = point['columns']

        ts_index = col_indexes.index('time')
        clean_index = col_indexes.index('clean')

        point_data: Dict[str, float] = {}
        data_by_point[point_id] = point_data

        for val in point['values']:
            ts = val[ts_index]
            dates.add(ts)
            clean = val[clean_index]
            point_data[ts] = clean

    sorted_dates = list(dates)
    sorted_dates.sort()
    data = []

    for d in sorted_dates:
        row = {'timestamp': d}
        for p in columns[1:]:
            val = data_by_point[p].get(d)
            point_name = point_names.get(p)
            point_col = f"{point_name} - {p}" if point_name else p
            row[point_col] = val
        data.append(row)

    df = pd.DataFrame(data)
    return df


def points_df_from_streaming_timeseries(timeseries: Iterable[PointData],
                                        points=[],
                                        point_column_label=None,
                                        ) -> pd.DataFrame:
    """Returns a pandas dataframe from the results of a timeseries query"""
    if point_column_label is None:
        def point_column_label(p):
            return p.get('id')

    point_names = {p['id']: point_column_label(p) for p in points}
    columns: List[Union[str, int]] = ['timestamp']
    dates = set()
    data_by_point = {}

    for point in timeseries:
        columns.append(point.point_id)
        ts_index = point.columns.index('time')
        data_index = point.columns.index(point.unit)

        point_data: Dict[str, Union[str, float, None]] = {}
        data_by_point[point.point_id] = point_data

        for val in point.values:
            ts: str = val[ts_index]  # type: ignore[assignment]
            dates.add(ts)
            clean = val[data_index]
            point_data[ts] = clean

    sorted_dates = list(dates)
    sorted_dates.sort()
    data = []

    for d in sorted_dates:
        row = {'timestamp': d}
        for p in columns[1:]:
            val = data_by_point[p].get(d)  # type: ignore
            point_col = point_names.get(p, p)
            row[point_col] = val  # type: ignore
        data.append(row)

    df = pd.DataFrame(data)
    return df


def df_time_index(df: pd.DataFrame,
                  time_col='timestamp', utc=True) -> pd.DataFrame:
    dt_series = pd.to_datetime(df[time_col], infer_datetime_format=True)
    datetime_index = pd.DatetimeIndex(dt_series.values)
    if utc:
        datetime_index = datetime_index.tz_localize('UTC')
    df_indexed = df.set_index(datetime_index)
    df_indexed.drop(time_col, axis=1, inplace=True)
    return df_indexed


def df_objs_to_numeric(df: pd.DataFrame):
    cols = df.columns[df.dtypes.eq('object')]
    return df[cols].apply(pd.to_numeric, errors='coerce')
