"""BotStatusManager — 봇 상태 관리 단일 소스 모듈.

dispatch.py, whisper-compile.py 에 분산되어 있던 봇 상태 관련 로직을 하나로 통합.

Usage:
    from utils.bot_status import BotStatusManager

    mgr = BotStatusManager()
    busy  = mgr.get_busy_bots()
    idle  = mgr.get_idle_bots()
    avail = mgr.is_bot_available("bot-e")
    status = mgr.get_team_status("dev4-team")
    occ   = mgr.get_bot_occupation()
    team  = mgr.suggest_team("배너 이미지 디자인 작업")
    warn  = mgr.validate_routing("dev1-team", "카피 마케팅 전략")
"""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Optional

from utils.logger import get_logger

logger = get_logger(__name__)

# ---------------------------------------------------------------------------
# 워크스페이스 경로
# ---------------------------------------------------------------------------

WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))

# ---------------------------------------------------------------------------
# ConfigManager (선택적 임포트 — 실패 시 파일 직접 읽기 fallback)
# ---------------------------------------------------------------------------
try:
    from config.loader import ConfigManager as _ConfigManager

    _cfg = _ConfigManager.get_instance()
    _CONFIG_AVAILABLE = True
except Exception:
    _cfg = None  # type: ignore[assignment]
    _CONFIG_AVAILABLE = False

# ---------------------------------------------------------------------------
# org_loader (선택적 임포트 — 실패 시 constants.json fallback)
# ---------------------------------------------------------------------------
try:
    from utils.org_loader import build_team_to_bot_id_map as _build_team_to_bot_id_map

    _TEAM_TO_BOT_ID: dict[str, str] = _build_team_to_bot_id_map()
    _ORG_LOADER_AVAILABLE = True
except Exception:
    _TEAM_TO_BOT_ID = {}
    _ORG_LOADER_AVAILABLE = False


def _load_constants_json() -> dict:
    """config/constants.json 파일 직접 읽기."""
    path = WORKSPACE / "config" / "constants.json"
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError) as exc:
        logger.warning(f"constants.json 읽기 실패: {exc}")
        return {}


def _resolve_team_to_bot_id() -> dict[str, str]:
    """팀→봇 매핑을 반환. org_loader 우선, fallback으로 constants.json."""
    if _ORG_LOADER_AVAILABLE and _TEAM_TO_BOT_ID:
        return _TEAM_TO_BOT_ID

    if _CONFIG_AVAILABLE and _cfg:
        try:
            result = _cfg.get_constant("team_to_bot")
            if result:
                return dict(result)
        except Exception:
            pass

    constants = _load_constants_json()
    return dict(constants.get("team_to_bot", {})) or {
        "dev1-team": "bot-b",
        "dev2-team": "bot-c",
        "dev3-team": "bot-d",
        "dev4-team": "bot-e",
        "dev5-team": "bot-f",
        "dev6-team": "bot-g",
        "dev7-team": "bot-h",
        "dev8-team": "bot-i",
    }


# ---------------------------------------------------------------------------
# 전역 상수
# ---------------------------------------------------------------------------

ALL_BOTS: list[str] = [
    "bot-b",
    "bot-c",
    "bot-d",
    "bot-e",
    "bot-f",
    "bot-g",
    "bot-h",
    "bot-i",
]

DYNAMIC_BOT_TEAMS: set[str] = {"marketing", "consulting", "publishing", "design", "content"}

_FALLBACK_TEAM_TO_BOT: dict[str, str] = {
    "dev1-team": "bot-b",
    "dev2-team": "bot-c",
    "dev3-team": "bot-d",
    "dev4-team": "bot-e",
    "dev5-team": "bot-f",
    "dev6-team": "bot-g",
    "dev7-team": "bot-h",
    "dev8-team": "bot-i",
}


# ---------------------------------------------------------------------------
# BotStatusManager
# ---------------------------------------------------------------------------


