# retoor <retoor@molodetz.nl>

import logging
import os

from devplacepy.services.base import BaseService, ConfigField

from . import config
from .config import (
    DEFAULT_AI_MODEL,
    DEFAULT_AI_URL,
    build_settings,
)
from .cost.tracker import Pricing

logger = logging.getLogger("devii.service")

INSTANCE_ORIGIN_DEFAULT = "http://127.0.0.1:10500"


class DeviiService(BaseService):
    default_enabled = False
    min_interval = 10
    title = "Devii"
    description = (
        "An in-platform agentic assistant exposed as a WebSocket terminal at /devii/ws. "
        "Each signed-in user's Devii operates their own DevPlace account; guests get a "
        "sandbox session. Spend is capped per user and per guest over a rolling 24 hours, "
        "with every turn recorded to a usage ledger and an audit log."
    )
    config_fields = [
        ConfigField(
            config.FIELD_AI_URL,
            "AI URL",
            type="url",
            default=DEFAULT_AI_URL,
            help="OpenAI-compatible chat-completions endpoint Devii reasons with.",
            group="AI",
        ),
        ConfigField(
            config.FIELD_AI_MODEL,
            "AI model",
            type="str",
            default=DEFAULT_AI_MODEL,
            help="Model name sent to the AI endpoint.",
            group="AI",
        ),
        ConfigField(
            config.FIELD_AI_KEY,
            "AI API key",
            type="password",
            default="",
            secret=True,
            help="Defaults to the DEVII_AI_KEY env var, then the gateway's internal key.",
            group="AI",
        ),
        ConfigField(
            "devii_base_url",
            "Platform base URL",
            type="url",
            default="",
            help="Origin Devii drives via each user's API key. Blank uses this instance.",
            group="AI",
        ),
        ConfigField(
            config.FIELD_TIMEOUT,
            "AI request timeout (seconds)",
            type="float",
            default=config.DEFAULT_TIMEOUT_SECONDS,
            minimum=config.MIN_TIMEOUT_SECONDS,
            help="Read timeout for each AI chat-completions call. Must be at least as large "
            "as the gateway's upstream timeout or large prompts abort early. Minimum five minutes.",
            group="Reliability",
        ),
        ConfigField(
            config.FIELD_FETCH_TIMEOUT,
            "Web fetch timeout (seconds)",
            type="float",
            default=config.DEFAULT_FETCH_TIMEOUT_SECONDS,
            minimum=config.MIN_TIMEOUT_SECONDS,
            help="Read timeout for the fetch tool when Devii retrieves a URL. Minimum five minutes.",
            group="Reliability",
        ),
        ConfigField(
            config.FIELD_PLAN_REQUIRED,
            "Require plan step",
            type="bool",
            default=True,
            help="Force a plan() call before any tool use.",
            group="Agent",
        ),
        ConfigField(
            config.FIELD_VERIFY_REQUIRED,
            "Require verify step",
            type="bool",
            default=True,
            help="Force a verify() call after mutating actions.",
            group="Agent",
        ),
        ConfigField(
            config.FIELD_MAX_ITERATIONS,
            "Max tool iterations",
            type="int",
            default=40,
            minimum=1,
            maximum=200,
            help="Upper bound on tool-loop iterations per turn.",
            group="Agent",
        ),
        ConfigField(
            config.FIELD_ALLOW_EVAL,
            "Allow JavaScript execution",
            type="bool",
            default=True,
            help="Let Devii run JavaScript in the user's own browser (run_js tool). "
            "Other client tools (navigate, reload, highlight, toast, context) are unaffected.",
            group="Agent",
        ),
        ConfigField(
            config.FIELD_BROWSER_CONTROL,
            "Allow browser control",
            type="bool",
            default=True,
            help="Let Devii drive the user's own browser with structured tools "
            "(discover elements, click, fill, submit, wait, sequences). Independent of "
            "raw JavaScript execution; read-only tools (context, discover, read) are unaffected.",
            group="Agent",
        ),
        ConfigField(
            config.FIELD_USER_DAILY_USD,
            "Max USD per user / 24h",
            type="float",
            default=1.0,
            minimum=0,
            help="Rolling 24-hour spend cap for a signed-in user.",
            group="Limits",
        ),
        ConfigField(
            config.FIELD_GUEST_DAILY_USD,
            "Max USD per guest / 24h",
            type="float",
            default=0.05,
            minimum=0,
            help="Rolling 24-hour spend cap for an anonymous guest.",
            group="Limits",
        ),
        ConfigField(
            config.FIELD_ADMIN_DAILY_USD,
            "Max USD per admin / 24h",
            type="float",
            default=0.0,
            minimum=0,
            help="Rolling 24-hour spend cap for administrators. 0 = unlimited.",
            group="Limits",
        ),
        ConfigField(
            config.FIELD_GUESTS_ENABLED,
            "Allow guests",
            type="bool",
            default=True,
            help="When off, only signed-in users may use Devii.",
            group="Limits",
        ),
        ConfigField(
            config.FIELD_PRICE_CACHE_HIT,
            "Price cache-hit / 1M",
            type="float",
            default=0.0028,
            minimum=0,
            help="USD per million cache-hit input tokens.",
            group="Pricing",
        ),
        ConfigField(
            config.FIELD_PRICE_CACHE_MISS,
            "Price cache-miss / 1M",
            type="float",
            default=0.14,
            minimum=0,
            help="USD per million cache-miss input tokens.",
            group="Pricing",
        ),
        ConfigField(
            config.FIELD_PRICE_OUTPUT,
            "Price output / 1M",
            type="float",
            default=0.28,
            minimum=0,
            help="USD per million output tokens.",
            group="Pricing",
        ),
        ConfigField(
            config.FIELD_RSEARCH_ENABLED,
            "Enable web search tools",
            type="bool",
            default=True,
            help="Allow the external rsearch_* tools (web/image search, web-grounded AI answer, "
            "chat, image description). When off, those tools are refused. These reach an external "
            "public service, not this platform.",
            group="Web search",
        ),
        ConfigField(
            config.FIELD_RSEARCH_URL,
            "Web search service URL",
            type="url",
            default=config.DEFAULT_RSEARCH_URL,
            help="Base URL of the rsearch-compatible service the rsearch_* tools call.",
            group="Web search",
        ),
        ConfigField(
            config.FIELD_RSEARCH_TIMEOUT,
            "Web search timeout (seconds)",
            type="float",
            default=config.DEFAULT_RSEARCH_TIMEOUT_SECONDS,
            minimum=config.MIN_TIMEOUT_SECONDS,
            help="Read timeout for rsearch_* calls. Web-grounded answers can take several "
            "minutes, so this is generous by default. Minimum five minutes.",
            group="Web search",
        ),
    ]

    def __init__(self):
        super().__init__(name="devii", interval_seconds=60)
        self._hub = None

    def hub(self):
        if self._hub is None:
            from .hub import DeviiHub

            self._hub = DeviiHub(self._build_settings)
        return self._hub

    def effective_config(self) -> dict:
        from devplacepy.database import internal_gateway_key

        cfg = self.get_config()
        cfg[config.FIELD_AI_KEY] = (
            cfg[config.FIELD_AI_KEY]
            or os.environ.get("DEVII_AI_KEY", "")
            or internal_gateway_key()
        )
        return cfg

    def instance_base_url(self) -> str:
        cfg = self.get_config()
        configured = cfg.get("devii_base_url", "")
        if configured:
            return configured
        return os.environ.get("DEVII_BASE_URL", INSTANCE_ORIGIN_DEFAULT)

    def _pricing(self, cfg: dict) -> Pricing:
        return Pricing(
            model=cfg[config.FIELD_AI_MODEL] or DEFAULT_AI_MODEL,
            cache_hit_per_m=float(cfg[config.FIELD_PRICE_CACHE_HIT]),
            cache_miss_per_m=float(cfg[config.FIELD_PRICE_CACHE_MISS]),
            output_per_m=float(cfg[config.FIELD_PRICE_OUTPUT]),
        )

    def _build_settings(
        self,
        api_key: str,
        base_url: str,
        owner_kind: str = "guest",
        is_admin: bool = False,
    ):
        cfg = self.effective_config()
        settings = build_settings(cfg, base_url, api_key, owner_kind, is_admin)
        return settings, self._pricing(cfg)

    def daily_limit_for(self, owner_kind: str, is_admin: bool = False) -> float:
        return config.effective_daily_limit(self.get_config(), owner_kind, is_admin)

    def guests_enabled(self) -> bool:
        return bool(self.get_config()[config.FIELD_GUESTS_ENABLED])

    def spent_24h(self, owner_kind: str, owner_id: str) -> float:
        return self.hub().ledger.spent_24h(owner_kind, owner_id)

    def reset_quota(self, owner_kind: str, owner_id: str) -> int:
        return self.hub().ledger.reset(owner_kind, owner_id)

    def reset_guest_quotas(self) -> int:
        return self.hub().ledger.reset_owner_kind("guest")

    def reset_all_quotas(self) -> int:
        return self.hub().ledger.reset_all()

    async def run_once(self) -> None:
        if not self.is_enabled():
            return
        hub = self.hub()
        pruned = hub.ledger.prune(48)
        removed = await hub.gc_idle()
        if pruned or removed:
            self.log(
                f"Housekeeping: pruned {pruned} ledger rows, closed {removed} idle sessions"
            )

    async def on_disable(self) -> None:
        if self._hub is not None:
            await self._hub.aclose()
            self._hub = None

    def collect_metrics(self) -> dict:
        cfg = self.get_config()
        hub = self._hub
        active = hub.active_sessions() if hub is not None else 0
        conns = hub.connection_count() if hub is not None else 0
        spent = hub.ledger.total_spent_24h() if hub is not None else 0.0
        return {
            "stats": [
                {"label": "Active sessions", "value": active},
                {"label": "Connections", "value": conns},
                {"label": "Spend 24h", "value": f"${spent:.4f}"},
                {
                    "label": "User cap 24h",
                    "value": f"${float(cfg[config.FIELD_USER_DAILY_USD]):.2f}",
                },
                {
                    "label": "Guest cap 24h",
                    "value": f"${float(cfg[config.FIELD_GUEST_DAILY_USD]):.2f}",
                },
                {
                    "label": "Admin cap 24h",
                    "value": (
                        f"${float(cfg[config.FIELD_ADMIN_DAILY_USD]):.2f}"
                        if float(cfg[config.FIELD_ADMIN_DAILY_USD]) > 0
                        else "unlimited"
                    ),
                },
                {
                    "label": "Guests",
                    "value": "on" if cfg[config.FIELD_GUESTS_ENABLED] else "off",
                },
                {"label": "Model", "value": cfg[config.FIELD_AI_MODEL]},
            ]
        }
