"""
watchdog.py - monitor.py 死活監視・自動再起動

scraping_results の最終 fetched_at を VPS 内 DB で監視し、
TIMEOUT_MINUTES 分以上更新がなければ monitor.py を kill して再起動する。

Usage:
    python watchdog.py <client_id>
"""

import argparse
import logging
import subprocess
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path

sys.path.append(str(Path(__file__).resolve().parents[1]))
from config.database import execute_query, execute_update

# ---------------------------------------------------------------------------
# 設定
# ---------------------------------------------------------------------------
TIMEOUT_MINUTES = 10    # この分数更新がなければタイムアウト判定
CHECK_INTERVAL  = 300   # 監視間隔（秒）
PYTHON_EXE      = r"C:\apps\ranking_system\venv\Scripts\python.exe"
MONITOR_SCRIPT  = str(Path(__file__).resolve().parent / "monitor.py")

# ---------------------------------------------------------------------------
# ロガー
# ---------------------------------------------------------------------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# DB ヘルパー
# ---------------------------------------------------------------------------
def get_latest_fetched_at(client_id: int) -> datetime | None:
    """scraping_results の最新 fetched_at を返す。データなしは None。"""
    rows = execute_query(
        "SELECT MAX(fetched_at) AS latest FROM scraping_results WHERE client_id = %s",
        (client_id,),
        local=True,
    )
    if not rows or rows[0]["latest"] is None:
        return None
    return rows[0]["latest"]


def log_error(client_id: int, message: str, error_type: str = "monitor_timeout") -> None:
    """error_logs テーブルにエラーを記録する。"""
    try:
        execute_update(
            """
            INSERT INTO error_logs (client_id, media_name, error_type, error_message, occurred_at)
            VALUES (%s, %s, %s, %s, NOW())
            """,
            (client_id, None, error_type, message),
            local=True,
        )
        logger.info("error_logs に記録しました")
    except Exception as e:
        logger.error("error_logs 書き込みエラー: %s", e)


# ---------------------------------------------------------------------------
# プロセス管理（Windows）
# ---------------------------------------------------------------------------
def _find_monitor_pids() -> list[str]:
    """wmic で monitor.py を含むプロセスの PID 一覧を返す。"""
    try:
        result = subprocess.run(
            [
                "wmic", "process", "where",
                "commandline like '%monitor.py%'",
                "get", "processid", "/format:value",
            ],
            capture_output=True, text=True, timeout=10,
        )
        pids = []
        for line in result.stdout.splitlines():
            line = line.strip()
            if line.startswith("ProcessId="):
                pid_str = line.split("=", 1)[1].strip()
                if pid_str.isdigit() and int(pid_str) > 0:
                    pids.append(pid_str)
        return pids
    except Exception as e:
        logger.error("PID 検索エラー: %s", e)
        return []


def kill_monitor() -> None:
    """monitor.py プロセスを PID で特定して kill する。"""
    pids = _find_monitor_pids()
    if not pids:
        logger.warning("monitor.py プロセスが見つかりませんでした")
        return
    for pid in pids:
        try:
            subprocess.run(
                ["taskkill", "/f", "/pid", pid],
                capture_output=True, timeout=10,
            )
            logger.info("monitor.py プロセス (PID=%s) を kill しました", pid)
        except Exception as e:
            logger.error("taskkill エラー (PID=%s): %s", pid, e)


def restart_monitor(client_id: int) -> None:
    """タスクスケジューラ経由で monitor.py を再起動する。フォールバックで直接起動。"""
    task_name = f"RankRadar_Monitor_Client{client_id}"
    try:
        no_window = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
        subprocess.Popen(
            ["schtasks", "/run", "/tn", task_name],
            creationflags=no_window,
        )
        logger.info("タスク '%s' の再起動を依頼しました", task_name)
        return
    except Exception as e:
        logger.error("schtasks 再起動エラー: %s", e)

    # フォールバック: 直接起動
    try:
        subprocess.Popen([PYTHON_EXE, MONITOR_SCRIPT, str(client_id)])
        logger.info("monitor.py を直接起動しました")
    except Exception as e:
        logger.error("直接起動エラー: %s", e)


# ---------------------------------------------------------------------------
# 監視ロジック
# ---------------------------------------------------------------------------
def check_and_restart(client_id: int) -> None:
    latest = get_latest_fetched_at(client_id)
    if latest is None:
        logger.info("scraping_results にデータなし。スキップ")
        return

    elapsed = datetime.now() - latest
    logger.info(
        "最終スクレイピング: %s (%.1f 分前)",
        latest.strftime("%H:%M:%S"),
        elapsed.total_seconds() / 60,
    )

    if elapsed > timedelta(minutes=TIMEOUT_MINUTES):
        msg = f"monitor.py が {TIMEOUT_MINUTES} 分以上応答なし。プロセスを再起動します。"
        logger.warning(msg)
        log_error(client_id, msg)
        kill_monitor()
        time.sleep(3)
        restart_monitor(client_id)
    else:
        logger.info("monitor.py 正常動作中")


# ---------------------------------------------------------------------------
# エントリポイント
# ---------------------------------------------------------------------------
def main() -> None:
    parser = argparse.ArgumentParser(description="monitor.py 死活監視スクリプト")
    parser.add_argument("client_id", type=int, help="監視対象クライアントID")
    args = parser.parse_args()

    logger.info(
        "watchdog 起動 (client_id=%d, タイムアウト=%d分, 監視間隔=%d秒)",
        args.client_id, TIMEOUT_MINUTES, CHECK_INTERVAL,
    )

    while True:
        try:
            check_and_restart(args.client_id)
            time.sleep(CHECK_INTERVAL)
        except KeyboardInterrupt:
            logger.info("watchdog 終了")
            break
        except Exception as e:
            logger.error("watchdog 予期せぬエラー: %s", e)
            log_error(args.client_id, f"watchdog 予期せぬエラー: {e}", error_type="watchdog_error")
            time.sleep(60)


if __name__ == "__main__":
    main()
