# -*- coding: utf-8 -*-
# Copyright (c) 2018-2021, earthobservations developers.
# Distributed under the MIT License. See LICENSE for more info.
import logging
import sys
from typing import List, Optional, Tuple, Union

from wetterdienst import Kind, Provider
from wetterdienst.core.process import create_date_range
from wetterdienst.core.scalar.request import ScalarRequestCore
from wetterdienst.core.scalar.result import StationsResult, ValuesResult
from wetterdienst.metadata.datarange import DataRange
from wetterdienst.metadata.period import PeriodType
from wetterdienst.metadata.resolution import Resolution, ResolutionType
from wetterdienst.provider.dwd.forecast import DwdMosmixType
from wetterdienst.util.enumeration import parse_enumeration_from_template

log = logging.getLogger(__name__)


def unpack_parameters(parameter: str) -> List[str]:
    """Parse parameters to either
    - list of str, each representing a parameter or
    - list of tuple of str representing a pair of parameter and dataset
    e.g.
       "precipitation_height,temperature_air_200" ->
           ["precipitation_height", "temperature_air_200"]

       "precipitation_height/precipitation_more,temperature_air_200/kl" ->
           [("precipitation_height", "precipitation_more"), ("temperature_air_200", "kl")]

    """

    def unpack_parameter(par: str) -> Union[str, Tuple[str, str]]:
        try:
            parameter_, dataset_ = par.split("/")
        except ValueError:
            return par

        return parameter_, dataset_

    # Create list of parameters from string if required
    try:
        parameter = parameter.split(",")
    except AttributeError:
        pass

    parameter = [unpack_parameter(p) for p in parameter]

    return parameter


def get_stations(
    api,
    parameter: List[str],
    resolution: str,
    period: List[str],
    date: Optional[str],
    issue: str,
    all_,
    station_id: List[str],
    name: str,
    coordinates: str,
    rank: int,
    distance: float,
    bbox: str,
    sql: str,
    si_units: bool,
    tidy: bool,
    humanize: bool,
) -> StationsResult:
    """
    Core function for querying stations via cli and restapi

    :param api:
    :param parameter:
    :param resolution:
    :param period:
    :param date:
    :param issue:
    :param all_:
    :param station_id:
    :param name:#
    :param coordinates:
    :param rank:
    :param distance:
    :param bbox:
    :param sql:
    :param date:
    :param si_units:
    :param tidy:
    :param humanize:
    :return:
    """
    # TODO: move this into Request core
    start_date, end_date = None, None
    if date:
        if api.provider == Provider.DWD and api.kind == Kind.FORECAST:
            mosmix_type = DwdMosmixType[resolution.upper()]

            if mosmix_type == DwdMosmixType.SMALL:
                res = Resolution.HOURLY
            else:
                res = Resolution.HOUR_6
        else:
            res = parse_enumeration_from_template(
                resolution, api._resolution_base, Resolution
            )

        # Split date string into start and end date string
        start_date, end_date = create_date_range(date=date, resolution=res)

    if api._data_range == DataRange.LOOSELY and not start_date and not end_date:
        raise TypeError(
            f"Combination of provider {api.provider.name} and kind {api.kind.name} "
            f"requires start and end date"
        )

    # Todo: We may have to apply other measures to allow for
    #  different request initializations
    # DWD Mosmix has fixed resolution and rather uses SMALL
    # and large for the different datasets

    kwargs = {
        "parameter": unpack_parameters(parameter),
        "start_date": start_date,
        "end_date": end_date,
        "si_units": si_units,
        "tidy": tidy,
        "humanize": humanize,
    }

    if api.provider == Provider.DWD and api.kind == Kind.FORECAST:
        kwargs["mosmix_type"] = resolution
        kwargs["start_issue"] = issue
    elif api._resolution_type == ResolutionType.MULTI:
        kwargs["resolution"] = resolution

    if api._period_type == PeriodType.MULTI:
        kwargs["period"] = period

    r = api(**kwargs)

    if all_:
        request = r.all()

    elif station_id:
        request = r.filter_by_station_id(station_id)

    elif name:
        request = r.filter_by_name(name)

    # Use coordinates twice in main if-elif to get same KeyError
    elif coordinates and rank:
        lat, lon = coordinates.split(",")

        request = r.filter_by_rank(
            latitude=float(lat),
            longitude=float(lon),
            rank=rank,
        )

    elif coordinates and distance:
        lat, lon = coordinates.split(",")

        request = r.filter_by_distance(
            latitude=float(lat),
            longitude=float(lon),
            distance=distance,
        )

    elif bbox:
        try:
            left, bottom, right, top = bbox.split(",")
        except ValueError as e:
            raise ValueError("bbox requires four floats separated by comma") from e

        request = r.filter_by_bbox(
            left=float(left),
            bottom=float(bottom),
            right=float(right),
            top=float(top),
        )

    elif sql:
        request = r.filter_by_sql(sql)

    else:
        param_options = [
            "all (boolean)",
            "station (string)",
            "name (string)",
            "coordinates (float,float) and rank (integer)",
            "coordinates (float,float) and distance (float)",
            "bbox (left float, bottom float, right float, top float)",
        ]
        raise KeyError(f"Give one of the parameters: {', '.join(param_options)}")

    return request


def get_values(
    api: ScalarRequestCore,
    parameter: List[str],
    resolution: str,
    date: str,
    issue: str,
    period: List[str],
    all_,
    station_id: List[str],
    name: str,
    coordinates: str,
    rank: int,
    distance: float,
    bbox: str,
    sql: str,
    sql_values: str,
    si_units: bool,
    tidy: bool,
    humanize: bool,
) -> ValuesResult:
    """
    Core function for querying values via cli and restapi

    :param api:
    :param parameter:
    :param resolution:
    :param date:
    :param issue:
    :param period:
    :param all_:
    :param station_id:
    :param name:
    :param coordinates:
    :param rank:
    :param distance:
    :param bbox:
    :param sql:
    :param sql_values:
    :param si_units:
    :param tidy:
    :param humanize:
    :return:
    """
    stations_ = get_stations(
        api=api,
        parameter=parameter,
        resolution=resolution,
        period=period,
        date=date,
        issue=issue,
        all_=all_,
        station_id=station_id,
        name=name,
        coordinates=coordinates,
        rank=rank,
        distance=distance,
        bbox=bbox,
        sql=sql,
        si_units=si_units,
        tidy=tidy,
        humanize=humanize,
    )

    try:
        # TODO: Add stream-based processing here.
        values_ = stations_.values.all()
    except ValueError as e:
        log.exception(e)
        sys.exit(1)
    else:
        if values_.df.empty:
            log.error("No data available for given constraints")
            sys.exit(1)

    if sql_values:
        log.info(f"Filtering with SQL: {sql_values}")

        values_.filter_by_sql(sql_values)

    return values_


def set_logging_level(debug: bool):
    # Setup logging.
    log_level = logging.INFO

    if debug:  # pragma: no cover
        log_level = logging.DEBUG

    log.setLevel(log_level)
