#!/usr/bin/env python3
"""팀장 → 아누 완료 통보 스크립트 (체인 인식 버전)"""

import argparse
import json
import os
import re
import shlex
import subprocess
import sys
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path

import requests

# scripts/ 디렉토리를 sys.path에 추가 (report_utils import용)
_SCRIPTS_DIR = Path(__file__).resolve().parent
if str(_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(_SCRIPTS_DIR))

from report_utils import format_notification_message as _format_notification_message  # noqa: E402

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

try:
    sys.path.insert(0, WORKSPACE_ROOT)
    from utils.todo_sync import sync_task_completion  # type: ignore[import-untyped]
except ImportError:
    sync_task_completion = None  # todo_sync가 없으면 스킵
KST = timezone(timedelta(hours=9))
DONE_PROTOCOL_LOG = str(Path(WORKSPACE_ROOT) / "logs" / "done-protocol.log")

# dispatch 인자 검증 패턴
_RE_TEAM = re.compile(r"^[a-z0-9\-]+$")
_RE_TASK_FILE = re.compile(r"^[a-zA-Z0-9/_\-\.]+$")
_RE_LEVEL = re.compile(r"^(normal|critical|security)$")
_RE_TASK_ID = re.compile(r"^task-\d+\.\d+$")


def load_env_keys(env_file: str | None = None) -> dict[str, str]:
    """`.env.keys` 파일을 파싱하여 환경변수 딕셔너리를 반환한다.

    지원 형식:
        export KEY=VALUE
        KEY=VALUE
        export KEY="VALUE"   (따옴표 제거)
        export KEY='VALUE'   (따옴표 제거)

    주석(#)과 빈 줄은 무시한다.
    파일이 없으면 빈 딕셔너리를 반환한다.
    """
    if env_file is None:
        env_file = f"{WORKSPACE_ROOT}/.env.keys"

    result: dict[str, str] = {}
    path = Path(env_file)
    if not path.exists():
        return result

    try:
        for raw_line in path.read_text(encoding="utf-8").splitlines():
            line = raw_line.strip()
            if not line or line.startswith("#"):
                continue
            # export 접두사 제거
            if line.startswith("export "):
                line = line[len("export ") :].strip()
            if "=" not in line:
                continue
            key, _, value = line.partition("=")
            key = key.strip()
            value = value.strip()
            # 감싸는 따옴표 제거
            if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
                value = value[1:-1]
            if key:
                result[key] = value
    except OSError:
        pass

    return result


def _validate_dispatch_args(
    team: str,
    task_file: str,
    level: str,
    next_task_id: str | None,
) -> None:
    """dispatch 인자를 검증한다. 패턴 불일치 시 ValueError를 발생시킨다.

    Args:
        team: 팀 이름 (예: "dev1-team")
        task_file: 태스크 파일 경로
        level: 우선순위 ("normal" | "critical" | "security")
        next_task_id: 다음 태스크 ID (예: "task-566.2") 또는 None
    """
    if not _RE_TEAM.match(team):
        msg = f"dispatch 인자 검증 실패 - team 패턴 불일치: {team!r}"
        print(f"ERROR: {msg}", file=sys.stderr)
        raise ValueError(msg)

    if not _RE_TASK_FILE.match(task_file):
        msg = f"dispatch 인자 검증 실패 - task_file 패턴 불일치: {task_file!r}"
        print(f"ERROR: {msg}", file=sys.stderr)
        raise ValueError(msg)

    if not _RE_LEVEL.match(level):
        msg = f"dispatch 인자 검증 실패 - level 허용값 초과: {level!r}"
        print(f"ERROR: {msg}", file=sys.stderr)
        raise ValueError(msg)

    if next_task_id is not None and not _RE_TASK_ID.match(next_task_id):
        msg = f"dispatch 인자 검증 실패 - next_task_id 패턴 불일치: {next_task_id!r}"
        print(f"ERROR: {msg}", file=sys.stderr)
        raise ValueError(msg)


def log_protocol(task_id: str, message: str) -> None:
    """done-protocol.log에 상태 전이를 기록한다."""
    ts = datetime.now(KST).isoformat()
    line = f"[{ts}] [notify-completion] {task_id}: {message}\n"
    try:
        log_path = Path(DONE_PROTOCOL_LOG)
        log_path.parent.mkdir(parents=True, exist_ok=True)
        with open(str(log_path), "a", encoding="utf-8") as f:
            f.write(line)
    except OSError:
        pass  # 로그 실패는 무시


