from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any

import config_manager

from src.auto_governance import run_auto_governance_cycle
from src.evolution_evidence import build_evolution_evidence_report
from src.evolution_coordinator import build_evolution_coordinator_payload
from src.ledger import append_ledger_event
from src.metrics_agg import aggregate_goal_targets
from src.system_bus import update_system_section
from src.code_evolution.orchestrator import run_code_evolution_cycle
from src.data_pipeline.upload_scheduler import run_data_upload_cycle
from src.data_pipeline.monitor import get_pipeline_status, check_and_alert

# ── Strategy Sync State (time-based, not per-cycle) ──────────────
_last_param_sync: float = 0.0
SYNC_INTERVAL: float = 300.0  # 5 minutes

APP_DIR = config_manager.get_app_dir()
LOGS_DIR = config_manager.get_logs_dir()
REPORTS_DIR = APP_DIR / "reports"
AUTONOMY_LATEST_FILE = LOGS_DIR / "autonomy_loop_latest.json"
AUTONOMY_HISTORY_FILE = LOGS_DIR / "autonomy_loop_history.ndjson"


def _load_runtime_rules() -> dict[str, Any]:
    try:
        from src.hermes_bridge import load_runtime_rules

        payload = load_runtime_rules()
        return payload if isinstance(payload, dict) else {}
    except Exception:
        return {}


def _refresh_goal_reports() -> dict[str, Any]:
    try:
        from src.hermes_bridge import refresh_goal_progress_reports

        payload = refresh_goal_progress_reports()
        return payload if isinstance(payload, dict) else {}
    except Exception:
        return {}


def _append_history(path: Path, payload: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "a", encoding="utf-8") as f:
        f.write(json.dumps(payload, ensure_ascii=False) + "\n")


