import datetime
import logging

import numpy as np
import pandas as pd
from casadi import *

# 1.8
from influxdb import DataFrameClient
from influxdb import InfluxDBClient as InfluxDBClient18

# 2.0
# from influxdb_client import InfluxDBClient as InfluxDBClient20
from .utils import dictOfList

influxlogger = logging.getLogger("cosmeinfluxlogger")


class InfluxdbHelper:
    def __init__(
        self,
        host,
        port,
        username,
        password,
        *,
        ssl=False,
        verify_ssl=False,
        use_udp=False,
        udp_port=8089,
        protocol=None,
    ):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.ssl = ssl
        self.verify_ssl = verify_ssl
        self.use_udp = use_udp
        self.udp_port = udp_port
        self.client18 = DataFrameClient(
            self.host,
            self.port,
            self.username,
            password,
            ssl=self.ssl,
            verify_ssl=self.verify_ssl,
        )

        self.client18basic = InfluxDBClient18(
            self.host,
            self.port,
            self.username,
            self.password,
            use_udp=self.use_udp,
            udp_port=self.udp_port,
            ssl=self.ssl,
            verify_ssl=self.verify_ssl,
        )

        # self.client20 = InfluxDBClient20(
        #     url=f"http://{self.host}:{self.port}",
        #     token=f"{self.username}:{self.password}",
        #     org="bmd",
        # )

    def write_data(
        self,
        dict_of_data,
        measurement,
        tags=None,
        time_key=None,
        dbname=None,
        time_base=datetime.datetime.now(datetime.timezone.utc),
        bulk_size=None,
        dask=False,
    ):
        if dict_of_data is not None and dict_of_data != {}:
            self.client18.switch_database(dbname)
            self.client18basic.switch_database(dbname)
            if time_key is None:
                fdict = formatdict(dict_of_data)
                data = {}
                data["measurement"] = measurement
                data["time"] = time_base.isoformat()
                data["fields"] = fdict
                points = [data]
                self.client18basic.write_points(
                    points,
                    time_precision=None,
                    database=dbname,
                    # retention_policy=retention_policy,
                    tags=tags,
                    batch_size=10000,
                    protocol="json",
                )
            else:
                fdict = formattimedict(dict_of_data, time_key=time_key)
                df = pd.DataFrame.from_dict(fdict)
                df = df.dropna()
                list_dict_fields = df.to_dict(orient="records")
                tags["time_key"] = time_key

                points = []
                for i in range(len(list_dict_fields)):
                    data = {}
                    data["measurement"] = measurement
                    Tdelta = datetime.timedelta(
                        microseconds=list_dict_fields[i][time_key] * 1e6
                    )
                    data["time"] = (time_base + Tdelta).isoformat()
                    data["fields"] = list_dict_fields[i]
                    points.append(data)

                if bulk_size is None:
                    bulk_size = int(10000 / len(fdict))
                    if bulk_size < 1:
                        bulk_size = 1

                influxlogger.info(f"Influxdb writes points with bulk_size {bulk_size}")

                chunk_points = [
                    points[i : i + bulk_size] for i in range(0, len(points), bulk_size)
                ]

                if dask and has_dask:
                    with worker_client() as client:
                        client.map(
                            self.client18basic.write_points,
                            chunk_points,
                            pure=False,
                            time_precision=None,
                            database=dbname,
                            tags=tags,
                            protocol="json",
                        )

                else:
                    for subpoints in chunk_points:
                        self.client18basic.write_points(
                            subpoints,
                            time_precision=None,
                            database=dbname,
                            tags=tags,
                            batch_size=25000,
                            protocol="json",
                        )

    def get_dataframe(self, dbname, sentence):
        self.client18.switch_database(dbname)
        dataframe = self.client18.query(sentence)
        return [dataframe[key] for key in dataframe]

    def get_listdict_dataset(self, dbname, sentence):
        list_dataframe = self.get_dataframe(dbname, sentence)
        return [pandas2dict(dataframe) for dataframe in list_dataframe]

    def get_dictlist_dataset(self, dbname, sentence):
        list_dataframe = self.get_dataframe(dbname, sentence)
        list_of_dict = [pandas2dict(dataframe) for dataframe in list_dataframe]
        return dictOfList(list_of_dict)

    def get_csv(self, dbname, sentence, name_path):
        dataframe = self.get_dataframe(dbname, sentence)
        return dataframe.to_csv(name_path)


