import re
import time
import threading
import whatap.util.process_util as process_util
import whatap.util.logging_util as logging_util


class NicInfo:
    def __init__(self):
        self.nicName = ""
        self.ipAddress = ""
        self.macAddress = ""
        self.link = ""
        self.state = ""
        self.duplex = None
        self.speed = None
        self.mode = None
        self.windowsDriver = None


def _has_cmd(cmd):
    try:
        out = process_util.executeCommandShellWithTimeout(
            'command -v %s >/dev/null 2>&1 && echo yes' % cmd, timeout=3)
        return out == 'yes'
    except Exception:
        return False


def _normalize_mac(raw):
    if not raw:
        return "none"
    s = re.sub(r'^0x', '', raw.strip(), flags=re.IGNORECASE)
    s = re.sub(r'[:\.\-\s]', '', s)
    if not re.match(r'^[0-9A-Fa-f]+$', s):
        return "none"
    s = s.zfill(12).lower()
    return ':'.join(s[i:i+2] for i in range(0, 12, 2))


def _parse_lanscan():
    info = {}
    try:
        out = process_util.executeCommandShellWithTimeout('lanscan', timeout=10)
        for line in out.split('\n'):
            line = line.strip()
            if not line:
                continue
            tokens = line.split()
            lan_name = None
            mac = None
            state = None
            for t in tokens:
                if lan_name is None and re.match(r'^lan\d+$', t):
                    lan_name = t
                if mac is None and re.match(r'^0x[0-9A-Fa-f]{12}$', t):
                    mac = _normalize_mac(t)
                up = t.upper()
                if state is None and up in ('UP', 'DOWN'):
                    state = up
            if not lan_name:
                continue
            info[lan_name] = {
                'mac': mac if mac else 'none',
                'state': state if state else 'Unknown!',
            }
    except Exception:
        pass
    return info


def _parse_netstat_in():
    iface_ip = {}
    try:
        out = process_util.executeCommandShellWithTimeout('netstat -in', timeout=10)
        lines = out.split('\n')
        for i, line in enumerate(lines):
            if i == 0:
                continue
            line = line.strip()
            if not line:
                continue
            tokens = line.split()
            if len(tokens) < 4:
                continue
            name = tokens[0]
            ip = tokens[3]
            if not ip or ip in ('-', '*'):
                ip = 'none'
            iface_ip[name] = ip
    except Exception:
        pass
    return iface_ip


def _aliases_of_base(base, netstat_map):
    pat = re.compile(r'^%s:\d+$' % re.escape(base))
    return sorted(k for k in netstat_map.keys() if pat.match(k))


def _ppa_of_lan(name):
    m = re.match(r'^lan(\d+)$', name)
    return m.group(1) if m else name


def _lanadmin_tuple(lan_name, cache):
    if lan_name in cache:
        return cache[lan_name]
    ppa = _ppa_of_lan(lan_name)
    link = 'Unknown!'
    speed = None
    duplex = 'unknown'

    try:
        outg = process_util.executeCommandShellWithTimeout('lanadmin -g %s' % ppa, timeout=10)
        for line in outg.split('\n'):
            ls = line.strip()
            if 'Operation Status' in ls:
                link = 'yes' if 'up(1)' in ls.lower() else 'no'
                break
        for line in outg.split('\n'):
            ls = line.strip()
            m = re.match(r'^Speed\s*=\s*(.+)$', ls)
            if not m:
                continue
            tokens = m.group(1).strip().split()
            if not tokens:
                continue
            try:
                bps = float(tokens[0])
                if bps > 0:
                    speed = bps
            except ValueError:
                pass
            break
    except Exception:
        pass

    try:
        outx = process_util.executeCommandShellWithTimeout('lanadmin -x %s' % ppa, timeout=10)
        for line in outx.split('\n'):
            ls = line.strip()
            m = re.match(r'^Speed\s*=\s*(.+)$', ls)
            if not m:
                continue
            tail = m.group(1).strip()
            tokens = tail.split()
            if tokens and speed is None:
                try:
                    mbps = float(tokens[0])
                    if mbps > 0:
                        speed = mbps * 1000000.0
                except ValueError:
                    pass
            tail_lower = tail.lower()
            if 'full' in tail_lower:
                duplex = 'Full'
            elif 'half' in tail_lower:
                duplex = 'Half'
            break
    except Exception:
        pass

    result = (link, speed, duplex)
    cache[lan_name] = result
    return result


