"""
Gateway — Main NeuralClaw entry point and orchestration engine.

Initializes all cortices, providers, channels, and the neural bus.
Orchestrates the full message lifecycle:

    Channel → Perception → Memory → Reasoning → Action → Response

This is the brain of NeuralClaw.
"""

from __future__ import annotations

import asyncio
import signal
import sys
from typing import Any

from neuralclaw.bus.neural_bus import EventType, NeuralBus
from neuralclaw.bus.telemetry import Telemetry
from neuralclaw.channels.protocol import ChannelAdapter, ChannelMessage
from neuralclaw.config import (
    NeuralClawConfig,
    ensure_dirs,
    get_api_key,
    load_config,
)
from neuralclaw.cortex.action.audit import AuditLogger
from neuralclaw.cortex.action.capabilities import CapabilityVerifier
from neuralclaw.cortex.memory.episodic import EpisodicMemory
from neuralclaw.cortex.memory.metabolism import MemoryMetabolism
from neuralclaw.cortex.memory.procedural import ProceduralMemory
from neuralclaw.cortex.memory.retrieval import MemoryRetriever
from neuralclaw.cortex.memory.semantic import SemanticMemory
from neuralclaw.cortex.perception.classifier import IntentClassifier
from neuralclaw.cortex.perception.intake import ChannelType, PerceptionIntake, Signal
from neuralclaw.cortex.perception.threat_screen import ThreatScreener
from neuralclaw.cortex.reasoning.deliberate import DeliberativeReasoner
from neuralclaw.cortex.reasoning.fast_path import FastPathReasoner
from neuralclaw.cortex.reasoning.reflective import ReflectiveReasoner
from neuralclaw.cortex.reasoning.meta import MetaCognitive
from neuralclaw.cortex.evolution.calibrator import BehavioralCalibrator
from neuralclaw.cortex.evolution.distiller import ExperienceDistiller
from neuralclaw.cortex.evolution.synthesizer import SkillSynthesizer
from neuralclaw.dashboard import Dashboard
from neuralclaw.providers.router import LLMProvider, ProviderRouter
from neuralclaw.skills.registry import SkillRegistry
from neuralclaw.swarm.delegation import DelegationChain, DelegationPolicy
from neuralclaw.swarm.consensus import ConsensusProtocol
from neuralclaw.swarm.mesh import AgentMesh


# ---------------------------------------------------------------------------
# Gateway
# ---------------------------------------------------------------------------

