"""Translate UK Repeater list to Yaesu ADMS format"""

import logging
import json

import pandas as pd
import requests

from .constants import (
    BANDS,
    CTCSS_FREQS,
    UKR_API_BASE,
    UKR_API_COLUMNS,
    UKR_GATEWAY_TYPES,
    UKR_REPEATER_TYPES,
)

logger = logging.getLogger(__package__)


def get_band(band):
    url = UKR_API_BASE + band
    logger.info("Fetching data for '%s' from '%s'", band, url)
    response = requests.get(url)
    if response.status_code != 200:
        raise response.raise_for_status()

    content = json.loads(response.content)

    return pd.DataFrame.from_records(
        content["data"], columns=UKR_API_COLUMNS, index="id"
    )


def filter_ukr_data(analog, fusion, gateway, operational_only):
    """Construct a filtering function based on user-specified policy"""

    # Filter gateways
    types = UKR_REPEATER_TYPES.copy()
    if gateway is True:
        types += UKR_GATEWAY_TYPES

    def is_type(x):
        return x in types

    # Filter modes
    modes = ["A"] if analog is True else []
    modes += ["F"] if fusion is True else []

    # Todo: possibly harden this with a more intelligent regex
    def is_mode(mode_str):
        if mode_str is None:
            return False
        return any([x in mode_str for x in modes])

    if operational_only is True:
        is_operational = lambda x: x == "OPERATIONAL"  # noqa: E731
    else:
        is_operational = lambda x: True  # noqa: E731

    def fn(row):
        return (
            is_type(row["type"])
            & is_mode(row["modeCodes"])
            & is_operational(row["status"])
        )

    return fn


def offset_direction(dfreq):
    """ADMS flags for frequency offset direction

    An explicit offset is computed. For crossband repeaters, the
    offset value exceeds the ADMS maximum allowed offset (99.95MHz),
    so the split mode is simply set to `-/+`.
    """
    if dfreq > 99.95:
        return "-/+"
    elif dfreq > 0:
        return "+RPT"
    elif dfreq < 0:
        return "-RPT"
    else:
        return "OFF"


def translate_modecode(modecode: str) -> str:
    """Translate UKR modeCode strings to ADMS dig/analog column

    Stations with Fusion capability are set to DN.  Otherwise set to FM.
    """
    if "F" in modecode:
        return "DN"

    return "FM"


def translate_tonemode(ctcss: float, default: str = "TONE SQL") -> str:
    """Default squelch mode based on CTCSS value

    Args:
      ctcss: the CTCSS frequency
      default: the default squelch mode to use

    Return:
      the CTCSS mode
    """
    if ctcss == 0:
        return "OFF"

    return default


def correct_ctcss(ctcss: pd.Series) -> pd.Series:
    """Correct CTCSS frequencies

    ADMS requires CTCSS values to be one of a number of
    discrete non-zero values, even if no CTCSS is used. This
    function sets ctcss to 100.0 if UKR has 0, else to the
    nearest ADMS CTCSS value.  A warning is emitted if the
    CTCSS value is changed.
    """

    def correct_fn(freq):
        if freq == 0:
            return 100.0
        if freq in CTCSS_FREQS:
            return freq
        return min(CTCSS_FREQS, key=lambda x: abs(x - freq))

    corrected = ctcss.apply(correct_fn)

    for k in ctcss.index[ctcss != corrected]:
        logger.warn(
            "CTCSS value %.1f changed to %.1f for ID=%i",
            ctcss[k],
            corrected[k],
            k,
        )

    return corrected.apply(lambda x: "{:.1f} Hz".format(x))


def translate_narrow(txbw):
    """Simple function to detect narrow bandwidth"""
    if txbw < 25.0:
        return "ON"

    return "OFF"


def format_to_adms(df, ams_mode):
    """Convert known df format to ADMS columns"""

    # Offset before scaling frequency
    freqdiff = (df["rx"] - df["tx"]) / 1e6

    # Convert rx and tx freqs to MHz
    newdf = df[["tx", "rx"]] / 1e6

    # Compute offset
    newdf["offset frequency"] = freqdiff.abs().clip(upper=99.95)
    newdf["offset direction"] = freqdiff.apply(offset_direction)

    # Operating modes
    newdf["operating mode"] = "FM"
    if ams_mode is True:
        newdf["dig/analog"] = "AMS"
    else:
        newdf["dig/analog"] = df["modeCodes"].apply(translate_modecode)

    # Name
    newdf["name"] = df["repeater"]

    # Tone mode
    newdf["tone mode"] = df["ctcss"].apply(translate_tonemode)
    newdf["ctcss frequency"] = df["ctcss"]

    newdf["dcs code"] = "023"
    newdf["user ctcss"] = "1500 Hz"

    # Fusion RX/TX
    newdf["rx dg-id"] = "RX 00"
    newdf["tx dg-id"] = "TX 00"

    # Power + Scan
    newdf["tx power"] = "MID"
    newdf["scan"] = "YES"

    # Step
    newdf["step"] = df["txbw"].astype(str) + "KHz"
    newdf["narrow"] = df["txbw"].apply(translate_narrow)

    # Clock shift
    newdf["clock shift"] = "OFF"

    # Sort by callsign
    newdf = newdf.sort_values("name")

    # Pad the remaining data frame with empty fields, as memories go up to 999
    # We also need a blank column, and a column of 0s for some unexplained reason.
    newdf = newdf.reset_index(drop=True)
    newdf.index = newdf.index + 1
    newdf = newdf.reindex(range(1, 1000))
    newdf["blank"] = ""
    newdf["blank1"] = 0

    return newdf


def ukr2adms(
    band: str | list[str],
    analog: bool = True,
    c4fm: bool = True,
    gateway: bool = True,
    operational_only: bool = False,
    ams_mode: bool = False,
) -> pd.DataFrame:
    """Provide a Yaesu ADMS-compatible CSV of repeater information

    Args:
      band: the required frequency bands (combinations of `["10m", "6m", "4m",
            "2m", "70cm", "23cm"]`)
      analog: include repeaters with analog capability
      c4fm: include repeaters with Yaesu C4FM capability
      gateway: include gateways as well as repeaters
      operational_only: include only OPERATIONAL stations
      ams_mode: if `True`, then AMS will be set as the transmission mode.  If
                False, FM will be set for non-Fusion capable repeaters, and
                DN will be set for Fusion capable repeaters.
    Return:
      A Pandas DataFrame containing ADMS-compatible repeater list
    """
    if isinstance(band, str):
        band = [band]
    assert all([x in BANDS for x in band]), f"Requested bands must be in '{BANDS}'"

    df = pd.concat([get_band(x) for x in band], axis="index")
    capability_filter = filter_ukr_data(analog, c4fm, gateway, operational_only)
    df = df[df.apply(capability_filter, axis=1)]

    # Correct incompatible ctcss values
    df["ctcss"] = correct_ctcss(df["ctcss"])

    df = format_to_adms(df, ams_mode)

    return df
