"""System Bridge: unifies all AgntSpace modules into the knowledge graph and wiki.

Reads from Journal, Tasks, Goals, Trust, Skills, Agents, Heartbeat, Guardian
and feeds both the unified knowledge graph (SPO triples) and wiki articles.

Runs daily at EOD, and can be triggered manually.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from agntspace.infra.timezone import local_today

if TYPE_CHECKING:
    from agntspace.automation.heartbeat import Heartbeat
    from agntspace.coach.service import CoachService
    from agntspace.journal.service import JournalService
    from agntspace.kb.service import KnowledgeBaseService
    from agntspace.memory.graph import KnowledgeGraph
    from agntspace.projects.service import ProjectService
    from agntspace.security.guardian import GuardianService
    from agntspace.skills.loader import SkillLoader
    from agntspace.tasks.service import TaskService
    from agntspace.trust.service import TrustService
    from agntspace.vault.reader import VaultReader
    from agntspace.vault.writer import VaultWriter

log = logging.getLogger(__name__)


class SystemBridge:
    """Bridges all AgntSpace modules into the unified knowledge graph and wiki."""

    def __init__(
        self,
        graph: KnowledgeGraph,
        vault_reader: VaultReader,
        vault_writer: VaultWriter,
        kb_service: KnowledgeBaseService | None = None,
        journal: JournalService | None = None,
        task_service: TaskService | None = None,
        coach: CoachService | None = None,
        trust_service: TrustService | None = None,
        skill_loader: SkillLoader | None = None,
        project_service: ProjectService | None = None,
        heartbeat: Heartbeat | None = None,
        guardian: GuardianService | None = None,
    ) -> None:
        self._graph = graph
        self._reader = vault_reader
        self._writer = vault_writer
        self._kb = kb_service
        self._journal = journal
        self._tasks = task_service
        self._coach = coach
        self._trust = trust_service
        self._skills = skill_loader
        self._projects = project_service
        self._heartbeat = heartbeat
        self._guardian = guardian
        self._activity: object | None = None  # injected post-hoc
        self._decisions: object | None = None  # injected post-hoc
        self._contract: object | None = None  # injected post-hoc

    async def sync_all(self, date: str | None = None) -> dict:
        """Run all module syncs. Returns summary of what was synced.

        Wrapped in ``scoped_operation`` so every triple added by the
        sub-syncs lands in one ``system-bridge-*`` correlation chain in
        /decisions. The counter sees the triples via ``note_graph_triple``
        fired from ``KnowledgeGraph.add_triple``.
        """
        if not date:
            date = local_today()

        from agntspace.activity.scoped import scoped_operation

        async with scoped_operation(
            writer=self._writer,
            contract=self._contract,
            decisions=self._decisions,
            activity=self._activity,
            actor="main",
            action_type="system_bridge_sync",
            action_target=f"graph/{date}",
            reason="system_bridge",
            scope_prefix="system-bridge",
            rationale=f"System bridge sync for {date}",
            caller="daily_cycle",
            skill_context=["memory-consolidate"],
        ) as _scope:
            return await self._do_sync_all(date)

    async def _do_sync_all(self, date: str) -> dict:
        """Internal sync body — caller owns the scope."""
        results: dict[str, int] = {}

        results["journal"] = self._sync_journal(date)
        results["tasks"] = self._sync_tasks()
        results["goals"] = self._sync_goals()
        results["projects"] = self._sync_projects()
        results["trust"] = self._sync_trust()
        results["skills"] = self._sync_skills()
        results["agents"] = self._sync_agents()
        results["heartbeat"] = await self._sync_heartbeat()
        results["guardian"] = self._sync_guardian()

        # Save graph
        self._graph.save()

        total = sum(results.values())
        log.info("SystemBridge: synced %d triples across %d modules", total, len([v for v in results.values() if v > 0]))

        return {"date": date, "triples_added": results, "total": total}

    def _sync_journal(self, date: str) -> int:
        """Extract decisions, learnings, and reflections from today's journal into graph + wiki."""
        if not self._journal:
            return 0

        count = 0
        doc = self._journal.get_entry(date)
        if not doc:
            return 0

        content = doc.content

        # Extract decisions
        in_decisions = False
        for line in content.split("\n"):
            stripped = line.strip()
            if "## Decisions" in line or "## decisions" in line:
                in_decisions = True
                continue
            if stripped.startswith("## ") and in_decisions:
                in_decisions = False
            if in_decisions and stripped.startswith("- "):
                decision = stripped[2:].strip()
                if len(decision) > 10:
                    self._graph.add_triple(
                        subject="Wolf", predicate="decided",
                        obj=decision[:200],
                        authority="explicit", source_file=f"journal/user/user-journal_{date}.md",
                        source_date=date, subject_type="person", object_type="literal",
                    )
                    count += 1

        # Extract learnings
        in_learnings = False
        for line in content.split("\n"):
            stripped = line.strip()
            if "## Learnings" in line or "## learnings" in line:
                in_learnings = True
                continue
            if stripped.startswith("## ") and in_learnings:
                in_learnings = False
            if in_learnings and stripped.startswith("- "):
                learning = stripped[2:].strip()
                if len(learning) > 10:
                    self._graph.add_triple(
                        subject="Wolf", predicate="learned",
                        obj=learning[:200],
                        authority="explicit", source_file=f"journal/user/user-journal_{date}.md",
                        source_date=date, subject_type="person", object_type="literal",
                    )
                    count += 1

        return count

    def _sync_tasks(self) -> int:
        """Sync active tasks into graph as triples."""
        if not self._tasks:
            return 0

        count = 0
        today = local_today()

        for task in self._tasks.list_tasks():
            title = task.get("title", "")
            status = task.get("status", "")
            domain = task.get("domain", "")

            if not title:
                continue

            # Task entity
            self._graph.add_entity(title, entity_type="task", authority="system")

            # Status triple
            self._graph.add_triple(
                subject=title, predicate="status_is",
                obj=status, authority="system",
                source_file="tasks", source_date=today,
                subject_type="task", object_type="literal",
            )

            # Domain triple
            if domain:
                self._graph.add_triple(
                    subject=title, predicate="part_of",
                    obj=domain, authority="system",
                    source_file="tasks", source_date=today,
                    subject_type="task", object_type="concept",
                )

            count += 1

        return count

    def _sync_goals(self) -> int:
        """Sync goals into graph."""
        if not self._coach:
            return 0

        count = 0
        today = local_today()

        for goal in self._coach.list_goals():
            title = goal.get("title", "")
            status = goal.get("status", "")
            target = goal.get("target_date", "")

            if not title:
                continue

            self._graph.add_entity(title, entity_type="goal", authority="explicit")

            self._graph.add_triple(
                subject=title, predicate="status_is",
                obj=status, authority="system",
                source_file="goals", source_date=today,
                subject_type="goal", object_type="literal",
            )

            if target:
                self._graph.add_triple(
                    subject=title, predicate="deadline_is",
                    obj=target, authority="explicit",
                    source_file="goals", source_date=today,
                    subject_type="goal", object_type="literal",
                )

            # Detect stalls
            stalls = self._coach.detect_stalls()
            for stall in stalls:
                if stall.get("title") == title:
                    self._graph.add_triple(
                        subject=title, predicate="status_is",
                        obj="stalled", authority="system",
                        source_file="goals", source_date=today,
                        subject_type="goal", object_type="literal",
                    )

            count += 1

        return count

    def _sync_projects(self) -> int:
        """Sync projects with their linked goals and agents into graph."""
        if not self._projects:
            return 0

        count = 0
        today = local_today()

        for proj in self._projects.list_projects():
            title = proj.get("title", "")
            slug = proj.get("slug", "")
            status = proj.get("status", "")

            if not title:
                continue

            self._graph.add_entity(title, entity_type="project", authority="explicit")

            self._graph.add_triple(
                subject=title, predicate="status_is",
                obj=status, authority="system",
                source_file=f"projects/{slug}", source_date=today,
                subject_type="project", object_type="literal",
            )

            # Link goals
            for goal_slug in proj.get("related_goals", []):
                self._graph.add_triple(
                    subject=title, predicate="has_goal",
                    obj=goal_slug.replace("-", " ").title(),
                    authority="explicit",
                    source_file=f"projects/{slug}", source_date=today,
                    subject_type="project", object_type="goal",
                )

            # Link agents (agent works_on project)
            for agent_key in proj.get("assigned_agents", []):
                self._graph.add_triple(
                    subject=agent_key.title(), predicate="works_on",
                    obj=title,
                    authority="explicit",
                    source_file=f"projects/{slug}", source_date=today,
                    subject_type="ai_agent", object_type="project",
                )

            count += 1

        return count

    def _sync_trust(self) -> int:
        """Sync trust levels into graph."""
        if not self._trust:
            return 0

        count = 0
        today = local_today()

        try:
            trust_data = self._trust.get_trust_data()
            for entry in trust_data:
                domain = entry.get("domain", "")
                level = entry.get("level", "")
                if domain and level:
                    self._graph.add_triple(
                        subject="Wolf", predicate="trusts_in",
                        obj=f"{domain}: {level}",
                        authority="system",
                        source_file="trust", source_date=today,
                        subject_type="person", object_type="literal",
                    )
                    count += 1
        except Exception:
            pass

        return count

    def _sync_skills(self) -> int:
        """Sync skill definitions into graph."""
        if not self._skills:
            return 0

        count = 0
        today = local_today()

        for skill in self._skills.skills.values():
            if skill.status != "active":
                continue

            self._graph.add_entity(skill.title, entity_type="skill", authority="system")

            self._graph.add_triple(
                subject=skill.title, predicate="is_a",
                obj="skill", authority="system",
                source_file=f"system/skills/{skill.path}", source_date=today,
                subject_type="skill", object_type="literal",
            )

            if skill.category:
                self._graph.add_triple(
                    subject=skill.title, predicate="part_of",
                    obj=skill.category, authority="system",
                    source_file=f"system/skills/{skill.path}", source_date=today,
                    subject_type="skill", object_type="literal",
                )

            count += 1

        return count

    def _sync_agents(self) -> int:
        """Sync subagent definitions into graph."""
        count = 0
        today = local_today()

        agents_dir = self._reader._resolve("system/agents")
        if not agents_dir.is_dir():
            return 0

        for f in agents_dir.iterdir():
            if not f.is_file() or f.suffix != ".md":
                continue
            try:
                doc = self._reader.read(f"system/agents/{f.name}")
                name = doc.metadata.get("title", f.stem.title())
                role = doc.metadata.get("role", "")
                skills = doc.metadata.get("skills", [])
                self._graph.add_entity(name, entity_type="agent", authority="system")

                self._graph.add_triple(
                    subject=name, predicate="has_role",
                    obj=role, authority="system",
                    source_file=f"system/agents/{f.name}", source_date=today,
                    subject_type="agent", object_type="literal",
                )

                for skill_name in skills:
                    self._graph.add_triple(
                        subject=name, predicate="uses",
                        obj=skill_name, authority="system",
                        source_file=f"system/agents/{f.name}", source_date=today,
                        subject_type="agent", object_type="skill",
                    )

                count += 1
            except Exception:
                continue

        return count

    async def _sync_heartbeat(self) -> int:
        """Sync recent heartbeat deviations into graph."""
        if not self._heartbeat:
            return 0

        count = 0
        today = local_today()

        try:
            deviations = await self._heartbeat.get_deviations(limit=10)
            for dev in deviations:
                details = dev.get("details", "")
                if details:
                    self._graph.add_triple(
                        subject="AgntSpace", predicate="had_deviation",
                        obj=details[:200], authority="system",
                        source_file="heartbeat", source_date=today,
                        subject_type="system", object_type="literal",
                    )
                    count += 1
        except Exception:
            pass

        return count

    def _sync_guardian(self) -> int:
        """Sync guardian security events into graph."""
        if not self._guardian:
            return 0

        count = 0
        today = local_today()

        try:
            # Read blacklist entries as entities
            blacklist = self._guardian.get_blacklist() if hasattr(self._guardian, "get_blacklist") else []
            for entry in blacklist:
                value = entry if isinstance(entry, str) else entry.get("value", "")
                if value:
                    self._graph.add_triple(
                        subject="Guardian", predicate="blocks",
                        obj=value, authority="system",
                        source_file="system/security/blacklist", source_date=today,
                        subject_type="system", object_type="literal",
                    )
                    count += 1
        except Exception:
            pass

        return count
