"""
Strategic Goal Synthesis,  autonomous long-term goal generation.

Instead of just executing human-given goals, the agent surveys its
accumulated wisdom, world state, and performance trends to autonomously
propose and prioritize strategic goals.

Key ideas:
  - **Wisdom-driven**: uses ultra-LTM patterns + self-benchmark scores
  - **Proactive gap analysis**: identifies capability gaps and creates goals to close them
  - **Risk-weighted**: each proposed goal has a risk score and expected impact
  - **De-duplication**: avoids re-proposing goals that are active or recently completed
  - **LLM-assisted**: the LLM proposes goals given the full agent context

Persistence: ``goal_synthesis_state.json`` in *data_dir*.
"""

from __future__ import annotations

import hashlib
import json
import logging
import time
from dataclasses import dataclass, field, asdict
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

_GOAL_SYNTH_SYSTEM = """You are a strategic planning engine for an autonomous AI agent.
Given the agent's current state (performance scores, capability gaps, recent patterns,
accumulated wisdom), propose 2-5 strategic goals the agent should pursue.

Output ONLY valid JSON,  an array of objects:
[
  {
    "goal": "concise goal description (5-20 words)",
    "rationale": "why this goal matters (1-2 sentences)",
    "category": "capability|efficiency|reliability|knowledge|social|maintenance",
    "priority": 1 to 10,
    "risk": 0.0 to 1.0,
    "expected_impact": 0.0 to 1.0,
    "estimated_ticks": 10 to 500
  }
]

Rules:
- Goals should be specific and actionable (not vague aspirations)
- Prioritize goals that address identified weaknesses
- Balance short-term improvements with long-term capability building
- Risk = probability of failure (0=safe, 1=risky)
- Expected impact = how much this improves agent autonomy (0=minimal, 1=transformative)
- Category types: capability (new skill), efficiency (do things faster),
  reliability (reduce errors), knowledge (learn domain), social (improve user relations),
  maintenance (fix/cleanup)"""


@dataclass
class SynthesizedGoal:
    """A goal generated by the synthesis engine."""

    goal_id: str
    description: str
    rationale: str
    category: str
    priority: int
    risk: float
    expected_impact: float
    estimated_ticks: int
    status: str = "proposed"  # proposed, accepted, rejected, completed
    created_at: str = ""
    tick_created: int = 0

    def __post_init__(self):
        if not self.created_at:
            self.created_at = datetime.now().isoformat()


