import warnings
from typing import Any, overload, Literal

import pandas as pd

from antupy.ddd_au import ( DIRECTORY, DEFINITIONS, SIMULATIONS_IO)
DIR_DATA = DIRECTORY.DIR_DATA
FILE_POSTCODES = DIRECTORY.FILE_POSTCODES

TS_WEATHER = SIMULATIONS_IO.TS_TYPES["weather"]
DEFINITION_SEASON = DEFINITIONS.SEASON
LOCATIONS_METEONORM = DEFINITIONS.LOCATIONS_METEONORM
LOCATIONS_STATE = DEFINITIONS.LOCATIONS_STATE
LOCATIONS_COORDINATES = DEFINITIONS.LOCATIONS_COORDINATES

#------------------
class LocationAU():
    def __init__(self, value: str|int|tuple[float,float] = "Sydney",):
        self.value = value
        if isinstance(value, str):
            self.input_type = "city"
        elif isinstance(value, int):
            self.input_type = "postcode"
        elif isinstance(value, tuple):
            self.input_type = "coords"
        else:
            raise TypeError(f"{type(self.value)} is not a valid type for Location")

    def __repr__(self) -> str:
        return f"{self.value}"
    
    def __str__(self) -> str:
        return f"{self.value}"

    @property
    def coords(self) -> tuple[float,float]:
        if isinstance(self.value, str):
            return LOCATIONS_COORDINATES[self.value]
        elif isinstance(self.value, int):
            return _from_postcode(int(self.value), get = "coords")
        elif isinstance(self.value, tuple):
            return self.value
        else:
            raise TypeError(f"{type(self.value)} is not a valid type for Location")

    @property
    def postcode(self) -> int:
        if isinstance(self.value, str):
            coords = self.coords
            return _from_coords(coords, get="postcode")
        elif isinstance(self.value, int):
            return self.value
        elif isinstance(self.value, tuple):
            return _from_coords(self.value, get="postcode")
        else:
            raise TypeError(f"{type(self.value)} is not a valid type for Location")
        
    @property
    def state(self) -> str:
        if isinstance(self.value, str):
            if self.value in LOCATIONS_STATE:
                return LOCATIONS_STATE[self.value]
            else:
                warnings.warn(f"location {self.value} has not a state associated. Check constants.DEFINITIONS.LOCATIONS_STATE. return empty string.")
                return ""
        elif isinstance(self.value, int):
            return _from_postcode(self.value, get = "state")
        elif isinstance(self.value, tuple):
            return _from_coords(self.value, get="state")
        else:
            raise TypeError(f"{type(self.value)} is not a valid type for Location")
        
    @property
    def lon(self) -> float:
        return self.coords[0]
    
    @property
    def lat(self) -> float:
        return self.coords[1]

#---------------
@overload
def _from_postcode(postcode: int, get: Literal["state"]) -> str: ...
@overload
def _from_postcode(postcode: int, get: Literal["coords"]) -> tuple[float, float]: ...

def _from_postcode(
    postcode: int = 2035,
    get: str = "state",
) -> str| tuple[float, float]:
    """It returns the state, coords, lon, or lat from a postcode, using the australian_postcodes.csv set.

    Args:
        postcode (int, optional): _description_. Defaults to 2035.
        get (str, optional): _description_. Defaults to "state".

    Raises:
        ValueError: _description_

    Returns:
        str| tuple[float, float]: _description_
    """
    
    df_raw = pd.read_csv(FILE_POSTCODES)
    cols = ["id","postcode","locality","state","long","lat","dc","type","status","region"]
    if get == "state":
        df = df_raw.groupby("postcode")["state"].agg(pd.Series.mode)
    elif get =="coords":
        df = df_raw.groupby("postcode")[["long","lat"]].mean()
    elif get =="lon":
        df = df_raw.groupby("postcode")[["long"]].mean()
    elif get =="lat":
        df = df_raw.groupby("postcode")[["lat"]].mean()
    else:
        raise ValueError(f"{get} is not a valid value for get.")

    return df[df.index == postcode].values[0]


@overload
def _from_coords(coords: tuple[float, float], get: Literal["postcode"]) -> int: ...
@overload
def _from_coords(coords: tuple[float, float], get: Literal["state"]) -> str: ...

def _from_coords(
        coords: tuple[float, float],
        get : str = "postcode",
) -> int|str:
    """It return the postcode or state closest to coords (a distance function is used). It uses australian_postcodes coordinates.

    Args:
        coords (tuple[float, float]): coordinates: (long, lat)
        get (str, optional): Any column from australian_postcods.csv. Although for now only [postcode,state] are accepted. Defaults to "postcode".

    Returns:
        int|str: the value according to get.
    """
    if get in ["postcode", "state"]:
        (lon, lat) = coords
        df = pd.read_csv(FILE_POSTCODES)
        df["d2"] = (df["long"]-lon)**2 + (df["lat"]-lat)**2
        value = df.loc[df["d2"].idxmin()][get].values[0]
        if get == "postcode":
            return int(value)
        elif get == "state":
            return str(value)
        else:
            raise ValueError(f"{get} is not a valid value for get.")
    else:
        raise ValueError(f"{get} is not a valid value for get.")

#------------------
def main():
    
    location = LocationAU("Sydney")
    print(location.value)
    print(location.coords)
    print(location.state)
    print(location.postcode)
    print()

    location = LocationAU(2035)
    print(location.value)
    print(location.coords)
    print(location.state)
    print(location.postcode)
    print()

    location = LocationAU(DEFINITIONS.LOCATIONS_COORDINATES["Sydney"])
    print(location.value)
    print(location.coords)
    print(location.state)
    print(location.postcode)

    return

if __name__ == "__main__":
    main()
    pass