def _save_completion_message(task_id: str, message: str) -> None:
    """완료 메시지를 파일로 저장한다. 아누가 .done 감지 시 이 파일을 읽어 그대로 전달한다."""
    completion_file = Path(f"{WORKSPACE_ROOT}/memory/events/{task_id}.completion.txt")
    try:
        completion_file.parent.mkdir(parents=True, exist_ok=True)
        completion_file.write_text(message, encoding="utf-8")
        log_protocol(task_id, f".completion.txt 저장 완료 ({len(message)} chars)")
    except OSError as e:
        log_protocol(task_id, f".completion.txt 저장 실패: {e}")


def get_anu_key() -> str:
    """ANU_KEY를 환경변수에서 가져온다. 없으면 종료."""
    key = os.environ.get("COKACDIR_KEY_ANU")
    if not key:
        print(
            "[FATAL] COKACDIR_KEY_ANU 환경변수가 설정되지 않았습니다. "
            "아누 세션 인증키가 필요합니다. "
            "해결: 'source /home/jay/workspace/.env.keys'를 실행하거나 "
            "'--anu-key <key>' 옵션으로 직접 전달하세요.",
            file=sys.stderr,
        )
        sys.exit(1)
    return key


def check_chain_status(task_id: str) -> dict:
    """chain_manager.py check 호출하여 체인 소속 여부 확인"""
    cmd = [
        "python3",
        f"{WORKSPACE_ROOT}/chain_manager.py",
        "check",
        "--task-id",
        task_id,
    ]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            return json.loads(result.stdout)
    except (subprocess.TimeoutExpired, json.JSONDecodeError):
        pass
    # 실패 시 체인 아닌 것으로 간주
    return {"in_chain": False, "is_last": False, "chain_id": None, "next_task_id": None}


def send_telegram_notification(chat_id: str, message: str) -> bool:
    """직접 Telegram Bot API 호출 (토큰 0, Claude 세션 미생성)"""
    bot_token = os.environ.get("ANU_BOT_TOKEN")
    if not bot_token:
        log_protocol("", "WARN: ANU_BOT_TOKEN 미설정, 알림 스킵")
        return False

    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    payload = {"chat_id": chat_id, "text": message, "parse_mode": "Markdown"}

    for attempt in range(3):
        try:
            resp = requests.post(url, json=payload, timeout=10)
            if resp.status_code == 200:
                return True
            if resp.status_code == 400:
                # Markdown 파싱 에러 → plain text fallback
                fallback = {"chat_id": chat_id, "text": message}
                fallback_resp = requests.post(url, json=fallback, timeout=10)
                if fallback_resp.status_code == 200:
                    return True
                log_protocol("", f"WARN: Telegram fallback도 실패 {fallback_resp.status_code}")
                return False
            if resp.status_code == 429:
                retry_after = int(resp.headers.get("Retry-After", 5))
                time.sleep(retry_after)
                continue
            log_protocol("", f"WARN: Telegram API {resp.status_code}")
        except requests.RequestException as e:
            log_protocol("", f"WARN: Telegram 전송 실패 (attempt {attempt + 1}): {e}")
            time.sleep(2**attempt)

    return False


def dispatch_next_phase(task_id: str) -> dict:
    """chain_manager.py next 직접 호출하여 다음 Phase dispatch"""
    cmd = [
        "python3",
        f"{WORKSPACE_ROOT}/chain_manager.py",
        "next",
        "--task-id",
        task_id,
    ]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        if result.returncode == 0:
            return json.loads(result.stdout)
    except (subprocess.TimeoutExpired, json.JSONDecodeError):
        pass
    return {"action": "error"}


def end_task_timer(task_id: str) -> None:
    """task-timer end 호출"""
    cmd = [
        "python3",
        f"{WORKSPACE_ROOT}/memory/task-timer.py",
        "end",
        task_id,
    ]
    try:
        subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    except subprocess.TimeoutExpired:
        print(
            f"WARNING: task-timer end 호출이 30초 초과로 타임아웃되었습니다 (task_id={task_id}). "
            f"수동으로 실행하세요: python3 {WORKSPACE_ROOT}/memory/task-timer.py end {task_id}",
            file=sys.stderr,
        )


