from __future__ import annotations

import json
import os
from datetime import datetime, timezone
from typing import Any

from persistence import MUGI_LOGS_DIR, secure_atomic_write
from tool_policy import context_from_prefs_and_flags
from tools import get_tools
from utils.skill_lifecycle import acquire_lifecycle_mutex, release_lifecycle_mutex

_STATE_FILE = ".curator_state.json"
_DEFAULT_INTERVAL_HOURS = 24 * 7


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _skills_base() -> str:
    from core import get_workspace_path

    workspace = get_workspace_path()
    base = os.path.join(workspace, "skills")
    os.makedirs(base, exist_ok=True)
    return base


def _state_path() -> str:
    return os.path.join(_skills_base(), _STATE_FILE)


def _read_state() -> dict[str, Any]:
    try:
        with open(_state_path(), "r", encoding="utf-8") as f:
            data = json.load(f)
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}


def _write_state(state: dict[str, Any]) -> None:
    secure_atomic_write(_state_path(), json.dumps(state, indent=2, ensure_ascii=True))


def _log_event(event: dict[str, Any]) -> None:
    try:
        os.makedirs(MUGI_LOGS_DIR, exist_ok=True)
        line = json.dumps(event, ensure_ascii=True) + "\n"
        with open(os.path.join(MUGI_LOGS_DIR, "skill_curator.log"), "a", encoding="utf-8") as f:
            f.write(line)
    except Exception:
        pass


def _pending_skill_names() -> set[str]:
    try:
        from evolution.pending_store import list_proposals_by_status

        pending = list_proposals_by_status("pending")
        return {str(p.get("skill_name") or "").strip() for p in pending if str(p.get("skill_name") or "").strip()}
    except Exception:
        return set()


def _user_skill_names() -> set[str]:
    base = _skills_base()
    out: set[str] = set()
    try:
        for name in os.listdir(base):
            if not name or name.startswith("."):
                continue
            if os.path.isfile(os.path.join(base, name, "SKILL.md")):
                out.add(name)
    except Exception:
        return set()
    return out


def _text_evolution_running() -> bool:
    try:
        from evolution.skill_gepa import get_text_evolution_status

        status = get_text_evolution_status() or {}
        return bool(status.get("running"))
    except Exception:
        return False


def _build_tools_override() -> list[Any]:
    allowed = {"skills_list", "skill_view", "skill_manage", "read_file"}
    return [t for t in get_tools(include_media=False, include_cli=False) if getattr(t, "name", None) in allowed]


def _write_report(content: str) -> str:
    os.makedirs(MUGI_LOGS_DIR, exist_ok=True)
    ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    path = os.path.join(MUGI_LOGS_DIR, f"skill_curator_{ts}.md")
    secure_atomic_write(path, content or "")
    return path


def maybe_run_skill_curator(*, app_prefs: dict[str, Any] | None = None, trigger: str = "idle_tick") -> dict[str, Any]:
    prefs = app_prefs or {}
    if not bool(prefs.get("skills_curator_enabled", False)):
        return {"ok": False, "reason": "disabled"}
    if bool(prefs.get("skills_curator_paused", False)):
        return {"ok": False, "reason": "paused"}
    if bool(prefs.get("plan_mode_enabled", False)):
        return {"ok": False, "reason": "plan_mode"}
    if _text_evolution_running():
        return {"ok": False, "reason": "text_evolution_running"}

    interval_hours = max(1, int(prefs.get("skills_curator_interval_hours", _DEFAULT_INTERVAL_HOURS)))
    state = _read_state()
    last = state.get("last_run_at")
    if isinstance(last, str):
        try:
            last_dt = datetime.fromisoformat(last.replace("Z", "+00:00"))
            delta = datetime.now(timezone.utc) - last_dt
            if delta.total_seconds() < (interval_hours * 3600):
                return {"ok": False, "reason": "interval"}
        except Exception:
            pass

    pending_names = _pending_skill_names()
    if pending_names.intersection(_user_skill_names()):
        return {"ok": False, "reason": "pending_conflict"}

    if not acquire_lifecycle_mutex(blocking=False):
        return {"ok": False, "reason": "lifecycle_busy"}

    result: dict[str, Any] = {}
    try:
        from agent_loop import run_raw_agent

        output_parts: list[str] = []

        def _on_finished(text: str) -> None:
            output_parts.append(text or "")

        tools_override = _build_tools_override()
        policy_context = context_from_prefs_and_flags(
            plan_mode=False,
            background_automation=True,
            unattended_external=False,
            subagent_mode=True,
            idle_tick=(trigger == "idle_tick"),
        )
        dry_run = bool(prefs.get("skills_curator_dry_run", False))
        prompt = (
            "You are the skills curator. Consolidate overlapping user workflow skills into cleaner umbrella workflows.\n"
            "Constraints:\n"
            "- Only use skills_list, skill_view, skill_manage, and read_file.\n"
            "- Skip bundled skills.\n"
            "- Prefer creating/patching umbrella skills and then deleting redundant leaf skills when safe.\n"
            "- Keep frontmatter valid.\n"
            "- If no consolidation is needed, say so.\n"
        )
        if dry_run:
            prompt += (
                "- DRY RUN: do not mutate skills. Provide a concise action plan with exact skill names and changes.\n"
            )
        run_raw_agent(
            messages=[{"role": "user", "content": prompt}],
            thread_id=f"skill_curator:{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}",
            on_chunk=lambda _chunk: None,
            on_finished=_on_finished,
            on_error=lambda _msg: None,
            on_log=None,
            on_status=None,
            stop_check=lambda: False,
            research_mode=False,
            has_media_attachment=False,
            include_cli=False,
            subagent_mode=True,
            tools_override=tools_override,
            policy_context=policy_context,
        )
        output = "\n".join([p for p in output_parts if p]).strip()
        report_path = _write_report(output)
        result = {
            "ok": True,
            "dry_run": dry_run,
            "report_path": report_path,
            "summary": output[:500],
        }
        _write_state(
            {
                "last_run_at": _now_iso(),
                "paused": bool(prefs.get("skills_curator_paused", False)),
                "last_report_path": report_path,
                "last_summary": output[:1000],
                "last_result": "ok",
            }
        )
        _log_event({"ts": _now_iso(), "trigger": trigger, "result": "ok", "report_path": report_path})
        return result
    except Exception as exc:
        result = {"ok": False, "reason": "error", "error": str(exc)}
        _write_state(
            {
                "last_run_at": _now_iso(),
                "paused": bool(prefs.get("skills_curator_paused", False)),
                "last_result": "error",
                "last_error": str(exc)[:500],
            }
        )
        _log_event({"ts": _now_iso(), "trigger": trigger, "result": "error", "error": str(exc)[:500]})
        return result
    finally:
        release_lifecycle_mutex()