def _build_apa_map(lan_names, has_nwmgr):
    # /etc/rc.config.d/hpapa fast-path: parse once
    agg_map = {}
    try:
        with open('/etc/rc.config.d/hpapa', 'r') as f:
            text = f.read()
        names = {}
        ports = {}
        for line in text.split('\n'):
            line = line.strip()
            m = re.match(r'HP_APA_INTERFACE_NAME\[(\d+)\]\s*=\s*(["\']?)([^"\']+)\2', line)
            if m:
                names[int(m.group(1))] = m.group(3).split(':')[0]
                continue
            m = re.match(r'HP_APA_PORT_AGGREGATE\[(\d+)\]\s*=\s*(["\']?)([^"\']+)\2', line)
            if m:
                ports[int(m.group(1))] = [p.split(':')[0] for p in m.group(3).split() if p]
                continue
            m = re.match(r'AGGR_NAME\[(\d+)\]\s*=\s*(["\']?)([^"\']+)\2', line)
            if m:
                names[int(m.group(1))] = m.group(3).split(':')[0]
                continue
            m = re.match(r'PORT\[(\d+)\]\s*=\s*(["\']?)([^"\']+)\2', line)
            if m:
                ports.setdefault(int(m.group(1)), []).append(m.group(3).split(':')[0])
                continue
        for idx, agg in names.items():
            mems = ports.get(idx, [])
            if mems:
                agg_map[agg] = mems
    except Exception:
        pass

    # nwmgr fallback: probe each lan (script's path) only if hpapa was empty
    if not agg_map and has_nwmgr:
        for lan in lan_names:
            try:
                out = process_util.executeCommandShellWithTimeout(
                    'nwmgr -q info -I %s' % lan, timeout=5)
                mems = []
                for tok in re.findall(r'\blan\d+(?::\d+)?\b', out):
                    base = tok.split(':')[0]
                    if base != lan and base not in mems:
                        mems.append(base)
                if mems:
                    agg_map[lan] = mems
            except Exception:
                continue

    return agg_map


def _collect():
    result = []
    try:
        lan_info = _parse_lanscan()
        if not lan_info:
            return result

        netstat_map = _parse_netstat_in()
        has_nwmgr = _has_cmd('nwmgr')
        apa_map = _build_apa_map(list(lan_info.keys()), has_nwmgr)

        tuple_cache = {}

        for lan in sorted(lan_info.keys()):
            entry = lan_info[lan]
            mac = entry.get('mac', 'none')
            state = entry.get('state', 'Unknown!')
            ip = netstat_map.get(lan, 'none')

            if lan in apa_map:
                members = apa_map[lan]
                any_yes = any_no = False
                total_speed = 0.0
                dpx = 'unknown'
                for mem in members:
                    mlink, mspeed, mduplex = _lanadmin_tuple(mem, tuple_cache)
                    if mlink == 'yes':
                        any_yes = True
                    elif mlink == 'no':
                        any_no = True
                    if mspeed:
                        total_speed += mspeed
                    if mduplex == 'Full':
                        dpx = 'Full'
                    elif dpx != 'Full' and mduplex == 'Half':
                        dpx = 'Half'
                link = 'yes' if any_yes else ('no' if any_no else 'Unknown!')
                speed = total_speed if total_speed > 0 else None
                duplex = dpx
                mode = 'APA'
            else:
                link, speed, duplex = _lanadmin_tuple(lan, tuple_cache)
                mode = ''

            info = NicInfo()
            info.nicName = lan
            info.ipAddress = ip
            info.macAddress = mac
            info.state = state
            info.link = link
            info.speed = speed
            info.duplex = duplex
            info.mode = mode
            result.append(info)

            for alias in _aliases_of_base(lan, netstat_map):
                a_info = NicInfo()
                a_info.nicName = '%s(%s)' % (alias, lan)
                a_info.ipAddress = netstat_map.get(alias, 'none')
                a_info.macAddress = mac
                a_info.state = state
                a_info.link = link
                a_info.speed = speed
                a_info.duplex = duplex
                a_info.mode = mode
                result.append(a_info)

        for agg in sorted(apa_map.keys()):
            for mem in apa_map[agg]:
                mentry = lan_info.get(mem)
                if not mentry:
                    continue
                mlink, mspeed, mduplex = _lanadmin_tuple(mem, tuple_cache)
                m_info = NicInfo()
                m_info.nicName = '%s_%s' % (agg, mem)
                m_info.ipAddress = 'none'
                m_info.macAddress = mentry.get('mac', 'none')
                m_info.state = mentry.get('state', 'Unknown!')
                m_info.link = mlink
                m_info.speed = mspeed
                m_info.duplex = mduplex
                m_info.mode = 'APA'
                result.append(m_info)

    except Exception as e:
        logging_util.error('nic_info_hpux collect error:', e)
    return result


_cache = []
_cache_lock = threading.Lock()
_bg_started = False
_bg_lock = threading.Lock()

_COLLECT_INTERVAL = 60


def _bg_loop():
    while True:
        try:
            data = _collect()
            with _cache_lock:
                _cache[:] = data
        except Exception as e:
            logging_util.error('nic_info_hpux background error:', e)
        time.sleep(_COLLECT_INTERVAL)


def _ensure_background():
    global _bg_started
    with _bg_lock:
        if _bg_started:
            return
        _bg_started = True
    t = threading.Thread(target=_bg_loop)
    t.daemon = True
    t.start()


def GetNicInfoList():
    _ensure_background()
    with _cache_lock:
        return list(_cache)