def get_team_id_from_task(task_id: str) -> str | None:
    """task-timers.json에서 task_id로 team_id 조회"""
    try:
        timer_file = Path(f"{WORKSPACE_ROOT}/memory/task-timers.json")
        if timer_file.exists():
            data = json.loads(timer_file.read_text(encoding="utf-8"))
            task_data = data.get("tasks", {}).get(task_id, {})
            return task_data.get("team_id")
    except Exception:
        pass
    return None


def set_bot_idle(team_id: str) -> bool:
    """bot-activity.json의 봇 상태를 idle로 설정

    Returns:
        성공 여부
    """
    try:
        from utils.bot_activity import set_bot_status  # type: ignore[import-untyped]

        set_bot_status(team_id, "idle")
        return True
    except ImportError:
        # utils/bot_activity.py가 없으면 직접 구현
        pass
    except Exception as e:
        print(f"WARNING: set_bot_idle 실패 (team={team_id}): {e}", file=sys.stderr)
        return False

    # 직접 구현 (fallback)
    try:
        bot_activity_file = Path(f"{WORKSPACE_ROOT}/memory/events/bot-activity.json")
        if not bot_activity_file.exists():
            return False

        with open(bot_activity_file, "r", encoding="utf-8") as f:
            data = json.load(f)

        if team_id in data.get("bots", {}):
            utc_now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
            data["bots"][team_id]["status"] = "idle"
            data["bots"][team_id]["since"] = utc_now

            # 원자적 쓰기
            temp_file = bot_activity_file.with_suffix(".tmp")
            with open(temp_file, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            temp_file.replace(bot_activity_file)
            return True
    except Exception as e:
        print(f"WARNING: set_bot_idle 직접 구현 실패 (team={team_id}): {e}", file=sys.stderr)
        return False

    return False


def main() -> None:
    parser = argparse.ArgumentParser(description="팀장 → 아누 완료 통보 (체인 인식)")
    parser.add_argument("task_id", help="작업 ID (예: task-381.1)")
    parser.add_argument(
        "--chat-id",
        default=os.environ.get("COKACDIR_CHAT_ID", "6937032012"),
    )
    parser.add_argument(
        "--anu-key",
        default=None,
    )
    args = parser.parse_args()

    # ANU_KEY: 호환성 유지 (Telegram API 직접 호출에는 미사용)
    anu_key = args.anu_key or os.environ.get("COKACDIR_KEY_ANU", "")

    # todo sync: task 완료 시 todo.json 업데이트
    if sync_task_completion:
        try:
            sync_result = sync_task_completion(args.task_id)
            if sync_result.get("updated"):
                print(f"[TODO] {sync_result['updated']}건 업데이트: {sync_result.get('details', '')}")
        except Exception as e:
            print(f"WARNING: todo sync 실패: {e}", file=sys.stderr)

    # 1. 체인 소속 여부 확인
    chain_info = check_chain_status(args.task_id)

    # 2. 분기
    if chain_info.get("in_chain") and not chain_info.get("is_last"):
        # (b) 체인 중간 Phase → 아누 세션 안 깨움
        print(f"[CHAIN] {args.task_id}: 체인 중간 Phase (chain={chain_info.get('chain_id')})")

        log_protocol(
            args.task_id,
            f"chain middle dispatch (chain={chain_info.get('chain_id')})",
        )

        # chain_manager.py next 직접 호출 (다음 Phase dispatch)
        dispatch_result = dispatch_next_phase(args.task_id)
        print(f"[CHAIN] dispatch 결과: {json.dumps(dispatch_result, ensure_ascii=False)}")

        # dispatch.py 실제 호출
        if dispatch_result.get("action") == "dispatch":
            task_file = dispatch_result.get("task_file")
            team = dispatch_result.get("team")
            level = dispatch_result.get("level", "normal")
            next_task_id = dispatch_result.get("task_id")
            if task_file and team:
                try:
                    _validate_dispatch_args(team, task_file, level, next_task_id)
                except ValueError:
                    log_protocol(args.task_id, "dispatch 인자 검증 실패 (셸 인젝션 방지)")
                    return

                # 환경변수 로드 및 병합 (source .env.keys 대체)
                env_keys = load_env_keys()
                merged_env = {**os.environ, **env_keys}

                # shell=False 리스트 방식으로 호출 (셸 인젝션 방지)
                dispatch_argv = [
                    "python3",
                    f"{WORKSPACE_ROOT}/dispatch.py",
                    "--team",
                    team,
                    "--task-file",
                    task_file,
                    "--level",
                    level,
                ]
                if next_task_id:
                    dispatch_argv.extend(["--task-id", next_task_id])

                try:
                    result = subprocess.run(
                        dispatch_argv,
                        capture_output=True,
                        text=True,
                        timeout=60,
                        env=merged_env,
                    )
                    if result.returncode != 0:
                        log_protocol(
                            args.task_id,
                            "dispatch 실패",
                        )
                        print(
                            f"ERROR: dispatch 실패: {result.stderr}",
                            file=sys.stderr,
                        )
                    else:
                        print(f"[CHAIN] dispatch.py 호출 성공: team={team}")
                except subprocess.TimeoutExpired:
                    log_protocol(
                        args.task_id,
                        "dispatch 타임아웃",
                    )
                    print(
                        "ERROR: dispatch.py 타임아웃",
                        file=sys.stderr,
                    )
        else:
            pass  # action != "dispatch"이면 print만 하고 넘어감

        # 텔레그램 알림만 전송
        chain_id = chain_info.get("chain_id", "unknown")
        _report_path = Path(f"{WORKSPACE_ROOT}/memory/reports/{args.task_id}.md")
        _done_path = Path(f"{WORKSPACE_ROOT}/memory/events/{args.task_id}.done")
        _done_data = None
        if _done_path.exists():
            try:
                _done_data = json.loads(_done_path.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                pass
        message = _format_notification_message(args.task_id, _report_path, _done_data)
        # 체인 정보 추가
        message = message.replace(
            f"**{args.task_id} 완료 보고**",
            f"**{args.task_id} 완료 보고** (chain: {chain_id}). 다음 Phase 자동 위임됨.",
            1,
        )
        _save_completion_message(args.task_id, message)
        if send_telegram_notification(args.chat_id, message):
            notified_path = Path(f"{WORKSPACE_ROOT}/memory/events/{args.task_id}.done.notified")
            notified_path.parent.mkdir(parents=True, exist_ok=True)
            try:
                fd = os.open(str(notified_path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
                os.close(fd)
                log_protocol(args.task_id, ".done.notified 마커 생성")
            except FileExistsError:
                log_protocol(args.task_id, ".done.notified 마커 이미 존재")

    else:
        # (a) 체인 아님 OR 마지막 Phase → 텔레그램 텍스트 알림만 전송

        if chain_info.get("in_chain"):
            print(f"[CHAIN] {args.task_id}: 체인 마지막 Phase → 텔레그램 알림")
            log_protocol(
                args.task_id,
                f"chain last telegram notify (chain={chain_info.get('chain_id')})",
            )
        else:
            print(f"[NOTIFY] {args.task_id}: 일반 작업 → 텔레그램 알림")
            log_protocol(args.task_id, "independent task telegram notify")

        _report_path = Path(f"{WORKSPACE_ROOT}/memory/reports/{args.task_id}.md")
        _done_path = Path(f"{WORKSPACE_ROOT}/memory/events/{args.task_id}.done")
        _done_data = None
        if _done_path.exists():
            try:
                _done_data = json.loads(_done_path.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                pass
        message = _format_notification_message(args.task_id, _report_path, _done_data)
        _save_completion_message(args.task_id, message)
        if send_telegram_notification(args.chat_id, message):
            notified_path = Path(f"{WORKSPACE_ROOT}/memory/events/{args.task_id}.done.notified")
            notified_path.parent.mkdir(parents=True, exist_ok=True)
            try:
                fd = os.open(str(notified_path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
                os.close(fd)
                log_protocol(args.task_id, ".done.notified 마커 생성")
            except FileExistsError:
                log_protocol(args.task_id, ".done.notified 마커 이미 존재")

    # 봇 상태를 "idle"로 설정 (모든 경로 공통)
    team_id = get_team_id_from_task(args.task_id)
    if team_id:
        try:
            if set_bot_idle(team_id):
                log_protocol(args.task_id, f"bot idle 전환 성공 (team={team_id})")
            else:
                log_protocol(args.task_id, f"bot idle 전환 실패 (team={team_id})")
                print(f"WARNING: bot idle 전환 실패 (team={team_id})", file=sys.stderr)
        except Exception as e:
            log_protocol(args.task_id, f"bot idle 전환 예외: {e}")
            print(f"WARNING: bot idle 전환 예외 (team={team_id}): {e}", file=sys.stderr)
    else:
        log_protocol(args.task_id, "team_id 없음, bot idle 전환 스킵")


if __name__ == "__main__":
    main()