class GoalSynthesis:
    """Autonomous strategic goal generation engine."""

    def __init__(
        self,
        data_dir: Path,
        synthesize_interval: int = 50,
        max_proposed: int = 20,
    ):
        self._data_dir = Path(data_dir)
        self._data_dir.mkdir(parents=True, exist_ok=True)
        self._state_file = self._data_dir / "goal_synthesis_state.json"

        self._synthesize_interval = synthesize_interval
        self._max_proposed = max_proposed

        self._goals: Dict[str, SynthesizedGoal] = {}
        self._last_synth_tick: int = 0
        self._total_synthesized: int = 0
        self._total_accepted: int = 0
        self._total_rejected: int = 0

        self._load_state()

    async def synthesize(
        self,
        llm: Any,
        tick: int,
        agent_context: Dict[str, Any],
    ) -> List[SynthesizedGoal]:
        """Generate strategic goals based on agent state.

        agent_context should include:
          - benchmark_scores: dict of suite → score
          - autonomy_score: float 0-100
          - wisdom_summary: str
          - recent_patterns: list[str]
          - capability_gaps: list[str]
          - active_goals: list[str]
          - completed_goals: list[str]
        """
        if tick - self._last_synth_tick < self._synthesize_interval:
            return []

        self._last_synth_tick = tick

        try:
            # Build context for LLM
            context_parts = []

            if agent_context.get("autonomy_score"):
                context_parts.append(f"Autonomy score: {agent_context['autonomy_score']}/100")

            if agent_context.get("benchmark_scores"):
                scores = agent_context["benchmark_scores"]
                context_parts.append("Performance scores: " + ", ".join(
                    f"{k}={v}" for k, v in scores.items()
                ))

            if agent_context.get("wisdom_summary"):
                context_parts.append(f"Accumulated wisdom: {agent_context['wisdom_summary'][:500]}")

            if agent_context.get("recent_patterns"):
                context_parts.append("Recent patterns:\n" + "\n".join(
                    f"- {p}" for p in agent_context["recent_patterns"][:10]
                ))

            if agent_context.get("capability_gaps"):
                context_parts.append("Capability gaps:\n" + "\n".join(
                    f"- {g}" for g in agent_context["capability_gaps"][:10]
                ))

            if agent_context.get("active_goals"):
                context_parts.append("Currently active goals:\n" + "\n".join(
                    f"- {g}" for g in agent_context["active_goals"][:10]
                ))

            if agent_context.get("completed_goals"):
                context_parts.append("Recently completed goals:\n" + "\n".join(
                    f"- {g}" for g in agent_context["completed_goals"][:5]
                ))

            context_text = "\n\n".join(context_parts) if context_parts else "No context available."

            messages = [
                {"role": "system", "content": _GOAL_SYNTH_SYSTEM},
                {"role": "user", "content": f"Current agent state:\n\n{context_text}"},
            ]

            result = await llm.invoke_with_tools(messages, [])
            text = result.get("text", "") if isinstance(result, dict) else str(result)

            # Parse JSON
            import re
            text = re.sub(r"<think>[\s\S]*?</think>", "", text).strip()
            if "```" in text:
                m = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", text, re.DOTALL)
                if m:
                    text = m.group(1).strip()

            start = text.find("[")
            end = text.rfind("]")
            if start < 0 or end < 0:
                return []

            items = json.loads(text[start:end + 1])
            new_goals = []

            for item in items:
                if not isinstance(item, dict):
                    continue
                desc = str(item.get("goal", "")).strip()
                if not desc:
                    continue

                # De-duplicate against existing goals
                goal_hash = hashlib.md5(desc.lower().encode()).hexdigest()[:8]
                goal_id = f"synth_{goal_hash}"

                if goal_id in self._goals:
                    continue

                # De-duplicate against active/completed goals
                desc_lower = desc.lower()
                duplicate = False
                for existing in self._goals.values():
                    if existing.status in ("proposed", "accepted"):
                        # Simple word overlap check
                        existing_words = set(existing.description.lower().split())
                        new_words = set(desc_lower.split())
                        overlap = len(existing_words & new_words) / max(len(new_words), 1)
                        if overlap > 0.6:
                            duplicate = True
                            break
                if duplicate:
                    continue

                goal = SynthesizedGoal(
                    goal_id=goal_id,
                    description=desc,
                    rationale=str(item.get("rationale", ""))[:300],
                    category=str(item.get("category", "capability")),
                    priority=int(item.get("priority", 5)),
                    risk=float(item.get("risk", 0.5)),
                    expected_impact=float(item.get("expected_impact", 0.5)),
                    estimated_ticks=int(item.get("estimated_ticks", 50)),
                    tick_created=tick,
                )

                self._goals[goal_id] = goal
                new_goals.append(goal)
                self._total_synthesized += 1

            # Prune old rejected goals
            if len(self._goals) > self._max_proposed * 2:
                old_rejected = [
                    g for g in self._goals.values()
                    if g.status == "rejected"
                ]
                old_rejected.sort(key=lambda g: g.tick_created)
                for g in old_rejected[:len(old_rejected) // 2]:
                    del self._goals[g.goal_id]

            self._save_state()
            return new_goals

        except Exception as e:
            logger.debug(f"Goal synthesis failed: {e}")
            return []

    def accept_goal(self, goal_id: str) -> bool:
        """Mark a synthesized goal as accepted for execution."""
        if goal_id in self._goals:
            self._goals[goal_id].status = "accepted"
            self._total_accepted += 1
            self._save_state()
            return True
        return False

    def reject_goal(self, goal_id: str) -> bool:
        """Mark a synthesized goal as rejected."""
        if goal_id in self._goals:
            self._goals[goal_id].status = "rejected"
            self._total_rejected += 1
            self._save_state()
            return True
        return False

    def get_proposed(self) -> List[Dict[str, Any]]:
        """Return all proposed (pending) goals sorted by priority."""
        proposed = [g for g in self._goals.values() if g.status == "proposed"]
        proposed.sort(key=lambda g: -g.priority)
        return [asdict(g) for g in proposed]

    # ── Stats ─────────────────────────────────────────────────────────────────

    def get_stats(self) -> Dict[str, Any]:
        by_status = {}
        by_category = {}
        for g in self._goals.values():
            by_status[g.status] = by_status.get(g.status, 0) + 1
            by_category[g.category] = by_category.get(g.category, 0) + 1

        proposed = [g for g in self._goals.values() if g.status == "proposed"]
        proposed.sort(key=lambda g: -g.priority)

        return {
            "total_synthesized": self._total_synthesized,
            "total_accepted": self._total_accepted,
            "total_rejected": self._total_rejected,
            "by_status": by_status,
            "by_category": by_category,
            "top_proposed": [
                {
                    "id": g.goal_id,
                    "goal": g.description[:100],
                    "category": g.category,
                    "priority": g.priority,
                    "risk": round(g.risk, 2),
                    "impact": round(g.expected_impact, 2),
                }
                for g in proposed[:8]
            ],
        }

    # ── Persistence ───────────────────────────────────────────────────────────

    def _save_state(self):
        try:
            state = {
                "goals": {k: asdict(v) for k, v in self._goals.items()},
                "last_synth_tick": self._last_synth_tick,
                "total_synthesized": self._total_synthesized,
                "total_accepted": self._total_accepted,
                "total_rejected": self._total_rejected,
            }
            self._state_file.write_text(json.dumps(state, indent=2, default=str))
        except Exception as e:
            logger.debug(f"Goal synthesis save failed: {e}")

    def _load_state(self):
        try:
            if self._state_file.exists():
                data = json.loads(self._state_file.read_text())
                self._last_synth_tick = data.get("last_synth_tick", 0)
                self._total_synthesized = data.get("total_synthesized", 0)
                self._total_accepted = data.get("total_accepted", 0)
                self._total_rejected = data.get("total_rejected", 0)

                for gid, gdata in data.get("goals", {}).items():
                    self._goals[gid] = SynthesizedGoal(**gdata)
        except Exception as e:
            logger.debug(f"Goal synthesis load failed: {e}")
