from __future__ import annotations

import argparse
import json
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

try:
    from src.tenant_manager import tenant_paths
except ModuleNotFoundError:
    from tenant_manager import tenant_paths


def _load_json(path: Path) -> Dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def _save_json(path: Path, payload: Dict[str, Any]) -> None:
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")


def _set_by_path(payload: Dict[str, Any], target_path: str, value: Any) -> Tuple[Any, Any]:
    nodes = target_path.split(".")
    obj: Any = payload
    for node in nodes[:-1]:
        if node not in obj or not isinstance(obj[node], dict):
            obj[node] = {}
        obj = obj[node]
    key = nodes[-1]
    old_value = obj.get(key)
    obj[key] = value
    return old_value, value


def parse_command(text: str) -> Optional[Tuple[str, Any]]:
    m = re.search(r"单笔风险.*?([0-9]+(?:\.[0-9]+)?)%", text)
    if m:
        return "SET_MAX_RISK_PER_TRADE", float(m.group(1))
    m = re.search(r"日亏损.*?([0-9]+(?:\.[0-9]+)?)%", text)
    if m:
        return "SET_DAILY_LOSS_LIMIT", float(m.group(1))
    m = re.search(r"最小置信度.*?([0-9]+(?:\.[0-9]+)?)", text)
    if m:
        return "SET_MIN_CONFIDENCE", float(m.group(1))
    m = re.search(r"切换.*?(legacy_only|ai_filter|hybrid|ai_primary)", text)
    if m:
        return "SWITCH_DECISION_MODE", m.group(1)
    if "紧急停止" in text or "紧急停机" in text:
        return "EMERGENCY_STOP", True
    return None


def _check_permission(rules: Dict[str, Any], role: str, intent: str) -> bool:
    grants = rules.get("roles", {}).get(role, [])
    if intent in {"SET_MAX_RISK_PER_TRADE", "SET_DAILY_LOSS_LIMIT", "EMERGENCY_STOP"}:
        return "TUNE_HIGH" in grants or "STOP" in grants
    if intent in {"SET_MIN_CONFIDENCE", "SWITCH_DECISION_MODE"}:
        return "TUNE_LOW" in grants
    return False


def apply_chat_command(
    project_root: Path,
    role: str,
    channel: str,
    text: str,
    approved: bool,
    tenant_id: str = "",
) -> Dict[str, Any]:
    rules_path = project_root / "ops/chat_command_rules.json"
    tpaths = tenant_paths(project_root, tenant_id)
    risk_path = tpaths["risk"]
    release_path = tpaths["release"]
    runtime_path = tpaths["runtime"]

    rules = _load_json(rules_path)
    parsed = parse_command(text)
    if parsed is None:
        return {"ok": False, "reason": "UNRECOGNIZED_COMMAND"}
    intent, value = parsed
    if channel not in rules.get("channels", []):
        return {"ok": False, "reason": "CHANNEL_NOT_ALLOWED"}
    if not _check_permission(rules, role, intent):
        return {"ok": False, "reason": "PERMISSION_DENIED"}

    command = next((c for c in rules.get("commands", []) if c.get("intent") == intent), None)
    if command is None:
        return {"ok": False, "reason": "RULE_NOT_FOUND"}
    if command.get("requires_approval") and not approved:
        return {"ok": False, "reason": "APPROVAL_REQUIRED"}

    if "allowed_range" in command:
        lo, hi = command["allowed_range"]
        if not (float(lo) <= float(value) <= float(hi)):
            return {"ok": False, "reason": "VALUE_OUT_OF_RANGE"}
    if "allowed_values" in command and value not in command["allowed_values"]:
        return {"ok": False, "reason": "VALUE_NOT_ALLOWED"}

    target_path = str(command["target_path"])
    if target_path.startswith("risk_governor_rules."):
        payload = _load_json(risk_path)
        old_value, new_value = _set_by_path(payload, target_path.replace("risk_governor_rules.", ""), value)
        _save_json(risk_path, payload)
        target_file = str(risk_path)
    elif target_path.startswith("release_gates."):
        payload = _load_json(release_path)
        old_value, new_value = _set_by_path(payload, target_path.replace("release_gates.", ""), value)
        _save_json(release_path, payload)
        target_file = str(release_path)
    else:
        payload = _load_json(runtime_path) if runtime_path.exists() else {}
        old_value, new_value = _set_by_path(payload, target_path.replace("runtime.", ""), value)
        _save_json(runtime_path, payload)
        target_file = str(runtime_path)

    audit = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "tenant_id": tenant_id or "default",
        "role": role,
        "channel": channel,
        "intent": intent,
        "old_value": old_value,
        "new_value": new_value,
        "approved": approved,
        "target_file": target_file,
    }
    tpaths["audit"].parent.mkdir(parents=True, exist_ok=True)
    with tpaths["audit"].open("a", encoding="utf-8") as f:
        f.write(json.dumps(audit, ensure_ascii=False) + "\n")
    return {"ok": True, "audit": audit}


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--role", required=True)
    parser.add_argument("--channel", required=True)
    parser.add_argument("--text", required=True)
    parser.add_argument("--approved", default="false")
    parser.add_argument("--tenant-id", default="")
    args = parser.parse_args()

    approved = str(args.approved).lower() in {"1", "true", "yes", "on"}
    project_root = Path(__file__).resolve().parents[1]
    result = apply_chat_command(project_root, args.role, args.channel, args.text, approved, args.tenant_id)
    print(json.dumps(result, ensure_ascii=False, indent=2))
    return 0 if result.get("ok") else 1


if __name__ == "__main__":
    raise SystemExit(main())