class NeuralClawGateway:
    """
    Main orchestration engine — the 'brain' of NeuralClaw.

    Wires together all cortices, providers, and channels into a
    cohesive cognitive pipeline.
    """

    def __init__(self, config: NeuralClawConfig | None = None) -> None:
        self._config = config or load_config()
        self._running = False

        # Neural bus
        self._bus = NeuralBus()
        self._telemetry = Telemetry(
            log_to_stdout=self._config.telemetry_stdout,
        )
        self._bus.subscribe_all(self._telemetry.handle_event)

        # Perception cortex
        self._intake = PerceptionIntake(self._bus)
        self._classifier = IntentClassifier(self._bus)
        self._threat_screener = ThreatScreener(
            bus=self._bus,
            threat_threshold=self._config.security.threat_threshold,
            block_threshold=self._config.security.block_threshold,
        )

        # Memory cortex
        self._episodic = EpisodicMemory(self._config.memory.db_path)
        self._semantic = SemanticMemory(self._config.memory.db_path)
        self._retriever = MemoryRetriever(
            self._episodic, self._semantic, self._bus,
            max_episodes=self._config.memory.max_episodic_results,
            max_facts=self._config.memory.max_semantic_results,
        )

        # Reasoning cortex
        self._fast_path = FastPathReasoner(self._bus, self._config.name)
        self._deliberate = DeliberativeReasoner(self._bus, self._config.persona)
        self._reflective = ReflectiveReasoner(self._bus, self._deliberate)

        # Action cortex
        self._capability_verifier = CapabilityVerifier(
            bus=self._bus,
            allow_shell=self._config.security.allow_shell_execution,
        )
        self._audit = AuditLogger()

        # Phase 2: Procedural memory + metabolism
        self._procedural = ProceduralMemory(self._config.memory.db_path, self._bus)
        self._metabolism = MemoryMetabolism(
            self._episodic, self._semantic, self._bus,
        )

        # Phase 2: Evolution cortex
        self._calibrator = BehavioralCalibrator(bus=self._bus)
        self._distiller = ExperienceDistiller(
            self._episodic, self._semantic, self._procedural, self._bus,
        )
        self._synthesizer = SkillSynthesizer(bus=self._bus)

        # Phase 3: Meta-cognitive reasoning
        self._meta_cognitive = MetaCognitive(bus=self._bus)

        # Phase 3: Swarm
        self._delegation = DelegationChain(bus=self._bus)
        self._consensus = ConsensusProtocol(self._delegation, bus=self._bus)
        self._mesh = AgentMesh(bus=self._bus)

        # Phase 3: Dashboard
        self._dashboard = Dashboard()

        # Skills
        self._skills = SkillRegistry()

        # Channels
        self._channels: dict[str, ChannelAdapter] = {}

        # Conversation history (per channel_id)
        self._history: dict[str, list[dict[str, str]]] = {}

        # Provider
        self._provider: ProviderRouter | None = None

    async def initialize(self) -> None:
        """Initialize all subsystems."""
        ensure_dirs()

        # Initialize memory databases
        await self._episodic.initialize()
        await self._semantic.initialize()
        await self._procedural.initialize()

        # Initialize evolution cortex
        await self._calibrator.initialize()

        # Load skills
        self._skills.load_builtins()

        # Initialize LLM provider
        self._provider = self._build_provider()
        if self._provider:
            self._deliberate.set_provider(self._provider)
            self._synthesizer.set_provider(self._provider)

        # Phase 3: Wire dashboard stats/agents providers
        self._dashboard.set_stats_provider(self._get_dashboard_stats)
        self._dashboard.set_agents_provider(self._get_dashboard_agents)

        # Start neural bus
        await self._bus.start()

    def _build_provider(self) -> ProviderRouter | None:
        """Build the provider router from config."""
        providers: list[LLMProvider] = []
        primary: LLMProvider | None = None

        cfg = self._config

        # Build all configured providers
        provider_builders = {
            "openai": self._build_openai,
            "anthropic": self._build_anthropic,
            "openrouter": self._build_openrouter,
            "local": self._build_local,
        }

        if cfg.primary_provider:
            builder = provider_builders.get(cfg.primary_provider.name)
            if builder:
                p = builder(cfg.primary_provider)
                if p:
                    primary = p

        for fp in cfg.fallback_providers:
            builder = provider_builders.get(fp.name)
            if builder:
                p = builder(fp)
                if p:
                    providers.append(p)

        if not primary:
            # Try to find any available provider
            for name, builder in provider_builders.items():
                from neuralclaw.config import ProviderConfig
                p = builder(ProviderConfig(name=name, model="", base_url=""))
                if p:
                    primary = p
                    break

        if not primary:
            return None

        return ProviderRouter(primary=primary, fallbacks=providers)

    def _build_openai(self, cfg: Any) -> LLMProvider | None:
        key = get_api_key("openai")
        if not key:
            return None
        from neuralclaw.providers.openai import OpenAIProvider
        return OpenAIProvider(api_key=key, model=cfg.model or "gpt-4o", base_url=cfg.base_url or "https://api.openai.com/v1")

    def _build_anthropic(self, cfg: Any) -> LLMProvider | None:
        key = get_api_key("anthropic")
        if not key:
            return None
        from neuralclaw.providers.anthropic import AnthropicProvider
        return AnthropicProvider(api_key=key, model=cfg.model or "claude-sonnet-4-20250514", base_url=cfg.base_url or "https://api.anthropic.com")

    def _build_openrouter(self, cfg: Any) -> LLMProvider | None:
        key = get_api_key("openrouter")
        if not key:
            return None
        from neuralclaw.providers.openrouter import OpenRouterProvider
        return OpenRouterProvider(api_key=key, model=cfg.model or "anthropic/claude-sonnet-4-20250514")

    def _build_local(self, cfg: Any) -> LLMProvider | None:
        from neuralclaw.providers.local import LocalProvider
        return LocalProvider(model=cfg.model or "llama3", base_url=cfg.base_url or "http://localhost:11434/v1")

    # -- Channel management -------------------------------------------------

    def add_channel(self, adapter: ChannelAdapter) -> None:
        """Register a channel adapter."""
        adapter.on_message(self._on_channel_message)
        self._channels[adapter.name] = adapter

    async def _start_channels(self) -> None:
        """Start all registered channel adapters."""
        for name, adapter in self._channels.items():
            try:
                await adapter.start()
                print(f"[Gateway] Channel registered: {name}")
            except Exception as e:
                print(f"[Gateway] Channel '{name}' start error: {e} (will retry in background)")

    async def _stop_channels(self) -> None:
        """Stop all channel adapters."""
        for name, adapter in self._channels.items():
            try:
                await adapter.stop()
            except Exception:
                pass

    # -- Message lifecycle --------------------------------------------------

    async def _on_channel_message(self, msg: ChannelMessage) -> None:
        """Handle an incoming message from any channel."""
        try:
            response = await self.process_message(
                content=msg.content,
                author_id=msg.author_id,
                author_name=msg.author_name,
                channel_id=msg.channel_id,
                channel_type_name=self._get_channel_type(msg),
            )

            # Route response back to the correct adapter
            source_channel = self._get_source_adapter(msg)
            if source_channel and source_channel in self._channels:
                try:
                    await self._channels[source_channel].send(msg.channel_id, response)
                except Exception as e:
                    print(f"[Gateway] Failed to send via {source_channel}: {e}")
            else:
                # Fallback: try all channels
                for name, adapter in self._channels.items():
                    try:
                        await adapter.send(msg.channel_id, response)
                        break
                    except Exception:
                        continue

        except Exception as e:
            print(f"[Gateway] Error processing message: {e}")

    async def process_message(
        self,
        content: str,
        author_id: str = "user",
        author_name: str = "User",
        channel_id: str = "cli",
        channel_type_name: str = "CLI",
    ) -> str:
        """
        Process a message through the full cognitive pipeline.

        Channel → Perception → Memory → Reasoning → Action → Response
        """
        # 1. PERCEPTION: Intake
        channel_type = ChannelType[channel_type_name.upper()] if channel_type_name.upper() in ChannelType.__members__ else ChannelType.CLI
        signal = await self._intake.process(
            content=content,
            author_id=author_id,
            author_name=author_name,
            channel_type=channel_type,
            channel_id=channel_id,
        )

        # 2. PERCEPTION: Threat screening
        threat = await self._threat_screener.screen(signal)
        if threat.blocked:
            return "⚠️ I've detected a potentially harmful request and blocked it for safety. If this was a mistake, try rephrasing."

        # 3. PERCEPTION: Intent classification
        intent_result = await self._classifier.classify(signal)

        # 4. MEMORY: Retrieve context
        memory_ctx = await self._retriever.retrieve(content)

        # 5. REASONING: Try fast path first
        fast_result = await self._fast_path.try_fast_path(signal, memory_ctx)
        if fast_result:
            await self._store_interaction(content, fast_result.content, author_name)
            try:
                await self._post_process(content, fast_result.content, author_name)
            except Exception:
                pass
            return fast_result.content

        # 6. REASONING: Check for procedural memory match
        procedures = await self._procedural.find_matching(content)

        # 7. REASONING: Route to reflective or deliberative path
        tools = self._skills.get_all_tools() if self._skills.tool_count > 0 else None
        history = self._history.get(channel_id, [])

        # Add calibrator persona modifiers
        persona_mods = self._calibrator.preferences.to_persona_modifiers()

        if self._reflective.should_reflect(signal, memory_ctx):
            envelope = await self._reflective.reflect(
                signal=signal,
                memory_ctx=memory_ctx,
                tools=tools,
                conversation_history=history[-20:],
            )
        else:
            envelope = await self._deliberate.reason(
                signal=signal,
                memory_ctx=memory_ctx,
                tools=tools,
                conversation_history=history[-20:],
            )

        # 8. RESPONSE: Store in memory and return
        await self._store_interaction(content, envelope.response, author_name)

        # Update conversation history
        if channel_id not in self._history:
            self._history[channel_id] = []
        self._history[channel_id].append({"role": "user", "content": content})
        self._history[channel_id].append({"role": "assistant", "content": envelope.response})

        # Trim history
        if len(self._history[channel_id]) > 40:
            self._history[channel_id] = self._history[channel_id][-40:]

        # Post-process (metabolism, distiller, calibrator) — never block response
        try:
            await self._post_process(content, envelope.response, author_name)
        except Exception as e:
            print(f"[Gateway] Post-process error (non-fatal): {e}")

        # Publish response event — never block response
        try:
            await self._bus.publish(
                EventType.RESPONSE_READY,
                {"content": envelope.response[:200], "confidence": envelope.confidence},
                source="gateway",
            )
        except Exception:
            pass

        return envelope.response

    async def _store_interaction(self, user_msg: str, agent_msg: str, author: str) -> None:
        """Store the interaction in episodic memory."""
        try:
            await self._episodic.store(
                content=f"{author}: {user_msg}",
                source="conversation",
                author=author,
                importance=0.5,
            )
            await self._episodic.store(
                content=f"NeuralClaw: {agent_msg}",
                source="conversation",
                author="NeuralClaw",
                importance=0.4,
            )
        except Exception as e:
            await self._bus.publish(
                EventType.ERROR,
                {"error": f"Memory store failed: {e}", "component": "gateway"},
                source="gateway",
            )

    def _get_channel_type(self, msg: ChannelMessage) -> str:
        """Get the channel type name from a ChannelMessage."""
        if msg.raw:
            raw_module = type(msg.raw).__module__
            if "telegram" in raw_module:
                return "TELEGRAM"
            if "discord" in raw_module:
                return "DISCORD"
            if "slack" in raw_module:
                return "SLACK"
        # Check metadata
        meta = msg.metadata or {}
        if "whatsapp" in str(meta.get("source", "")):
            return "WHATSAPP"
        if "signal" in str(meta.get("source", "")):
            return "SIGNAL"
        if "web" in str(meta.get("source", "")):
            return "CLI"  # Web chat uses CLI channel type
        return "CLI"

    def _get_source_adapter(self, msg: ChannelMessage) -> str | None:
        """Identify which adapter originated this message."""
        if msg.raw:
            raw_module = type(msg.raw).__module__
            if "telegram" in raw_module:
                return "telegram"
            if "discord" in raw_module:
                return "discord"
            if "slack" in raw_module:
                return "slack"
        meta = msg.metadata or {}
        source = str(meta.get("source", ""))
        if "whatsapp" in source:
            return "whatsapp"
        if "signal" in source:
            return "signal"
        if "web" in source:
            return "web"
        return None

    async def _post_process(self, user_msg: str, agent_msg: str, author: str) -> None:
        """Post-processing: tick metabolism/distiller, run calibration."""
        self._metabolism.tick()
        self._distiller.tick()

        # Implicit calibration signal
        await self._calibrator.process_implicit_signal(
            user_msg_length=len(user_msg),
            agent_msg_length=len(agent_msg),
        )

        # Run metabolism cycle if due
        if self._metabolism.should_run:
            try:
                await self._metabolism.run_cycle()
            except Exception as e:
                await self._bus.publish(
                    EventType.ERROR,
                    {"error": f"Metabolism cycle failed: {e}", "component": "metabolism"},
                    source="gateway",
                )

        # Run distillation if due
        if self._distiller.should_distill:
            try:
                await self._distiller.distill()
            except Exception as e:
                await self._bus.publish(
                    EventType.ERROR,
                    {"error": f"Distillation failed: {e}", "component": "distiller"},
                    source="gateway",
                )

        # Phase 3: Meta-cognitive tick + analysis
        self._meta_cognitive.record_interaction(
            category="conversation",
            success=True,
            confidence=0.7,
        )
        if self._meta_cognitive.should_analyze:
            try:
                report = await self._meta_cognitive.analyze()
                self._dashboard.push_trace(
                    "reasoning",
                    f"Meta-cognitive analysis: {report.overall_success_rate:.0%} success, "
                    f"{len(report.capability_gaps)} gaps detected",
                )
            except Exception as e:
                await self._bus.publish(
                    EventType.ERROR,
                    {"error": f"Meta-cognitive analysis failed: {e}", "component": "meta"},
                    source="gateway",
                )

    # -- Lifecycle ----------------------------------------------------------

    async def start(self) -> None:
        """Start the gateway (all channels + bus)."""
        self._running = True
        await self.initialize()
        await self._start_channels()

        print(f"\n🧠 {self._config.name} Gateway is running (Phase 3: Swarm)")
        print(f"   Provider: {self._provider.name if self._provider else 'NONE'}")
        print(f"   Skills: {self._skills.count} ({self._skills.tool_count} tools)")
        print(f"   Channels: {list(self._channels.keys()) or ['none']}")
        print(f"   Evolution: calibrator + distiller + synthesizer")
        print(f"   Swarm: delegation + consensus + mesh")

        # Start dashboard in background
        try:
            await self._dashboard.start()
        except Exception as e:
            print(f"   Dashboard: failed to start ({e})")
        print()

    async def stop(self) -> None:
        """Gracefully stop the gateway."""
        self._running = False
        await self._stop_channels()
        await self._dashboard.stop()
        await self._bus.stop()
        await self._episodic.close()
        await self._semantic.close()
        await self._procedural.close()
        await self._calibrator.close()
        print("\n🧠 NeuralClaw Gateway stopped.")

    def _get_dashboard_stats(self) -> dict[str, Any]:
        """Provide stats for the dashboard."""
        return {
            "provider": self._provider.name if self._provider else "none",
            "interactions": sum(
                r.total for r in self._meta_cognitive._performance.values()
            ),
            "success_rate": self._meta_cognitive.get_performance_summary().get(
                "success_rate", 1.0
            ),
            "skills": self._skills.count,
            "channels": ", ".join(self._channels.keys()) or "none",
        }

    def _get_dashboard_agents(self) -> list[dict[str, Any]]:
        """Provide swarm agent list for the dashboard."""
        return self._mesh.get_mesh_status().get("agents", [])

    async def run_forever(self) -> None:
        """Run the gateway until interrupted."""
        await self.start()

        stop_event = asyncio.Event()

        def _signal_handler() -> None:
            stop_event.set()

        loop = asyncio.get_running_loop()
        for sig in (signal.SIGINT, signal.SIGTERM):
            try:
                loop.add_signal_handler(sig, _signal_handler)
            except NotImplementedError:
                # Windows doesn't support add_signal_handler
                pass

        try:
            await stop_event.wait()
        except KeyboardInterrupt:
            pass
        finally:
            await self.stop()
