#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================================
KIEU STORY - BLAST RADIUS & IMPACT ANALYSIS ENGINE
=============================================================================
Cỗ máy phân tích vùng ảnh hưởng mã nguồn (Two-Tier Domain Engine):
- Quét On-The-Fly AST Graph các file phụ thuộc trong workspace.
- Hỗ trợ cờ --ack mở khóa chỉnh sửa an toàn trong 30 phút.
- Tuân thủ quy chuẩn an toàn Windows (ensure_ascii=True, UTF-8, prunned os.walk).
=============================================================================
"""

import os
import sys
import ast
import json
import time
import argparse
from pathlib import Path
from typing import Dict, List, Set, Any

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
    sys.stderr.reconfigure(encoding="utf-8")

WORKSPACE_ROOT = Path(__file__).resolve().parent.parent.parent
ACK_CACHE_FILE = WORKSPACE_ROOT / ".agents" / "blast_ack_cache.json"
ACK_EXPIRY_SECONDS = 1800  # 30 phút


def _load_ack_cache() -> Dict[str, float]:
    if not ACK_CACHE_FILE.exists():
        return {}
    try:
        with open(ACK_CACHE_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def _save_ack_cache(cache: Dict[str, float]) -> None:
    ACK_CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
    try:
        with open(ACK_CACHE_FILE, "w", encoding="utf-8") as f:
            json.dump(cache, f, ensure_ascii=True, indent=2)
    except Exception as e:
        print(f"[!] Warning: Không thể lưu ack cache: {e}", file=sys.stderr)


def is_acknowledged(target_path: str) -> bool:
    cache = _load_ack_cache()
    norm = os.path.normpath(os.path.abspath(target_path)).lower()
    now = time.time()
    ts = cache.get(norm, 0.0)
    return (now - ts) < ACK_EXPIRY_SECONDS


def record_acknowledgment(target_path: str) -> None:
    cache = _load_ack_cache()
    norm = os.path.normpath(os.path.abspath(target_path)).lower()
    cache[norm] = time.time()
    _save_ack_cache(cache)


def find_ast_dependents(target_path: str) -> List[str]:
    stem = os.path.splitext(os.path.basename(target_path))[0]
    target_abs = os.path.normpath(os.path.abspath(target_path)).lower()
    dependents = []

    for root, dirs, files in os.walk(WORKSPACE_ROOT):
        dirs[:] = [
            d for d in dirs
            if d not in [".git", ".gemini", "node_modules", "venv", ".venv", "__pycache__", "dist", "build", "downloads", "chrome_profile"]
        ]
        for f in files:
            if not f.endswith(".py"):
                continue
            full_p = os.path.join(root, f)
            if os.path.normpath(os.path.abspath(full_p)).lower() == target_abs:
                continue
            try:
                with open(full_p, "r", encoding="utf-8", errors="ignore") as src:
                    content = src.read()
                if stem in content:
                    tree = ast.parse(content, filename=full_p)
                    for node in ast.walk(tree):
                        if isinstance(node, (ast.Import, ast.ImportFrom)):
                            dependents.append(os.path.relpath(full_p, WORKSPACE_ROOT))
                            break
            except Exception:
                continue

    return sorted(list(set(dependents)))


def main():
    parser = argparse.ArgumentParser(description="KieuStory Blast Radius Analyzer")
    parser.add_argument("--target", required=True, help="Đường dẫn file đích cần phân tích")
    parser.add_argument("--format", choices=["text", "json"], default="text", help="Định dạng xuất kết quả")
    parser.add_argument("--ack", action="store_true", help="Ghi nhận và giải phóng quyền chỉnh sửa trong 30 phút")

    args = parser.parse_args()
    target_abs = os.path.normpath(os.path.abspath(args.target))

    dependents = find_ast_dependents(target_abs)
    count = len(dependents)

    if args.ack:
        record_acknowledgment(target_abs)
        if args.format == "json":
            res = {
                "blocked": False,
                "acknowledged": True,
                "target": os.path.basename(target_abs),
                "dependents_count": count,
                "dependents": dependents,
                "reason": f"Acknowledged by Agent. Blast radius unlocked for 30 minutes ({count} dependents)."
            }
            print(json.dumps(res, ensure_ascii=True))
        else:
            print(f"[✓] ĐÃ GHI NHẬN ACK: Mở khóa quyền chỉnh sửa cho '{os.path.basename(target_abs)}' trong 30 phút.")
            print(f"    Tổng số module phụ thuộc: {count}")
            for d in dependents:
                print(f"    - {d}")
        return 0

    acked = is_acknowledged(target_abs)
    if acked:
        blocked = False
        reason = f"Cleared by local project Blast Radius Calculator (Active Ack, {count} dependents)."
    elif count >= 6:
        blocked = True
        reason = f"[BLAST RADIUS BLOCKED] File '{os.path.basename(target_abs)}' có {count} module liên đới. Chạy với --ack để mở khóa."
    else:
        blocked = False
        reason = f"Low blast radius ({count} dependents)."

    if args.format == "json":
        res = {
            "blocked": blocked,
            "acknowledged": acked,
            "target": os.path.basename(target_abs),
            "dependents_count": count,
            "dependents": dependents,
            "reason": reason
        }
        print(json.dumps(res, ensure_ascii=True))
    else:
        status_str = "BLOCKED" if blocked else "CLEARED"
        print(f"[{status_str}] File: {os.path.basename(target_abs)} | Dependents: {count}")
        for d in dependents:
            print(f"  - {d}")
        if blocked:
            print("\n[!] Để mở khóa, hãy chạy lại lệnh kèm cờ --ack.")

    return 0 if not blocked else 1


if __name__ == "__main__":
    sys.exit(main())
