from .common import insert
from .host import HOST


def frr_to_ietf_neighbor_state(state):
    """Fetch OSPF neighbor state from Frr"""
    state = state.split("/")[0]
    if state == "TwoWay":
        return "2-way"
    return state.lower()


def frr_to_ietf_neighbor_role(role):
    """Translate FRR neighbor role to YANG enumeration values"""
    if role == "Backup":
        return "BDR"
    # DR and DROther are already correct
    return role


def add_routes(ospf):
    """Fetch OSPF routes from Frr"""
    cmd = ['vtysh', '-c', "show ip ospf route json"]
    data = HOST.run_json(cmd, default=[])
    if data == []:
        return  # No OSPF routes available

    routes = []
    for prefix, info in data.items():
        if prefix.find("/") == -1:  # Ignore router IDs
            continue

        route = {}
        route["prefix"] = prefix

        nexthops = []
        routetype = info["routeType"].split(" ")

        if len(routetype) > 1:
            if routetype[1] == "E1":
                route["route-type"] = "external-1"
            elif routetype[1] == "E2":
                route["route-type"] = "external-2"
            elif routetype[1] == "IA":
                route["route-type"] = "inter-area"
        elif routetype[0] == "N":
            route["route-type"] = "intra-area"

        # Add area information if available
        # Note: augmented by infix-routing.yang since standard ietf-ospf doesn't include it
        # Must use the augmenting module's prefix
        if info.get("area") is not None:
            route["infix-routing:area-id"] = info["area"]

        # Add metric (cost) if available
        if info.get("cost") is not None:
            route["metric"] = info["cost"]
        elif info.get("metric") is not None:
            route["metric"] = info["metric"]

        # Add route-tag for external routes
        if info.get("tag") is not None:
            route["route-tag"] = info["tag"]

        for hop in info["nexthops"]:
            nexthop = {}
            if hop["ip"] != " ":
                nexthop["next-hop"] = hop["ip"]
            else:
                nexthop["outgoing-interface"] = hop["directlyAttachedTo"]
            nexthops.append(nexthop)

        route["next-hops"] = {}
        route["next-hops"]["next-hop"] = nexthops
        routes.append(route)

    insert(ospf, "ietf-ospf:local-rib", "ietf-ospf:route", routes)


