"""Connector interface (build plan Section 9.1).

A connector exposes tools that plug straight into the agent loop: it reuses the
same ``ToolSpec`` / ``ToolResult`` / ``ToolContext`` types as built-in tools, so
the runner never needs to know whether a tool came from a connector. Adding a new
connector is just a new module that conforms to this protocol and registers
itself — no core changes.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Protocol, runtime_checkable

from legate.agent.tooling import ToolContext, ToolResult, ToolSpec, validate_args


@dataclass
class ConnectionResult:
    ok: bool
    message: str = ""


class ConnectorConfigError(ValueError):
    """Raised when a connector's config or secrets fail validation."""


@runtime_checkable
class Connector(Protocol):
    type: str
    title: str
    config_schema: dict
    secret_schema: dict

    def validate_config(self, config: dict) -> None: ...

    def validate_secrets(self, secrets: dict) -> None: ...

    def list_tools(self) -> list[ToolSpec]: ...

    async def test_connection(self, config: dict, secrets: dict) -> ConnectionResult: ...

    async def execute(
        self, tool: str, args: dict, config: dict, secrets: dict, ctx: ToolContext
    ) -> ToolResult: ...


class BaseConnector:
    """Convenience base providing schema-driven validation."""

    type: str = ""
    title: str = ""
    config_schema: dict = {"type": "object", "properties": {}}
    secret_schema: dict = {"type": "object", "properties": {}}

    def validate_config(self, config: dict) -> None:
        errors = validate_args(self.config_schema, config)
        if errors:
            raise ConnectorConfigError("; ".join(errors))

    def validate_secrets(self, secrets: dict) -> None:
        errors = validate_args(self.secret_schema, secrets)
        if errors:
            raise ConnectorConfigError("; ".join(errors))

    def list_tools(self) -> list[ToolSpec]:  # pragma: no cover - overridden
        raise NotImplementedError

    async def test_connection(self, config: dict, secrets: dict) -> ConnectionResult:
        return ConnectionResult(ok=True, message="no connectivity check implemented")

    async def execute(
        self, tool: str, args: dict, config: dict, secrets: dict, ctx: ToolContext
    ) -> ToolResult:  # pragma: no cover - overridden
        raise NotImplementedError
