#!/usr/bin/env python3
from __future__ import annotations

import re
import subprocess
from pathlib import Path
from typing import Optional
import logging

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [INFO] root: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# ─────────────────────────────────────────────────────────────
# 저장 위치: data/scripts 아래에 있다면 → data/idrac_info 생성
def resolve_output_dir() -> Path:
    here = Path(__file__).resolve().parent
    base = here.parent if here.name.lower() == "scripts" else here
    out = base / "temp" / "staging"
    out.mkdir(parents=True, exist_ok=True)
    return out


# ─────────────────────────────────────────────────────────────
# iDRAC 접속 계정
IDRAC_USER = "root"
IDRAC_PASS = "calvin"


def run(cmd: list[str]) -> str:
    """racadm 명령 실행 (stdout 수집)"""
    try:
        return subprocess.getoutput(" ".join(cmd))
    except Exception:
        return ""


def parse_single_value(pattern: str, text: str) -> Optional[str]:
    """단일 값 추출용"""
    m = re.search(pattern, text, flags=re.IGNORECASE)
    return m.group(1).strip() if m else None


def find_mac_by_fqdd(fqdd: str, text: str) -> Optional[str]:
    """
    swinventory 출력에서 FQDD = {fqdd} 라인을 찾고,
    그 주변(±10줄) 내에서 MAC 주소를 추출.
    """
    lines = text.splitlines()
    for i, line in enumerate(lines):
        if f"FQDD = {fqdd}" in line:
            for j in range(max(0, i - 10), min(i + 10, len(lines))):
                m = re.search(r"([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})", lines[j])
                if m:
                    return m.group(1)
    return None


def extract_vendors(hwinventory: str) -> tuple[str, str]:
    """
    [InstanceID: DIMM...] 또는 [InstanceID: Disk.Bay...] 블록 내 Manufacturer 추출
    """
    # Memory Vendors
    mem_vendors = re.findall(
        r"\[InstanceID:\s*DIMM[^\]]*\][^\[]*?Manufacturer\s*=\s*([^\n\r]+)",
        hwinventory,
        flags=re.IGNORECASE
    )
    mem_vendors = [v.strip() for v in mem_vendors if v.strip()]
    memory = ", ".join(sorted(set(mem_vendors))) if mem_vendors else "Not Found"

    # SSD Vendors
    ssd_vendors = re.findall(
        r"\[InstanceID:\s*Disk\.Bay[^\]]*\][^\[]*?Manufacturer\s*=\s*([^\n\r]+)",
        hwinventory,
        flags=re.IGNORECASE
    )
    ssd_vendors = [v.strip() for v in ssd_vendors if v.strip()]
    ssd = ", ".join(sorted(set(ssd_vendors))) if ssd_vendors else "Not Found"

    return memory, ssd


def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
    """단일 서버 iDRAC 정보 수집"""
    logging.info(f"[+] Collecting iDRAC info from {ip} ...")

    getsysinfo = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "getsysinfo"])
    hwinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "hwinventory"])
    swinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "swinventory"])

    # 서비스 태그
    svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
    if not svc_tag:
        logging.error(f"[!] Failed to retrieve SVC Tag for IP: {ip}")
        return

    # iDRAC MAC
    idrac_mac = parse_single_value(r"MAC\s*Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)

    # NIC.Integrated / Onboard MAC
    integrated_1 = parse_single_value(r"NIC\.Integrated\.1-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
    integrated_2 = parse_single_value(r"NIC\.Integrated\.1-2-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
    onboard_1 = parse_single_value(r"NIC\.Embedded\.1-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
    onboard_2 = parse_single_value(r"NIC\.Embedded\.2-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)

    # NIC Slot별 MAC
    NIC_Slot_2_1 = find_mac_by_fqdd("NIC.Slot.2-1-1", swinventory)
    NIC_Slot_2_2 = find_mac_by_fqdd("NIC.Slot.2-2-1", swinventory)
    NIC_Slot_3_1 = find_mac_by_fqdd("NIC.Slot.3-1-1", swinventory)
    NIC_Slot_3_2 = find_mac_by_fqdd("NIC.Slot.3-2-1", swinventory)

    # 메모리 / SSD 제조사
    memory, ssd = extract_vendors(hwinventory)

    # 결과 파일 저장
    out_file = output_dir / f"{svc_tag}.txt"
    with out_file.open("w", encoding="utf-8", newline="\n") as f:
        f.write(f"{svc_tag}\n")
        f.write(f"{integrated_1 or ''}\n")
        f.write(f"{integrated_2 or ''}\n")
        f.write(f"{onboard_1 or ''}\n")
        f.write(f"{onboard_2 or ''}\n")
        f.write(f"{NIC_Slot_2_1 or ''}\n")
        f.write(f"{NIC_Slot_2_2 or ''}\n")
        f.write(f"{idrac_mac or ''}\n")
        f.write(f"{memory}\n")
        f.write(f"{ssd}\n")

    logging.info(f"[✔] {svc_tag} ({ip}) info saved.")


def main(ip_file: str) -> None:
    ip_path = Path(ip_file)
    if not ip_path.is_file():
        logging.error(f"[!] IP file {ip_file} does not exist.")
        return

    output_dir = resolve_output_dir()
    ips = [line.strip() for line in ip_path.read_text(encoding="utf-8").splitlines() if line.strip()]
    if not ips:
        logging.error("[!] No IP addresses found in the file.")
        return

    for ip in ips:
        try:
            fetch_idrac_info_one(ip, output_dir)
        except Exception as e:
            logging.error(f"[!] Error processing {ip}: {e}")

    try:
        ip_path.unlink(missing_ok=True)
    except Exception:
        pass

    logging.info("\n정보 수집 완료.")


if __name__ == "__main__":
    import sys, time

    if len(sys.argv) != 2:
        logging.error("Usage: python script.py <ip_file>")
        sys.exit(1)

    start = time.time()
    main(sys.argv[1])
    end = time.time()
    elapsed = int(end - start)
    h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
    logging.info(f"수집 완료 시간: {h}시간 {m}분 {s}초")