def add_areas(control_protocols):
    """Populate OSPF status"""
    cmd = ['/usr/libexec/statd/ospf-status']
    data = HOST.run_json(cmd, default={})
    if data == {}:
        return  # No OSPF data available

    control_protocol = {}
    control_protocol["type"] = "infix-routing:ospfv2"
    control_protocol["name"] = "default"
    control_protocol["ietf-ospf:ospf"] = {}
    control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"] = {}


    control_protocol["ietf-ospf:ospf"]["ietf-ospf:router-id"] = data.get("routerId")
    control_protocol["ietf-ospf:ospf"]["ietf-ospf:address-family"] = "ipv4"
    areas = []

    for area_id, values in data.get("areas", {}).items():
        area = {}
        area["ietf-ospf:area-id"] = area_id
        area["ietf-ospf:interfaces"] = {}
        if values.get("area-type"):
            area["ietf-ospf:area-type"] = values["area-type"]
        interfaces = []
        for iface in values.get("interfaces", {}):
            interface = {}
            interface["ietf-ospf:neighbors"] = {}
            interface["name"] = iface["name"]

            if iface.get("drId"):
                interface["dr-router-id"] = iface["drId"]
            if iface.get("drAddress"):
                interface["dr-ip-addr"] = iface["drAddress"]
            if iface.get("bdrId"):
                interface["bdr-router-id"] = iface["bdrId"]
            if iface.get("bdrAddress"):
                interface["bdr-ip-addr"] = iface["bdrAddress"]

            if iface.get("timerPassiveIface"):
                interface["passive"] = True
            else:
                interface["passive"] = False

            interface["enabled"] = iface["ospfEnabled"]
            if iface["networkType"] == "POINTOPOINT":
                interface["interface-type"] = "point-to-point"
            elif iface["networkType"] == "BROADCAST":
                interface["interface-type"] = "broadcast"
            elif iface["networkType"] == "POINTOMULTIPOINT":
                if iface.get("p2mpNonBroadcast", False):
                    interface["interface-type"] = "point-to-multipoint"
                else:
                    interface["interface-type"] = "hybrid"
            elif iface["networkType"] == "NBMA":
                interface["interface-type"] = "non-broadcast"

            if iface.get("state"):
                # Wev've never seen "DependUpon", and has no entry in
                # the YANG model, but is listed before down in Frr
                xlate = {
                    "DependUpon":     "down",
                    "Down":           "down",
                    "Waiting":        "waiting",
                    "Loopback":       "loopback",
                    "Point-To-Point": "point-to-point",
                    "DROther":        "dr-other",
                    "Backup":         "bdr",
                    "DR":             "dr"
                }
                val = xlate.get(iface["state"], "unknown")
                interface["state"] = val

            # Interface priority (for DR/BDR election)
            if iface.get("priority") is not None:
                interface["priority"] = iface["priority"]

            # Interface cost
            if iface.get("cost") is not None:
                interface["cost"] = iface["cost"]

            # Configuration timers (in seconds)
            if iface.get("timerDeadSecs") is not None:
                interface["dead-interval"] = iface["timerDeadSecs"]

            if iface.get("timerRetransmitSecs") is not None:
                interface["retransmit-interval"] = iface["timerRetransmitSecs"]

            if iface.get("transmitDelaySecs") is not None:
                interface["transmit-delay"] = iface["transmitDelaySecs"]

            # Hello interval - convert from milliseconds to seconds
            if iface.get("timerMsecs") is not None:
                hello_sec = iface["timerMsecs"] // 1000
                # timer-value-seconds16 requires range 1..65535, use max(1, value)
                if hello_sec >= 1:
                    interface["hello-interval"] = hello_sec

            # Operational state timers (config false)
            # Hello timer - time remaining until next Hello (convert ms to seconds)
            if iface.get("timerHelloInMsecs") is not None:
                hello_timer_sec = iface["timerHelloInMsecs"] // 1000
                # timer-value-seconds16 requires range 1..65535, use max(1, value)
                if hello_timer_sec >= 1:
                    interface["hello-timer"] = hello_timer_sec

            # Wait timer - time until interface exits Waiting state
            if iface.get("timerWaitSecs") is not None:
                wait_sec = iface["timerWaitSecs"]
                # timer-value-seconds16 requires range 1..65535
                if wait_sec >= 1:
                    interface["wait-timer"] = wait_sec

            neighbors = []
            for neigh in iface["neighbors"]:
                neighbor = {}
                neighbor["neighbor-router-id"] = neigh["neighborIp"]
                neighbor["address"] = neigh["ifaceAddress"]

                # Priority - use existing YANG leaf for operational data
                if neigh.get("nbrPriority") is not None:
                    neighbor["priority"] = neigh["nbrPriority"]

                # Uptime - convert from milliseconds to seconds
                # Note: augmented by infix-routing.yang
                # Use lastPrgrsvChangeMsec from detail output (time since last progressive state change)
                if neigh.get("lastPrgrsvChangeMsec") is not None:
                    uptime_sec = neigh["lastPrgrsvChangeMsec"] // 1000
                    neighbor["infix-routing:uptime"] = uptime_sec

                # Dead timer - convert from milliseconds to seconds
                # timer-value-seconds16 requires range 1..65535
                if neigh.get("routerDeadIntervalTimerDueMsec") is not None:
                    dead_timer_sec = neigh["routerDeadIntervalTimerDueMsec"] // 1000
                    if dead_timer_sec >= 1:
                        neighbor["dead-timer"] = dead_timer_sec

                neighbor["state"] = frr_to_ietf_neighbor_state(neigh["nbrState"])

                # Store role (DR/BDR/DROther) for display
                # Note: augmented by infix-routing.yang
                if neigh.get("role"):
                    neighbor["infix-routing:role"] = frr_to_ietf_neighbor_role(neigh["role"])

                # Store interface name with local address (e.g., "e5:10.0.23.1")
                # Note: augmented by infix-routing.yang
                # Compose from ifaceName and localIfaceAddress
                if neigh.get("ifaceName") and neigh.get("localIfaceAddress"):
                    neighbor["infix-routing:interface-name"] = f"{neigh['ifaceName']}:{neigh['localIfaceAddress']}"
                elif neigh.get("ifaceName"):
                    neighbor["infix-routing:interface-name"] = neigh["ifaceName"]

                if neigh.get("routerDesignatedId"):
                    neighbor["dr-router-id"] = neigh["routerDesignatedId"]
                if neigh.get("routerDesignatedBackupId"):
                    neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"]
                neighbors.append(neighbor)

            interface["ietf-ospf:neighbors"] = {}
            interface["ietf-ospf:neighbors"]["ietf-ospf:neighbor"] = neighbors
            interfaces.append(interface)

        area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces
        areas.append(area)

    add_routes(control_protocol["ietf-ospf:ospf"])
    control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas
    insert(control_protocols, "control-plane-protocol", [control_protocol])


def operational():
    out = {
        "ietf-routing:routing": {
            "control-plane-protocols": {
            }
        }
    }

    add_areas(out['ietf-routing:routing']['control-plane-protocols'])
    return out