class BotStatusManager:
    """모든 봇의 현재 상태를 관리하는 단일 소스.

    task-timers.json 및 config/constants.json을 기반으로 봇 점유/유휴 상태,
    팀 라우팅 추천, 라우팅 검증 기능을 제공합니다.
    """

    def __init__(
        self,
        workspace_root: Optional[Path] = None,
        *,
        task_timers: Optional[dict] = None,
    ) -> None:
        self._workspace = workspace_root or WORKSPACE
        self._timer_file: Path = self._workspace / "memory" / "task-timers.json"
        self._constants_file: Path = self._workspace / "config" / "constants.json"
        self._injected_task_timers: Optional[dict] = task_timers

    # ------------------------------------------------------------------
    # 내부 헬퍼
    # ------------------------------------------------------------------

    def _load_constants(self) -> dict:
        """인스턴스 workspace 기준 constants.json 파일 직접 읽기."""
        if not self._constants_file.exists():
            return {}
        try:
            return json.loads(self._constants_file.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError) as exc:
            logger.warning(f"constants.json 읽기 실패: {exc}")
            return {}

    def _resolve_team_to_bot_id(self) -> dict[str, str]:
        """인스턴스 workspace 기준 팀→봇 매핑 반환."""
        # 커스텀 workspace이면 파일에서 직접 읽기
        if self._workspace != WORKSPACE:
            constants = self._load_constants()
            return dict(constants.get("team_to_bot", {})) or _FALLBACK_TEAM_TO_BOT

        return _resolve_team_to_bot_id()

    def _resolve_bot_to_dev(self) -> dict[str, str]:
        """인스턴스 workspace 기준 봇→dev short id 매핑."""
        team_to_bot = self._resolve_team_to_bot_id()
        return {bot: tid.replace("-team", "") for tid, bot in team_to_bot.items()}

    def _resolve_dev_short_ids(self) -> list[str]:
        """인스턴스 workspace 기준 dev short id 목록."""
        team_to_bot = self._resolve_team_to_bot_id()
        return [tid.replace("-team", "") for tid in team_to_bot]

    def _load_task_timers(self) -> dict:
        """task-timers.json의 tasks 섹션 반환. 실패 시 빈 dict.

        주입된 task_timers가 있으면 파일 읽기 없이 그것을 반환.
        """
        if self._injected_task_timers is not None:
            return self._injected_task_timers
        if not self._timer_file.exists():
            return {}
        try:
            data = json.loads(self._timer_file.read_text(encoding="utf-8"))
            return data.get("tasks", {})
        except (json.JSONDecodeError, OSError) as exc:
            logger.warning(f"task-timers.json 읽기 실패: {exc}")
            return {}

    def _load_logical_teams(self) -> dict:
        """config/constants.json의 logical_teams 섹션 반환.

        ConfigManager 우선, fallback으로 파일 직접 읽기.
        """
        if _CONFIG_AVAILABLE and _cfg:
            try:
                result = _cfg.get_constant("logical_teams")
                if result:
                    return dict(result)
            except Exception:
                pass

        constants = self._load_constants()
        return constants.get("logical_teams", {})

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def get_busy_bots(self, exclude_task_id: Optional[str] = None) -> dict[str, dict[str, str]]:
        """running 상태 태스크에서 점유된 봇 정보 반환.

        dev팀 running task → TEAM_TO_BOT_ID 매핑으로 봇 추출.
        논리적 팀(design 등) running task → bot 필드 직접 사용.

        Args:
            exclude_task_id: 결과에서 제외할 task_id (자기 자신 제외용).

        Returns:
            {"bot-b": {"task_id": "...", "team_id": "..."}, ...}
        """
        team_to_bot = self._resolve_team_to_bot_id()
        tasks = self._load_task_timers()
        busy: dict[str, dict[str, str]] = {}

        for task_id, entry in tasks.items():
            if entry.get("status") != "running":
                continue
            if exclude_task_id and task_id == exclude_task_id:
                continue

            team_id: str = entry.get("team_id", "")

            # dev팀 → 고정 봇 매핑
            if team_id in team_to_bot:
                bot = team_to_bot[team_id]
                busy[bot] = {"task_id": task_id, "team_id": team_id}

            # 논리적 팀 / composite 등 → bot 필드 직접 활용
            bot_field: str = entry.get("bot", "")
            if bot_field:
                busy[bot_field] = {"task_id": task_id, "team_id": team_id}

        return busy

    def get_idle_bots(self) -> list[str]:
        """전체 봇 목록에서 busy 봇을 제외한 유휴 봇 ID 목록 반환.

        Returns:
            ["bot-c", "bot-f", ...] (우선순위 순서 유지)
        """
        busy = set(self.get_busy_bots().keys())
        return [bot for bot in ALL_BOTS if bot not in busy]

    def is_bot_available(self, bot_id: str) -> bool:
        """특정 봇이 현재 유휴 상태인지 확인.

        Args:
            bot_id: 확인할 봇 ID (예: "bot-e").

        Returns:
            True이면 유휴, False이면 점유 중.
        """
        return bot_id not in self.get_busy_bots()

    def get_team_status(self, team_id: str) -> str:
        """dev팀의 현재 상태 문자열 반환.

        - running task 있음 → "작업중"
        - 봇이 논리적 팀에 점유됨 → "봇점유({team}:{task_id})"
        - 그 외 → "유휴"

        Args:
            team_id: dev팀 ID (예: "dev4-team").

        Returns:
            "작업중" | "봇점유(design:task-123)" | "유휴"
        """
        tasks = self._load_task_timers()

        # running task 직접 확인
        for entry in tasks.values():
            if entry.get("status") == "running" and entry.get("team_id") == team_id:
                return "작업중"

        # 봇 점유 확인 (논리적 팀이 이 팀의 봇을 점유)
        team_to_bot = self._resolve_team_to_bot_id()
        bot_id = team_to_bot.get(team_id)
        if bot_id:
            occupation = self.get_bot_occupation()
            dev_short = team_id.replace("-team", "")
            if dev_short in occupation:
                occ_info = occupation[dev_short]
                return f"봇점유({occ_info['team']}:{occ_info['task_id']})"

        return "유휴"

    def get_bot_occupation(self) -> dict[str, dict[str, str]]:
        """논리적 팀이 물리 봇을 점유하는 경우를 탐지.

        dev팀 자체 작업은 제외하고, marketing/design/content 등 논리적 팀이
        dev봇을 빌려 사용하는 경우만 반환합니다.

        Returns:
            {dev_short_id: {"team": team_id, "task_id": task_id, "bot_id": bot_id}}
            예: {"dev4": {"team": "design", "task_id": "task-99", "bot_id": "bot-e"}}
        """
        bot_to_dev = self._resolve_bot_to_dev()
        dev_short_ids = set(self._resolve_dev_short_ids())
        tasks = self._load_task_timers()
        occupation: dict[str, dict[str, str]] = {}

        for task_id, entry in tasks.items():
            if entry.get("status") != "running":
                continue

            team_id: str = entry.get("team_id", "")
            bot_id: str = entry.get("bot", "")
            if not bot_id:
                continue

            # dev팀 자체 작업은 점유 대상 아님
            team_short = team_id.replace("-team", "")
            if team_short in dev_short_ids:
                continue

            # bot_id가 dev봇에 해당하는지 확인
            dev_owner = bot_to_dev.get(bot_id)
            if dev_owner:
                occupation[dev_owner] = {
                    "team": team_id,
                    "task_id": entry.get("task_id", task_id),
                    "bot_id": bot_id,
                }

        return occupation

    def suggest_team(self, task_desc: str) -> Optional[str]:
        """작업 설명에서 키워드 매칭으로 적합한 논리적 팀 추천.

        constants.json의 logical_teams 키워드를 사용하며 anti_keywords가
        포함된 팀은 후보에서 제외합니다.

        Args:
            task_desc: 작업 설명 문자열.

        Returns:
            추천 팀 ID (예: "design") 또는 None (매칭 없음).
        """
        logical_teams = self._load_logical_teams()
        if not logical_teams:
            return None

        best_team: Optional[str] = None
        best_score: int = 0

        for team_id, config in logical_teams.items():
            if team_id == "composite":
                continue  # composite은 추천 대상 아님

            keywords: list = config.get("keywords", [])
            anti_keywords: list = config.get("anti_keywords", [])

            # anti-keyword가 하나라도 포함되면 이 팀 제외
            if any(ak in task_desc for ak in anti_keywords):
                continue

            # keyword 매칭 점수 계산
            score = sum(1 for kw in keywords if kw in task_desc)

            if score > best_score:
                best_score = score
                best_team = team_id

        return best_team if best_score > 0 else None

    def validate_routing(
        self,
        team_id: str,
        task_desc: str,
        override_routing: bool = False,
    ) -> Optional[str]:
        """dev팀에 논리적 팀 소관 작업이 위임될 때 경고 메시지 반환.

        논리적 팀(design, marketing 등)에 직접 위임된 경우는 검증 생략.

        Args:
            team_id: 위임 대상 팀 ID (예: "dev1-team").
            task_desc: 작업 설명 문자열.
            override_routing: True이면 경고를 무시하고 None 반환.

        Returns:
            경고 메시지 문자열 또는 None (문제 없음).
        """
        # 논리적 팀이면 검증 불필요
        if team_id in DYNAMIC_BOT_TEAMS:
            return None

        suggested = self.suggest_team(task_desc)
        if suggested is None:
            return None

        if override_routing:
            logger.warning(f"[routing-override] {team_id}에 {suggested} 소관 작업 위임 (override_routing=True)")
            return None

        logical_teams = self._load_logical_teams()
        description = logical_teams.get(suggested, {}).get("description", "")
        return (
            f"이 작업은 --team {suggested}이 적합합니다 ({description}). "
            f"계속하려면 override_routing=True를 사용하세요."
        )
