
import json
import math
import sys
import json
from pandas import read_csv
from pandas import to_datetime

from pydomo.DomoAPIClient import DomoAPIClient
from pydomo.datasets import DataSetClient
from pydomo.streams import StreamClient

class UtilitiesClient(DomoAPIClient):
    def __init__(self, transport, logger):
        super(UtilitiesClient, self).__init__(transport, logger)
        self.ds = DataSetClient(self.transport, self.logger)
        self.stream = StreamClient(self.transport, self.logger)
        self.transport = transport

    def domo_schema(self, ds_id):
        this_get = self.ds.get(ds_id)
        return this_get['schema']['columns']

    def data_schema(self, df):
        col_types = dict(df.dtypes)
        output_list = []
        for key, value in col_types.items():
            output_list.append({'type': self.type_conversion_text(value), 'name': key})
        return output_list

    def type_conversion_text(self, dt):
        """Convert data type strings to standardized type names."""

        dt_str = str(dt)

        if dt_str in {'<M8[ns]', 'datetime64', 'datetime64[ns]', 'datetime64[us]', 'datetime64[ms]', 'datetime64[s]'}:
            return 'DATETIME'
        elif dt_str in {'uint8', 'uint16', 'uint32', 'uint64', 'int8', 'int16', 'int32', 'int64', 'Int8', 'Int16', 'Int32', 'Int64'}:
            return 'LONG'
        elif dt_str in {'float16', 'float32', 'float64', 'Float32', 'Float64'}:
            return 'DOUBLE'
        elif dt_str in {'date', 'datetime.date'}:
            return 'DATE'
        elif dt_str in {'decimal', 'decimal.Decimal'}:
            return 'DECIMAL'
        else:
            return 'STRING'

    def convert_domo_type_to_pandas_type(self, domo_type):
        # type mapping can be adjusted accordingly
        # there is no Date type in Pandas and Timestamps are not a type in Domo
        type_mapping = {
            "LONG": "Int64",
            "STRING": "string",
            "DECIMAL": "Float64",
            "DOUBLE": "Float64",
            "DATETIME": "datetime64[ns]",
            "DATE": "datetime64[ns]",
        }

        return type_mapping.get(domo_type, "object")

    def is_date_type(self, column_type):
        return column_type in ("DATE", "DATETIME")

    def read_content_to_dataframe(self, content):
        df = read_csv(content)

        # Convert to dates or datetimes if possible
        for col in df.columns:
            if df[col].dtype == "object":
                try:
                    df[col] = to_datetime(df[col])
                except ValueError:
                    pass
                except TypeError:
                    pass

        return df

    def identical(self, c1, c2):
        cc1 = json.dumps(c1)
        cc2 = json.dumps(c2)
        return cc1 == cc2

    def get_stream_id(self, ds_id):
        url = '/v1/streams/search?q=dataSource.id:{ds_id}'.format(ds_id=ds_id)
        all_info = self._get(url,'Search to retrieve stream id')
        return all_info[0]['id']

    def estimate_chunk_rows(self, data, kbytes=10000):
        sz = sys.getsizeof(data)
        targetSize = kbytes * 3 # compression factor
        data_rows = len(data.index)
        ch_size = data_rows
        if sz / 1000 > targetSize:
            ch_size = math.floor(data_rows*(targetSize) / (sz/1000))
        return(ch_size)

    def stream_upload(self, ds_id, df_up, warn_schema_change=True):
        domoSchema = self.domo_schema(ds_id)
        dataSchema = self.data_schema(df_up)

        stream_id = self.get_stream_id(ds_id)

        if self.identical(domoSchema,dataSchema) == False:
            new_schema = {'schema': {'columns': dataSchema}}
            url = '/v1/datasets/{ds}'.format(ds=ds_id)
            change_result = self.transport.put(url,new_schema)
            if warn_schema_change:
                print('Schema Updated')

        exec_info = self.stream.create_execution(stream_id)
        exec_id = exec_info['id']

        chunksz = self.estimate_chunk_rows(df_up)
        start = 0
        df_rows = len(df_up.index)
        end = df_rows
        if df_rows > chunksz:
            end = chunksz

        for i in range(math.ceil(df_rows/chunksz)):
            df_sub = df_up.iloc[start:end]
            csv = df_sub.to_csv(header=False,index=False)
            self.stream.upload_part(stream_id, exec_id, i, csv)
            start = end
            end = end + chunksz
            if end > df_rows:
                end = df_rows

        result = self.stream.commit_execution(stream_id, exec_id)

        return result

    def stream_create(self, up_ds, name, description, updateMethod='REPLACE', keyColumnNames=[]):
        df_schema = self.data_schema(up_ds)
        req_body = {'dataSet': {'name': name, 'description': description, 'schema': {'columns': df_schema}}, 'updateMethod': updateMethod}
        if( updateMethod == 'UPSERT' ):
            req_body['keyColumnNames'] = keyColumnNames
        # return req_body
        st_created = self.transport.post('/v1/streams/', req_body, {})
        return json.loads(st_created.content.decode('utf-8'))
