from __future__ import annotations
import abc
import asyncio
from typing import Any, Dict, Optional, Set, Callable, Awaitable

from pydantic import BaseModel, Field

from .bus import CognitiveBus
from .events import BusEvent
from .confidence import ConfidenceReport
from .context import ContextObject
from .retry import async_retry
from ..providers.base import Provider, LLMMessage

class AgentConfig(BaseModel):
    agent_id: str
    system_prompt: str = "You are a helpful agent."
    max_concurrency: int = 8
    min_confidence_to_claim: float = 0.0
    retry_attempts: int = 1
    extra: Dict[str, Any] = Field(default_factory=dict)

class BaseAgent(abc.ABC):
    def __init__(self, *, config: AgentConfig, bus: CognitiveBus, provider: Optional[Provider] = None):
        self.config = config
        self.bus = bus
        self.provider = provider
        self._sem = asyncio.Semaphore(config.max_concurrency)

    def subscribe(self) -> None:
        self.bus.subscribe(
            agent_id=self.config.agent_id,
            callback=self._guarded_handle,
            filter_fn=self.filter_event,
            min_confidence=self.config.min_confidence_to_claim,
            topics=self.topics(),
        )

    def topics(self) -> Optional[Set[str]]:
        return None

    def filter_event(self, event: BusEvent) -> bool:
        return True

    async def _guarded_handle(self, event: BusEvent) -> None:
        async with self._sem:
            await self._handle_with_retry(event)

    async def _handle_with_retry(self, event: BusEvent) -> None:
        if self.config.retry_attempts <= 1:
            await self.handle(event)
            return

        @async_retry(attempts=self.config.retry_attempts)
        async def _run() -> None:
            await self.handle(event)

        try:
            await _run()
        except Exception as e:
            # publish failure signal for retrospective learning
            await self.bus.emit(
                topic="signal.failure",
                type="ALERT",
                sender=self.config.agent_id,
                parent_id=event.event_id,
                context=ContextObject(text=f"Agent failed after retries: {e}"),
                confidence=0.0,
                priority=1,
            )

    @abc.abstractmethod
    async def handle(self, event: BusEvent) -> None:
        ...

    async def think(self, user_text: str, *, extra_system: Optional[str] = None, **kwargs: Any) -> str:
        if not self.provider:
            raise RuntimeError(f"{self.config.agent_id} has no provider attached.")
        system = self.config.system_prompt + (("\n" + extra_system) if extra_system else "")
        messages = [LLMMessage(role="system", content=system), LLMMessage(role="user", content=user_text)]
        resp = await self.provider.generate(messages, **kwargs)
        return resp.text

    async def emit_confidence(self, *, parent: BusEvent, score: float, rationale: str = "") -> None:
        await self.bus.emit(
            topic="signal.confidence",
            type="THOUGHT",
            sender=self.config.agent_id,
            parent_id=parent.event_id,
            confidence=score,
            meta={"rationale": rationale, "source_event_topic": parent.topic},
        )

    async def report(self, *, parent: BusEvent, topic: str, text: str, confidence: float = 0.7) -> None:
        await self.bus.emit(
            topic=topic,
            type="RESULT",
            sender=self.config.agent_id,
            parent_id=parent.event_id,
            context=ContextObject(text=text),
            confidence=confidence,
        )

    async def score_fit(self, event: BusEvent) -> ConfidenceReport:
        return ConfidenceReport(score=0.5, rationale="default")
