#!/usr/bin/env python3
"""
통합 시스템 제어 스크립트 (Unified System Control Script)
기존의 개별 제어 스크립트들을 하나로 통합하였습니다.

지원 기능:
- power_on      (06-PowerON.py)
- power_off     (07-PowerOFF.py)
- log_clear     (05-clrsel.py)
- job_delete    (08-job_delete_all.py)
- tsr_collect   (03-tsr_log.py)
- tsr_save      (04-tsr_save.py)
"""

import os
import sys
import argparse
import subprocess
import time
import logging
import re
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv

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

# Load .env relative to script location or current directory
load_dotenv() 

IDRAC_USER = os.getenv("IDRAC_USER", "root")
IDRAC_PASS = os.getenv("IDRAC_PASS", "calvin")
TSR_SHARE_URL = os.getenv("TSR_SHARE_URL", "//10.10.3.15/share/")
OME_USER = os.getenv("OME_USER", "")
OME_PASS = os.getenv("OME_PASS", "")
TSR_JOB_TIMEOUT = int(os.getenv("TSR_JOB_TIMEOUT", "900"))
TSR_JOB_POLL_INTERVAL = int(os.getenv("TSR_JOB_POLL_INTERVAL", "10"))
TSR_SAVE_WORKERS = int(os.getenv("TSR_SAVE_WORKERS", "30"))

# -----------------------------------------------------------------------------
# Command Definitions
# -----------------------------------------------------------------------------
# Each action maps to a function that returns the racadm command list
def cmd_power_on(ip):
    return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"]

def cmd_power_off(ip):
    return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"]

def cmd_log_clear(ip):
    return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "clrsel"]

def cmd_job_delete(ip):
    return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"]

def cmd_tsr_collect(ip):
    return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"]

def cmd_tsr_save(ip):
    return [
        "racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS,
        "techsupreport", "export", "-l", TSR_SHARE_URL, "-u", OME_USER, "-p", OME_PASS
    ]

ACTION_MAP = {
    "power_on": cmd_power_on,
    "power_off": cmd_power_off,
    "log_clear": cmd_log_clear,
    "job_delete": cmd_job_delete,
    "tsr_collect": cmd_tsr_collect,
    "tsr_save": cmd_tsr_save,
}

# -----------------------------------------------------------------------------
# Execution Logic
# -----------------------------------------------------------------------------
def redact_cmd(cmd):
    redacted = []
    hide_next = False
    for part in cmd:
        if hide_next:
            redacted.append("***")
            hide_next = False
            continue
        redacted.append(part)
        if part == "-p":
            hide_next = True
    return redacted


def extract_job_id(output: str) -> Optional[str]:
    match = re.search(r"Job ID\s*=\s*(JID_\d+)", output or "")
    return match.group(1) if match else None


def wait_for_job(ip: str, job_id: str) -> bool:
    deadline = time.time() + TSR_JOB_TIMEOUT
    view_cmd = [
        "racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS,
        "jobqueue", "view", "-i", job_id
    ]

    while time.time() < deadline:
        result = subprocess.run(
            view_cmd,
            capture_output=True,
            text=True,
            timeout=120
        )
        output = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part)
        logger.info(f"[{ip}] Job {job_id} status:\n{output}")

        if "Status=Completed" in output:
            return True
        if "Status=Failed" in output:
            return False

        time.sleep(TSR_JOB_POLL_INTERVAL)

    logger.error(f"[{ip}] Job {job_id} status check timed out after {TSR_JOB_TIMEOUT} seconds.")
    return False


def execute_action(ip: str, action_name: str) -> bool:
    """Run the racadm command for a single IP."""
    try:
        cmd_func = ACTION_MAP.get(action_name)
        if not cmd_func:
            logger.error(f"[{ip}] Unknown action: {action_name}")
            return False

        cmd = cmd_func(ip)
        logger.info(f"[{ip}] Executing {action_name}...")
        if action_name == "tsr_save":
            logger.info(f"[{ip}] Command: {redact_cmd(cmd)}")
        
        # Run command
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=600  # Generous timeout for TSR operations
        )
        output = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part)

        if result.returncode == 0:
            logger.info(f"[{ip}] Success: {output}")
            if action_name == "tsr_save":
                job_id = extract_job_id(output)
                if not job_id:
                    logger.error(f"[{ip}] TSR Save did not return a Job ID.")
                    return False
                return wait_for_job(ip, job_id)
            return True
        else:
            logger.error(f"[{ip}] Failed: {output}")
            return False

    except Exception as e:
        logger.error(f"[{ip}] Exception: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description="Unified System Control Script")
    parser.add_argument("action", choices=ACTION_MAP.keys(), help="Action to perform")
    parser.add_argument("ip_file", type=Path, help="Path to the IP list file")
    
    # Optional: Allow passing IP directly via args instead of file (future extension)
    
    args = parser.parse_args()

    ip_file = args.ip_file
    action = args.action

    if not ip_file.is_file():
        logger.error(f"IP file not found: {ip_file}")
        sys.exit(1)

    # Read IPs
    try:
        with ip_file.open("r", encoding="utf-8") as f:
            ips = [line.strip() for line in f if line.strip()]
    except Exception as e:
        logger.error(f"Failed to read IP file: {e}")
        sys.exit(1)

    if not ips:
        logger.warning("No IPs found in file.")
        sys.exit(0)

    logger.info(f"Starting '{action}' for {len(ips)} servers...")
    start_time = time.time()

    success_count = 0
    failed_count = 0

    if action == "tsr_save":
        max_workers = max(1, min(TSR_SAVE_WORKERS, len(ips)))
        logger.info(f"TSR Save parallel workers={max_workers}")
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = [executor.submit(execute_action, ip, action) for ip in ips]
            for future in futures:
                if future.result():
                    success_count += 1
                else:
                    failed_count += 1
    else:
        # Parallel Execution
        # Adjust max_workers as needed. 20 is a safe defaults for I/O bound network tasks.
        with ThreadPoolExecutor(max_workers=20) as executor:
            futures = [executor.submit(execute_action, ip, action) for ip in ips]
            for future in futures:
                if future.result():
                    success_count += 1
                else:
                    failed_count += 1

    elapsed = time.time() - start_time
    logger.info(
        f"Completed '{action}' in {elapsed:.2f} seconds. "
        f"success={success_count}, failed={failed_count}"
    )

    if failed_count:
        sys.exit(1)


if __name__ == "__main__":
    main()
