"""Functions for formatting filters for querying."""
import pandas as pd
import pandera.pandas as pa

from collections.abc import Iterable
from datetime import datetime, timedelta
from typing import Union
import logging
from teehr.models.filters import TableFilter
from pyspark.sql.types import StructType, StringType, IntegerType, FloatType, TimestampType

logger = logging.getLogger(__name__)

SQL_DATETIME_STR_FORMAT = "%Y-%m-%d %H:%M:%S"


def get_datetime_list_string(values):
    """Get a datetime list as a list of strings."""
    return [f"'{v.strftime(SQL_DATETIME_STR_FORMAT)}'" for v in values]


def format_iterable_value(
    values: Iterable[Union[str, int, float, datetime]]
) -> str:
    """Return an SQL formatted string from list of values.

    Parameters
    ----------
    values : Iterable
        Contains values to be formatted as a string for SQL. Only one type of
        value (str, int, float, datetime) should be used. First value in list
        is used to determine value type. Values are not checked for type
        consistency.

    Returns
    -------
    str
        An SQL formatted string from list of values.
    """
    # string
    if isinstance(values[0], str):
        return f"""({",".join([f"'{v}'" for v in values])})"""
    # int or float
    elif isinstance(values[0], int) or isinstance(values[0], float):
        return f"""({",".join([f"{v}" for v in values])})"""
    # datetime
    elif isinstance(values[0], datetime):
        return f"""({",".join(get_datetime_list_string(values))})"""
    else:
        logger.warning(
            "Treating value as string because didn't know what else to do."
        )
        return f"""({",".join([f"'{str(v)}'" for v in values])})"""


def format_timedelta_value(value: timedelta) -> str:
    """Return a PySpark formatted string for timedelta value.

    Parameters
    ----------
    value : timedelta
        A timedelta object.

    Returns
    -------
    str
        A PySpark formatted filter string for timedelta value.
    """
    total_seconds = int(value.total_seconds())
    hours, remainder = divmod(total_seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    days = value.days
    return f"INTERVAL '{days} {hours:02d}:{minutes:02d}:{seconds:02d}' DAY TO SECOND"  # noqa


def format_filter(
    filter: TableFilter,
) -> str:
    r"""Return an SQL formatted string for single filter object.

    Parameters
    ----------
    filter : str or TableFilter
        A single TableFilter object or a subclass
        of TableFilter.

    Returns
    -------
    str
        An SQL formatted string for single filter object.
    """
    column = filter.column
    operator = filter.operator.value

    if isinstance(filter.value, str):
        return f"""{column} {operator} '{filter.value}'"""
    elif (
        isinstance(filter.value, int)
        or isinstance(filter.value, float)
    ):
        return f"""{column} {operator} {filter.value}"""
    elif isinstance(filter.value, datetime):
        dt_str = filter.value.strftime(SQL_DATETIME_STR_FORMAT)
        return f"""{column} {operator} '{dt_str}'"""
    elif (
        isinstance(filter.value, Iterable)
        and not isinstance(filter.value, str)
    ):
        value = format_iterable_value(filter.value)
        return f"""{column} {operator} {value}"""
    elif isinstance(filter.value, timedelta):
        value = format_timedelta_value(filter.value)
        # seconds = filter.value.total_seconds()
        return f"""{column} {operator} {value}"""
    else:
        logger.warning(
            f"Treating value {filter.value} as string because "
            "didn't know what else to do."
        )
        return f"""{column} {operator} '{str(filter.value)}'"""


def validate_filter(
    filter: TableFilter,
    dataframe_schema: StructType
):
    """Validate a single model."""
    model_field_data_type = dataframe_schema[filter.column].dataType
    logging.debug(
        f"Model field {filter.column} has type: {model_field_data_type}"
    )

    # if string or not iterable, make it iterable
    vals = filter.value
    value_was_iterable = True
    if isinstance(vals, str) or not isinstance(vals, Iterable):
        vals = [vals]
        value_was_iterable = False

    validate_vals = []
    for v in vals:
        logging.debug(f"Validating filter value: {v}")
        if model_field_data_type == StringType():
            validate_vals.append(str(v))
        elif model_field_data_type == IntegerType():
            validate_vals.append(int(v))
        elif (
            model_field_data_type == FloatType() or
            model_field_data_type == "float64" or
            model_field_data_type == "float32"
        ):
            validate_vals.append(float(v))
        elif (
            model_field_data_type == TimestampType() or
            model_field_data_type == "datetime64[ns]" or
            model_field_data_type == "datetime64[ms]"
        ):
            validate_vals.append(pd.Timestamp(v))
        else:
            logger.warning(
                "Treating value as string because "
                "didn't know what else to do."
            )
            validate_vals.append(str(v))

    if value_was_iterable:
        filter.value = validate_vals
    else:
        filter.value = validate_vals[0]

    return filter
