"""Contains internal utils for dealing with the client"""
import argparse
import inspect
import json
import sys
from collections import Counter
from enum import Enum
from functools import wraps
from typing import Any, Callable

import flexpolyline
import pandas as pd
from shapely import Point

from casageo.client.types import Response
from casageo.types import CasaGeoError


def load_json(response: Response[Any]) -> dict:
    data = json.loads(response.content)
    if response.status_code != 200:
        raise CasaGeoError(f"Bad status code {response.status_code}: {data}")
    return data


def load_flexpolyline(inp: str) -> list[tuple[float, float]]:
    """
    Convert a flexpolyline of lat-long coordinates to a list of (lng, lat) tuples.

    We convert from geographic coordinates (lat, lng) to lng, lat
    because geopandas likes it that way, see
    https://geopandas.org/en/stable/gallery/create_geopandas_from_pandas.html.
    """
    inp = flexpolyline.decode(inp)
    return [(float(lng), float(lat)) for (lat, lng) in inp]


def point_to_string(p: Point) -> str:
    """
    Return a lat-lng string representation of a lng-lat Point object.

    The API expects coordinates in the geographic format.
    """
    return f"{p.y},{p.x}"


def string_to_point(data: str) -> Point:
    try:
        lng, lat = (float(val) for val in data.split(","))
    except Exception:
        raise CasaGeoError(
            f"Bad coordinate string {data!r} a good one looks like this 0.0,0.0"
        )
    return Point(lng, lat)

def implicit_enum_parameters(func):
    """
    Decorator that allows parameters of enum type to accept values of
    their underlying type directly.
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        signature = inspect.signature(func)
        bound_args = signature.bind(*args, **kwargs)
        for k, v in bound_args.arguments.items():
            param = signature.parameters[k]
            if (
                    param != inspect.Parameter.empty
                    and isinstance(param.annotation, type)
                    and issubclass(param.annotation, Enum)
            ):
                bound_args.arguments[k] = param.annotation(v)

        return func(*bound_args.args, **bound_args.kwargs)

    return wrapper


def duplicates(inp: list):
    ctr = Counter(inp)
    return [v for v, c in ctr.items() if c > 1]


def cli_catch_exceptions(main) -> int:
    try:
        main()
    except Exception as e:
        # this is not pretty, but it will look pretty
        print(f"{e.__class__.__name__}: {e}", file=sys.stderr)
        return 1
    return 0


def cli_throwing_main(
        parser: argparse.ArgumentParser,
        main: Callable[[argparse.Namespace], dict | pd.DataFrame]
):
    args = parser.parse_args()
    if not args.key:
        raise CasaGeoError(
            "Please specify a key in the environment variable CASAGEO_KEY or the --key argument"
        )

    f = sys.stdout
    if args.output:
        f = open(args.output, "w", encoding="utf-8")

    result = main(args)

    if type(result) is dict:
        json.dump(result, f, indent=args.indent, ensure_ascii=False)
    elif type(result) is pd.DataFrame:
        result.to_csv(f, sep=";")


def cli_main(
        parser: argparse.ArgumentParser,
        main: Callable[[argparse.Namespace], dict | pd.DataFrame]
):
    exit(cli_catch_exceptions(lambda: cli_throwing_main(parser, main)))