def rename_field():
    pass


def setFieldOfQuery(query, *fields):
    base = query.split("FROM")
    new_fields = "SELECT "
    for field in fields:
        new_fields += field + ","
    return new_fields[:-1] + " FROM " + base[1]


def shape(x):
    if not isinstance(x, (float, int)):
        dims = list(x.shape)
        if len(dims) == 1:
            dims.append(1)
    else:
        dims = [1, 1]
    return dims


def formatdict(casadiResult):
    scalardict = {}
    for key in casadiResult:
        var = casadiResult[key]
        if shape(var) != [1, 1]:
            nrows = shape(var)[0]
            ncols = shape(var)[1]
            for i in range(nrows):
                for j in range(ncols):
                    if ncols == 1:
                        val = float(var[i])
                    else:
                        val = float(var[i, j])
                    scalardict[key + "[" + str(i) + "," + str(j) + "]"] = val
        else:
            scalardict[key] = float(var)
    return scalardict


def formattimedict(casadiResult, time_key="time"):
    N = shape(casadiResult[time_key])[1]
    if N > 1:
        scalardict = {}
        for key in casadiResult:
            var = casadiResult[key]
            if shape(var) == [1, 1]:
                scalardict[key] = [float(var)] * N
            elif shape(var)[1] >= N and shape(var)[0] > 1:
                nrows = shape(var)[0]
                ncols = shape(var)[1] // N
                if shape(var)[1] % N != 0:
                    raise Exception(key + " wrong object shape")
                for i in range(nrows):
                    for j in range(ncols):
                        scalardict[key + "[" + str(i) + "," + str(j) + "]"] = var[
                            i, j::ncols
                        ].tolist()
            elif shape(var)[1] == N and shape(var)[0] == 1:
                scalardict[key] = var[0, :].tolist()
            elif shape(var)[1] < N and shape(var) != [1, 1]:
                nrows = shape(var)[0]
                ncols = shape(var)[1]
                for i in range(nrows):
                    for j in range(ncols):
                        scalardict[key + "[" + str(i) + "," + str(j) + "]"] = [
                            var[i, j]
                        ] * N
    else:
        scalardict = formatdict(casadiResult)
    return scalardict


def pandas2dict(df):
    keys_mat_vec = list(set([x.split("[")[0] for x in df.keys() if "[" in x]))
    d = {key: [] for key in keys_mat_vec}
    for r in range(df.shape[0]):
        for key in keys_mat_vec:
            if not np.isnan(df[key + "[0,0]"][r]):
                indices = [
                    eval("[" + x[x.find("[") + 1 : x.find("]")] + "]")
                    for x in df.keys()
                    if key + "[" in x
                ]
                n_rows = max([c[0] for c in indices]) + 1
                n_cols = max([c[1] for c in indices]) + 1
                x = DM.zeros(n_rows, n_cols)
                for i in range(n_rows):
                    for j in range(n_cols):
                        x[i, j] = list(df[key + "[" + str(i) + "," + str(j) + "]"])[r]
                d[key].append(x)

    basic_keys = [
        key for key in df.keys() if ("[" not in key and not df[key].dtypes == object)
    ]
    for key in basic_keys:
        d[key] = DM([list(df[key].dropna())]).__array__()
    for key in keys_mat_vec:
        cond = all(
            [
                all(x.__array__().flatten() == d[key][0].__array__().flatten())
                for x in d[key]
            ]
        )
        if cond:
            d[key] = d[key][0].__array__()
        else:
            d[key] = horzcat(*d[key]).__array__()
    return d
