// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

/**
 * @file physical.cpp
 * @brief Layer 1 (Physical) - link bring-up and egress reporting.
 *
 * WiFi station bring-up is asynchronous (poll wifi_ready()). det_net_egress()
 * reports the live default-route interface (lwIP `netif_default`) and classifies
 * it against the WiFi IPs; the stack owns failover, so there is nothing to poll
 * or track here. The IP classifier is pure and host-tested.
 */

#include "physical.h"

#ifdef ARDUINO
#include "lwip/ip_addr.h"
#include "lwip/netif.h"
#include <WiFi.h>
#if DETWS_ENABLE_ETHERNET
#include <ETH.h>
#endif

bool init_wifi_physical(const char *ssid, const char *password)
{
    WiFi.begin(ssid, password);
    return true;
}

bool wifi_ready()
{
    return WiFi.isConnected();
}

#if DETWS_ENABLE_ETHERNET
bool init_eth_physical(void)
{
    // PHY pins / type / clock come from the ETH_PHY_* build flags (ETH.begin() defaults).
    return ETH.begin();
}
bool eth_ready(void)
{
    return ETH.linkUp() && (uint32_t)ETH.localIP() != 0;
}
#else
bool init_eth_physical(void)
{
    return false; // Ethernet not enabled (DETWS_ENABLE_ETHERNET)
}
bool eth_ready(void)
{
    return false;
}
#endif

#if DETWS_ENABLE_IPV6
#include "lwip/ip6_addr.h"
#include <string.h>

bool init_ipv6_physical(void)
{
    // The WiFi wrapper's enable call was renamed in Arduino-ESP32 3.0; the address readout
    // below goes straight to lwIP, which is stable across both cores.
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
    return WiFi.enableIPv6(true);
#else
    return WiFi.enableIpV6();
#endif
}

bool net_global_ipv6(DetIp *out)
{
    if (!out || !netif_default)
        return false;
    for (int8_t i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++)
    {
        if (!ip6_addr_isvalid(netif_ip6_addr_state(netif_default, i)))
            continue;
        const ip6_addr_t *a6 = netif_ip6_addr(netif_default, i);
        if (!ip6_addr_isglobal(a6))
            continue;
        out->family = DET_IP_V6;
        memcpy(out->bytes, a6->addr, 16); // lwIP holds the 16 bytes in network order
        return true;
    }
    return false;
}

bool ipv6_ready(void)
{
    DetIp tmp;
    return net_global_ipv6(&tmp);
}
#else
bool init_ipv6_physical(void)
{
    return false; // IPv6 not enabled (DETWS_ENABLE_IPV6)
}
bool net_global_ipv6(DetIp *)
{
    return false;
}
bool ipv6_ready(void)
{
    return false;
}
#endif

uint32_t det_net_egress_ip(void)
{
    // netif_default is the current default-route interface (the egress).
    return netif_default ? ip4_addr_get_u32(ip_2_ip4(&netif_default->ip_addr)) : 0;
}

DetIface det_net_egress(void)
{
    uint32_t egress = det_net_egress_ip();
    if (egress == 0)
        return DETIFACE_ANY;
    uint32_t sta = WiFi.isConnected() ? (uint32_t)WiFi.localIP() : 0;
    uint32_t ap = (WiFi.getMode() & WIFI_AP) ? (uint32_t)WiFi.softAPIP() : 0;
    return det_net_classify_ip(egress, sta, ap);
}

#else // host build - no radio / stack

bool init_wifi_physical(const char *, const char *)
{
    return true;
}
bool wifi_ready()
{
    return true;
}
bool init_eth_physical(void)
{
    return false; // no Ethernet PHY on a host build
}
bool eth_ready(void)
{
    return false;
}
bool init_ipv6_physical(void)
{
    return false; // no netif on a host build
}
bool net_global_ipv6(DetIp *)
{
    return false;
}
bool ipv6_ready(void)
{
    return false;
}
uint32_t det_net_egress_ip(void)
{
    return 0;
}
DetIface det_net_egress(void)
{
    return DETIFACE_ANY;
}

#endif // ARDUINO

// Pure classifier (always compiled, host-tested).
DetIface det_net_classify_ip(uint32_t egress_ip, uint32_t sta_ip, uint32_t ap_ip)
{
    if (egress_ip == 0)
        return DETIFACE_ANY;
    if (sta_ip != 0 && egress_ip == sta_ip)
        return DETIFACE_STA;
    if (ap_ip != 0 && egress_ip == ap_ip)
        return DETIFACE_AP;
    return DETIFACE_ETH; // a live route that is neither WiFi IP -> wired
}
