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 _normalize_mac(m):
    if not m:
        return "none"
    raw = re.sub(r'^0x', '', m.strip(), flags=re.IGNORECASE)
    raw = re.sub(r'[:\.\-\s]', '', raw)
    if re.match(r'^[0-9A-Fa-f]{12}$', raw):
        raw = raw.lower()
        return ':'.join(raw[i:i+2] for i in range(0, 12, 2))
    if re.match(r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$', m):
        return m.lower()
    return "none"


def _extract_mac(text):
    m = re.search(r'Hardware Address[:\s]+(\S+)', text, re.IGNORECASE)
    if m:
        return _normalize_mac(m.group(1))
    return "none"


def _get_entstat(ifn, cache):
    if ifn not in cache:
        try:
            cache[ifn] = process_util.executeCommandShellWithTimeout('entstat -d %s 2>/dev/null' % ifn, timeout=10)
        except Exception:
            cache[ifn] = ''
    return cache[ifn]


def _parse_entstat(out):
    link = 'Unknown!'
    speed = None
    duplex = 'unknown'

    for line in out.split('\n'):
        ls = line.strip()
        ll = ls.lower()
        if 'logical port link state' in ll:
            val = ls.split(':', 1)[-1].strip().lower()
            link = 'yes' if val.startswith('up') else 'no'
        elif 'media speed running' in ll:
            parts = ls.split(':', 1)
            if len(parts) > 1:
                tok = parts[1].strip().split()
                if tok:
                    try:
                        speed = float(tok[0]) * 1000000.0
                    except ValueError:
                        pass
        elif 'full duplex' in ll:
            duplex = 'Full'

    if link == 'Unknown!' and speed is not None:
        link = 'yes'

    return link, speed, duplex


def _get_all_ens():
    try:
        out = process_util.executeCommandShellWithTimeout('ifconfig -a 2>/dev/null', timeout=10)
        return sorted(set(re.findall(r'^(en\d+):', out, re.MULTILINE)))
    except Exception:
        return []


def _get_all_ents():
    try:
        out = process_util.executeCommandShellWithTimeout('lsdev -Cc adapter 2>/dev/null', timeout=10)
        return re.findall(r'^(ent\d+)', out, re.MULTILINE)
    except Exception:
        return []


def _get_ifconfig_info(en):
    try:
        out = process_util.executeCommandShellWithTimeout('ifconfig %s 2>/dev/null' % en, timeout=5)
        ips = re.findall(r'inet\s+(\d+\.\d+\.\d+\.\d+)', out)
        ip_str = ','.join(ips) if ips else 'none'
        first = out.split('\n')[0] if out else ''
        state = 'UP' if re.search(r'<[^>]*UP', first) else 'DOWN'
        return ip_str, state
    except Exception:
        return 'none', 'DOWN'


def _get_nv(ifn, cache):
    if ifn not in cache:
        try:
            cache[ifn] = process_util.executeCommandShellWithTimeout(
                'netstat -v %s 2>/dev/null' % ifn, timeout=10)
        except Exception:
            cache[ifn] = ''
    return cache[ifn]


def _get_mac_best(ifn, nv_cache, entstat_cache):
    nv = _get_nv(ifn, nv_cache)
    mac = _extract_mac(nv)
    if mac != 'none':
        return mac
    return _extract_mac(_get_entstat(ifn, entstat_cache))


def _get_ent_from_nv(en, nv_cache):
    out = _get_nv(en, nv_cache)
    for tok in out.split():
        if re.match(r'^ent\d+$', tok):
            return tok
    return None


def _discover_etherchannel(all_ents):
    agg_map = {}
    member_map = {}
    for ent in all_ents:
        try:
            out = process_util.executeCommandShellWithTimeout('lsattr -El %s 2>/dev/null' % ent, timeout=5)
            if 'adapter_names' not in out:
                continue
            mode_m = re.search(r'^mode\s+(\S+)', out, re.MULTILINE)
            mode = mode_m.group(1) if mode_m else ''
            agg_map[ent] = mode

            mem_m = re.search(r'^adapter_names\s+(\S+)', out, re.MULTILINE)
            mem_raw = mem_m.group(1) if mem_m else ''
            members = [x for x in re.split(r',', mem_raw) if x.strip()]

            bak_m = re.search(r'^backup_adapter\s+(\S+)', out, re.MULTILINE)
            backup = bak_m.group(1).strip() if bak_m else ''
            if backup and backup.lower() != 'none':
                members.append(backup)

            for mem in members:
                member_map[mem] = (ent, mode)
        except Exception:
            pass
    return agg_map, member_map


def _parse_members_from_agg_entstat(out):
    members = {}
    cur = mac = speed_raw = link = duplex = ''
    for line in out.split('\n'):
        ls = line.strip()
        ll = ls.lower()
        m = re.match(r'ETHERNET STATISTICS\s*\((\S+)\)', ls)
        if m:
            if cur:
                spd = None
                try:
                    spd = float(speed_raw) * 1000000.0
                except (ValueError, TypeError):
                    pass
                lnk = link if link else ('yes' if spd is not None else 'Unknown!')
                members[cur] = (_normalize_mac(mac) if mac else 'none', lnk, spd, duplex or 'unknown')
            cur = m.group(1)
            mac = speed_raw = link = duplex = ''
            continue
        if not cur:
            continue
        hw = re.search(r'Hardware Address[:\s]+(\S+)', ls, re.IGNORECASE)
        if hw:
            mac = hw.group(1)
        elif 'logical port link state' in ll:
            val = ls.split(':', 1)[-1].strip().lower()
            link = 'yes' if val.startswith('up') else 'no'
        elif 'media speed running' in ll:
            parts = ls.split(':', 1)
            if len(parts) > 1:
                tok = parts[1].strip().split()
                if tok:
                    speed_raw = tok[0]
        elif 'full duplex' in ll:
            duplex = 'Full'

    if cur:
        spd = None
        try:
            spd = float(speed_raw) * 1000000.0
        except (ValueError, TypeError):
            pass
        lnk = link if link else ('yes' if spd is not None else 'Unknown!')
        members[cur] = (_normalize_mac(mac) if mac else 'none', lnk, spd, duplex or 'unknown')
    return members


def _collect():
    result = []
    try:
        entstat_cache = {}
        nv_cache = {}

        all_ens = _get_all_ens()
        all_ents = _get_all_ents()
        agg_map, member_map = _discover_etherchannel(all_ents)

        en_data = {}
        for en in all_ens:
            ent = _get_ent_from_nv(en, nv_cache)
            ips, state = _get_ifconfig_info(en)
            mac = _get_mac_best(en, nv_cache, entstat_cache)
            en_data[en] = (ent, mac, ips, state)

        ent_mac_map = {}
        if any(ent is None for ent, _, _, _ in en_data.values()):
            for ent_dev in all_ents:
                mac = _get_mac_best(ent_dev, nv_cache, entstat_cache)
                if mac != 'none':
                    ent_mac_map[ent_dev] = mac

        seen_aggs = set()

        for en in all_ens:
            ent, mac, ips, state = en_data[en]

            if ent is None and mac != 'none' and ent_mac_map:
                for e, e_mac in ent_mac_map.items():
                    if e_mac == mac:
                        ent = e
                        break

            info = NicInfo()
            info.nicName = '%s(%s)' % (en, ent) if ent else en
            info.ipAddress = ips
            info.state = state

            if mac == 'none' and ent:
                mac = _get_mac_best(ent, nv_cache, entstat_cache)
            info.macAddress = mac

            src = ent if ent else en
            info.link, info.speed, info.duplex = _parse_entstat(_get_entstat(src, entstat_cache))

            if ent and ent in agg_map:
                info.mode = 'EtherChannel:%s' % agg_map[ent]
                seen_aggs.add(ent)
            else:
                info.mode = ''

            result.append(info)

        for agg in seen_aggs:
            mode_val = agg_map.get(agg, '')
            agg_out = _get_entstat(agg, entstat_cache)
            member_info = _parse_members_from_agg_entstat(agg_out)

            agg_members = [m for m, (a, _) in member_map.items() if a == agg]
            for mem in agg_members:
                info = NicInfo()
                info.nicName = '%s_%s' % (agg, mem)
                info.ipAddress = 'none'
                info.state = 'UP'
                info.mode = 'EtherChannel:%s' % mode_val

                if mem in member_info:
                    info.macAddress, info.link, info.speed, info.duplex = member_info[mem]
                else:
                    info.macAddress = _get_mac_best(mem, nv_cache, entstat_cache)
                    info.link, info.speed, info.duplex = _parse_entstat(_get_entstat(mem, entstat_cache))

                result.append(info)

    except Exception as e:
        logging_util.error('nic_info_aix 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_aix 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)
