from __future__ import annotations
from pathlib import Path
import os
import sys
import uuid
import logging
import subprocess
import platform
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
from watchdog.observers import Observer
from backend.services.watchdog_handler import FileCreatedHandler
from config import Config

# Job ID별 진행률 (스레드 안전)
_progress: dict[str, int] = {}
_progress_lock = Lock()


def _set_progress(job_id: str, value: int) -> None:
    with _progress_lock:
        _progress[job_id] = max(0, min(100, int(value)))


def get_progress(job_id: str) -> int:
    with _progress_lock:
        return int(_progress.get(job_id, 0))


def on_complete(job_id: str) -> None:
    _set_progress(job_id, 100)


# ─────────────────────────────────────────────────────────────
# IP 목록 저장
# ─────────────────────────────────────────────────────────────

def save_ip_addresses(ips: str, folder: str | os.PathLike[str]) -> list[tuple[str, str]]:
    out_dir = Path(folder)
    out_dir.mkdir(parents=True, exist_ok=True)

    ip_files: list[tuple[str, str]] = []
    for i, raw in enumerate((ips or "").splitlines()):
        ip = raw.strip()
        if not ip:
            continue
        file_path = out_dir / f"ip_{i}.txt"
        file_path.write_text(ip + "\n", encoding="utf-8")
        ip_files.append((ip, str(file_path)))
    return ip_files


# ─────────────────────────────────────────────────────────────
# 개별 IP 처리
# ─────────────────────────────────────────────────────────────

def _build_command(script: str, ip_file: str, xml_file: str | None) -> list[str]:
    script_path = Path(Config.SCRIPT_FOLDER) / script
    if not script_path.exists():
        raise FileNotFoundError(f"스크립트를 찾을 수 없습니다: {script_path}")

    if script_path.suffix == ".sh":
        # Windows에서 .sh 실행은 bash 필요 (Git Bash/WSL 등). 없으면 예외 처리.
        if platform.system() == "Windows":
            bash = shutil.which("bash")  # type: ignore[name-defined]
            if not bash:
                raise RuntimeError("Windows에서 .sh 스크립트를 실행하려면 bash가 필요합니다.")
            cmd = [bash, str(script_path), ip_file]
        else:
            cmd = [str(script_path), ip_file]
    elif script_path.suffix == ".py":
        cmd = [sys.executable, str(script_path), ip_file]
    else:
        raise ValueError(f"지원되지 않는 스크립트 형식: {script_path.suffix}")

    if xml_file:
        cmd.append(xml_file)
    return cmd


def process_ip(ip_file: str, script: str, xml_file: str | None = None) -> None:
    ip = Path(ip_file).read_text(encoding="utf-8").strip()
    cmd = _build_command(script, ip_file, xml_file)

    logging.info("🔧 실행 명령: %s", " ".join(cmd))
    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=True,
            cwd=str(Path(Config.SCRIPT_FOLDER)),
            timeout=int(os.getenv("SCRIPT_TIMEOUT", "1800")),  # 30분 기본
        )
        logging.info("[%s] ✅ stdout:\n%s", ip, result.stdout)
        if result.stderr:
            logging.warning("[%s] ⚠ stderr:\n%s", ip, result.stderr)
    except subprocess.CalledProcessError as e:
        logging.error("[%s] ❌ 스크립트 실행 오류(code=%s): %s", ip, e.returncode, e.stderr or e)
    except subprocess.TimeoutExpired:
        logging.error("[%s] ⏰ 스크립트 실행 타임아웃", ip)


# ─────────────────────────────────────────────────────────────
# 병렬 처리 진입점
# ─────────────────────────────────────────────────────────────

def process_ips_concurrently(ip_files, job_id, observer: Observer, script: str, xml_file: str | None):
    total = len(ip_files)
    completed = 0
    _set_progress(job_id, 0)

    try:
        with ThreadPoolExecutor(max_workers=Config.MAX_WORKERS) as pool:
            futures = {pool.submit(process_ip, ip_path, script, xml_file): ip for ip, ip_path in ip_files}
            for fut in as_completed(futures):
                ip = futures[fut]
                try:
                    fut.result()
                except Exception as e:
                    logging.error("%s 처리 중 오류 발생: %s", ip, e)
                finally:
                    completed += 1
                    if total:
                        _set_progress(job_id, int(completed * 100 / total))
    finally:
        try:
            observer.stop()
            observer.join(timeout=5)
        except Exception:
            pass


# ─────────────────────────────────────────────────────────────
# 외부에서 한 번에 처리(동기)
# ─────────────────────────────────────────────────────────────

def handle_ip_processing(ip_text: str, script: str, xml_file: str | None = None) -> str:
    job_id = str(uuid.uuid4())

    temp_dir = Path(Config.UPLOAD_FOLDER) / job_id
    ip_files = save_ip_addresses(ip_text, temp_dir)

    xml_path = str(Path(Config.XML_FOLDER) / xml_file) if xml_file else None

    handler = FileCreatedHandler(job_id, len(ip_files))
    observer = Observer()
    observer.schedule(handler, Config.IDRAC_INFO_FOLDER, recursive=False)
    observer.start()

    process_ips_concurrently(ip_files, job_id, observer, script, xml_path)
    return job_id