"""Normbase client abstraction and adapters for EDA engine."""
from __future__ import annotations

from typing import Any, Dict, List, Optional, Protocol

import aiohttp

from security.auth.verifier import build_internal_auth_headers


class NormbaseClient(Protocol):
    """Abstract interface for EDA to consume norm retrieval services."""

    async def retrieve_norms(
        self,
        context: Dict[str, Any],
        agent_role: str,
        field_ids: Optional[List[str]] = None,
        top_k: int = 5,
    ) -> List[Dict[str, Any]]:
        ...

    async def get_default_norms(self, agent_role: str) -> List[Dict[str, Any]]:
        ...

    async def report_outcome(
        self, agent_id: str, norm_id: str, outcome: str, details: Dict[str, Any]
    ) -> None:
        ...


class MockNormbaseClient:
    """Simple in-memory normbase adapter for local prototype tests."""

    def __init__(self, norms: Optional[List[Dict[str, Any]]] = None):
        self.norms = norms or []

    async def retrieve_norms(
        self,
        context: Dict[str, Any],
        agent_role: str,
        field_ids: Optional[List[str]] = None,
        top_k: int = 5,
    ) -> List[Dict[str, Any]]:
        del context, field_ids
        scoped = [
            n
            for n in self.norms
            if agent_role in n.get("scope_roles", [])
            and n.get("status", "active") == "active"
        ]
        return scoped[:top_k]

    async def get_default_norms(self, agent_role: str) -> List[Dict[str, Any]]:
        return await self.retrieve_norms(context={}, agent_role=agent_role, top_k=20)

    async def report_outcome(
        self, agent_id: str, norm_id: str, outcome: str, details: Dict[str, Any]
    ) -> None:
        del agent_id, norm_id, outcome, details
        return None


class HttpNormbaseClient:
    """HTTP adapter over the existing Normbase API."""

    def __init__(
        self,
        base_url: str,
        retrieve_timeout_seconds: int = 60,
        outcome_timeout_seconds: int = 20,
    ):
        self.base_url = base_url.rstrip("/")
        self.retrieve_timeout_seconds = retrieve_timeout_seconds
        self.outcome_timeout_seconds = outcome_timeout_seconds

    async def retrieve_norms(
        self,
        context: Dict[str, Any],
        agent_role: str,
        field_ids: Optional[List[str]] = None,
        top_k: int = 5,
    ) -> List[Dict[str, Any]]:
        payload: Dict[str, Any] = {
            "context": context,
            "agent_role": agent_role,
            "top_k": top_k,
        }
        if field_ids:
            payload["field_ids"] = field_ids
        headers = build_internal_auth_headers()
        async with aiohttp.ClientSession() as session:
            async with session.post(
                f"{self.base_url}/api/v1/retrieve",
                json=payload,
                headers=headers,
                timeout=aiohttp.ClientTimeout(total=self.retrieve_timeout_seconds),
            ) as response:
                response.raise_for_status()
                data = await response.json()
                return data.get("norms", [])

    async def get_default_norms(self, agent_role: str) -> List[Dict[str, Any]]:
        # Prototype default: use retrieval with empty context.
        return await self.retrieve_norms(context={}, agent_role=agent_role, top_k=20)

    async def report_outcome(
        self, agent_id: str, norm_id: str, outcome: str, details: Dict[str, Any]
    ) -> None:
        payload = {
            "agent_id": agent_id,
            "norm_id": norm_id,
            "outcome": outcome,
            "details": details,
        }
        headers = build_internal_auth_headers()
        async with aiohttp.ClientSession() as session:
            async with session.post(
                f"{self.base_url}/api/v1/norms/{norm_id}/outcomes",
                json=payload,
                headers=headers,
                timeout=aiohttp.ClientTimeout(total=self.outcome_timeout_seconds),
            ) as response:
                if response.status >= 400:
                    return None
        return None

