from datetime import date, datetime
from decimal import Decimal as PyDecimal
from types import NoneType
import numpy as np
import pandas as pd
import datetime as dt
from pandas import DataFrame

#--------------------------------------------------
# Python types
#--------------------------------------------------

def column_to_concept_name(df:DataFrame, col) -> str:
    """ Infer the type of the column in the dataframe and return the corresponding concept name. """
    _type = df[col].dtype
    if pd.api.types.is_datetime64_any_dtype(_type):
        return "DateTime"
    elif pd.api.types.is_object_dtype(_type) and is_date_column(df[col]):
        return "Date"
    elif pd.api.types.is_string_dtype(_type):
        return "String"
    else:
        return pytype_to_concept_name.get(_type, "Any")

def is_date_column(col) -> bool:
    """ Check whether the dataframe column contains date or datetime values. """
    sample = col.dropna()
    if sample.empty:
        return False
    sample_value = sample.iloc[0]
    return isinstance(sample_value, dt.date) and not isinstance(sample_value, dt.datetime)

pytype_to_concept_name: dict[object, str] = {
    NoneType:  "Any",
    int: "Integer", float: "Number(38,14)", str: "String", bool: "Boolean",
    date: "Date", datetime: "DateTime", PyDecimal: "Number(38,14)",
    # Int128Dtype(): "Integer",

    # NumPy dtypes
    **{np.dtype(k): "Integer" for k in ("int8","int16","int32","int64","uint8","uint16","uint32","uint64")},
    **{np.dtype(k): "Number(38,14)" for k in ("float32","float64")},
    np.dtype("bool"): "Boolean",
    np.dtype("object"): "String",
    np.dtype("datetime64[ns]"): "DateTime",
    np.dtype("datetime64[ms]"): "DateTime",
    np.dtype("datetime64[s]"): "DateTime",

    # Pandas extension dtypes
    **{t(): "Integer" for t in (pd.Int8Dtype, pd.Int16Dtype, pd.Int32Dtype, pd.Int64Dtype,
                        pd.UInt8Dtype, pd.UInt16Dtype, pd.UInt32Dtype, pd.UInt64Dtype)},
    **{t(): "Number(38,14)" for t in (pd.Float32Dtype, pd.Float64Dtype)},
    pd.StringDtype(na_value=np.nan): "String",
    pd.StringDtype(): "String",
    pd.BooleanDtype(): "Boolean",
    "int": "Integer", "float": "Number(38,14)", "str": "String", "bool": "Boolean",
    "date": "Date", "datetime": "DateTime", "decimal": "Number(38,14)",
    "datetime.date": "Date", "datetime.datetime": "DateTime"
}