def run_autonomy_loop_cycle(
    project_root: Path | None = None,
    tenant_id: str = "default",
    source: str = "scheduler",
) -> dict[str, Any]:
    root = project_root or APP_DIR
    goal = aggregate_goal_targets(root, tenant_id)
    governance = run_auto_governance_cycle(root, tenant_id, source=f"autonomy_loop:{source}")
    evolution = build_evolution_evidence_report(root, tenant_id, 30)
    goal_reports = _refresh_goal_reports()
    runtime_rules = _load_runtime_rules()

    gates = goal.get("gates", {}) if isinstance(goal.get("gates", {}), dict) else {}
    verdict = (evolution.get("verdict", {}) if isinstance(evolution.get("verdict", {}), dict) else {})
    all_passed = bool(gates.get("all_passed", False))
    coordination = build_evolution_coordinator_payload(
        project_root=root,
        tenant_id=tenant_id,
        source=f"autonomy_loop:{source}",
        external_inputs={
            "goal_gates": gates,
            "evolution_verdict": verdict,
            "auto_governance": governance,
        },
    )
    coord_decision = coordination.get("decision", {}) if isinstance(coordination.get("decision", {}), dict) else {}
    next_action = str(coord_decision.get("autonomyAction", "CONTINUE_COLLECT_EVIDENCE") or "CONTINUE_COLLECT_EVIDENCE")
    notice = str(coord_decision.get("autonomyNotice", "not_significant_keep_collecting") or "not_significant_keep_collecting")

    # ── Code Evolution Cycle ─────────────────────────────────────────
    # When param evolution has plateaued (no suggestions, still poor stats)
    # or evidence shows structural underperformance, try code-level evolution.
    code_ev_result: dict = {}
    if all_passed and next_action in ("CONTINUE_COLLECT_EVIDENCE", "FORCE_BASELINE_GUARD"):
        code_ev_result = run_code_evolution_cycle(
            project_root=root,
            tenant_id=tenant_id,
            source=f"autonomy_loop:{source}",
            evolution_report=evolution,
            evolution_verdict=verdict,
        )
    # ──────────────────────────────────────────────────────────────────

    # ── Learning Brain Scheduler (Ring 2/3 triggers) ──────────────────
    learning_result: dict = {}
    try:
        from src.learning.brain_scheduler import run_learning_cycles
        learning_result = run_learning_cycles()
    except Exception:
        pass
    # ──────────────────────────────────────────────────────────────────

    # ── Demand-Driven Innovation (auto-trigger on performance vacuum) ──
    ddi_result: dict = {}
    if all_passed and evolution_status in ("SIGNIFICANT_UNDERPERFORM", "MIXED_PERFORMANCE"):
        try:
            from src.demand_driven_innovation import DemandDrivenInnovation
            ddi = DemandDrivenInnovation()
            gaps = ddi.analyze_gaps()
            if gaps.get("demand_signal"):
                ddi_result = ddi.innovate()
        except Exception:
            pass
    # ──────────────────────────────────────────────────────────────────

    # ── Data Pipeline Upload Cycle ─────────────────────────────────────
    # Capture, anonymize, and upload new trade events to the central server.
    # Runs every autonomy cycle (~1 min) for near-real-time data collection.
    data_result: dict = {}
    if all_passed:
        try:
            data_result = run_data_upload_cycle(
                project_root=root,
                tenant_id=tenant_id,
                source=f"autonomy_loop:{source}",
            )
            # Fire alerts if thresholds exceeded
            check_and_alert()
        except Exception as e:
            data_result = {"error": str(e), "uploaded": 0, "failed": 0}
    # ────────────────────────────────────────────────────────────────────

    # ── Strategy Sync Cycle ─────────────────────────────────────────────
    # Pull optimized params from server every ~5 min (not every cycle).
    # Only runs when all gates passed so we don't apply changes during cooldown.
    sync_result: dict = {}
    global _last_param_sync
    now_ts = time.time()
    if all_passed and (now_ts - _last_param_sync) >= SYNC_INTERVAL:
        try:
            from src.strategy_sync import sync_params, check_risk_alerts, process_alerts
            sync_result = sync_params()
            alerts = check_risk_alerts()
            if alerts:
                process_alerts(alerts)
        except Exception as e:
            sync_result = {"error": str(e)}
        _last_param_sync = now_ts
    # ────────────────────────────────────────────────────────────────────

    payload = {
        "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "tenant_id": tenant_id or "default",
        "source": source,
        "goal_gates": gates,
        "governance_action": governance.get("action", "NOOP"),
        "governance_reason": governance.get("reason", ""),
        "evolution_verdict": verdict,
        "evolution_coordinator": {
            "state": str((coordination.get("coordinator", {}) or {}).get("state", "")),
            "decision_status": str(coord_decision.get("status", "")),
            "promotion_mode": str(coord_decision.get("promotionMode", "")),
            "promotion_target": coord_decision.get("promotionTarget"),
        },
        "runtime_rules": {
            "auto_governance_enabled": bool(runtime_rules.get("auto_governance_enabled", True)),
            "evolver_auto_apply": bool(runtime_rules.get("evolver_auto_apply", False)),
        },
        "goal_reports_ready": bool(goal_reports),
        "next_action": next_action,
        "notice": notice,
        "code_evolution": {
            "triggered": bool(code_ev_result.get("triggered", False)),
            "trigger_reason": code_ev_result.get("trigger_reason", ""),
            "patches_created": int(code_ev_result.get("patches_created", 0)),
            "patches_passed": int(code_ev_result.get("patches_passed", 0)),
            "patches_failed": int(code_ev_result.get("patches_failed", 0)),
            "active_patch": code_ev_result.get("active_patch"),
        },
        "data_pipeline": {
            "uploaded": int(data_result.get("uploaded", 0)),
            "failed": int(data_result.get("failed", 0)),
            "skipped": int(data_result.get("skipped", 0)),
            "freshness_lag_sec": float(data_result.get("freshness_lag_sec", 0)),
            "queue_depth": int(data_result.get("queue_depth", 0)),
            "network_reachable": bool(data_result.get("network_reachable", False)),
            "errors": data_result.get("errors", []),
        },
        "strategy_sync": {
            "status": sync_result.get("status", "skipped"),
            "applied": bool(sync_result.get("applied", False)),
            "source": sync_result.get("source", "none"),
            "event_count": sync_result.get("event_count", 0),
            "changes": sync_result.get("changes", {}),
        },
    }

    AUTONOMY_LATEST_FILE.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    _append_history(AUTONOMY_HISTORY_FILE, payload)

    append_ledger_event(
        root / "logs" / "chat_tuning_audit.ndjson",
        "autonomy_loop_notice",
        {
            "tenant_id": tenant_id,
            "payload": payload,
        },
    )

    try:
        update_system_section(
            root=root,
            tenant_id=tenant_id,
            section="autonomy_loop",
            patch=payload,
            source="autonomy_loop",
            event_type="autonomy_cycle_completed",
        )
    except Exception:
        pass

    return payload


if __name__ == "__main__":
    result = run_autonomy_loop_cycle(APP_DIR, "default", "manual")
    print(json.dumps(result, ensure_ascii=False))
