from abc import ABC, abstractmethod
from typing import Optional

from blocks_control_sdk.control.agent_base import LLM, CodingAgentBaseCLI, NotifyMessageArgs, NotifyCompleteArgs, NotifyToolCallArgs, NotifyStartArgs, NotifyResumeArgs, NotifyInternalMessageArgs, NotifyInterruptArgs
from blocks_control_sdk.utils import should_skip_based_on_last_updated_at, BlocksRuntimeConfigKeys


class SandboxPausingError(Exception):
    """Raised when the sandbox monitor transitions to PAUSING on non-VM environments."""
    pass


class AgentActivityProvider(ABC):
    def __init__(self, trigger_alias: str):
        self.trigger_alias = trigger_alias
        self.input = None

    def setup(self, input: dict, llm_provider: LLM) -> None:
        """One-time platform setup (create comments, patch runtime config).
        Override in subclasses; call super().setup() first to store input."""
        self.input = input

    @staticmethod
    def _get_user_messages(messages: list) -> list:
        return [m for m in messages if m.role == "user" and m.type == "message"]

    @abstractmethod
    def on_message(self, notification: NotifyMessageArgs) -> None: ...

    @abstractmethod
    def on_complete(self, notification: NotifyCompleteArgs) -> None: ...

    @abstractmethod
    def on_tool_call(self, notification: NotifyToolCallArgs) -> None: ...

    def on_start(self, notification: NotifyStartArgs) -> None:
        pass

    def on_resume(self, notification: NotifyResumeArgs) -> None:
        pass

    def on_interrupt(self, notification: NotifyInterruptArgs) -> None:
        pass

    def check(self, messages: list) -> None:
        """Called every polling cycle with latest messages.
        Raises SandboxPausingError if the sandbox should shut down."""
        pass

    @staticmethod
    def _prepare_progress_summary(notification: NotifyMessageArgs) -> Optional[str]:
        """Shared throttle + truncate logic. Returns summary string or None to skip."""
        message = notification.message
        status = notification.status

        should_skip = should_skip_based_on_last_updated_at(BlocksRuntimeConfigKeys.INTENDED_OUTCOME_SUMMARY_LAST_UPDATED_AT)

        if message.content and not should_skip and status == CodingAgentBaseCLI.AgentStatus.TURNS_IN_PROGRESS:
            return f"{message.content[:120]}..." if len(message.content) > 120 else message.content

        return None
