"""Async clients for OpenViking and EverOS used by the lightweight API."""
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import httpx

from .config import get_config
from .store import ADMIN_ACCOUNT_ID, ADMIN_USER_ID, OpenVikingUserKeyStore


@dataclass(frozen=True)
class OpenVikingCredential:
    api_key: str
    account_id: str | None = None
    user_id: str | None = None
    agent_id: str | None = None
    user_key_auth: bool = False


class OpenVikingMemorySystemClient:
    def __init__(self, store: OpenVikingUserKeyStore | None = None) -> None:
        config = get_config()
        self.base_url = config.openviking.url.rstrip("/")
        self.root_key = config.openviking.api_key or "your-secret-root-key"
        self.timeout = config.openviking.timeout
        self.verify_ssl = config.openviking.verify_ssl
        self.store = store or OpenVikingUserKeyStore(config.storage.sqlite_path)

    async def health(self) -> dict[str, Any]:
        async with self._client(self.root_key) as client:
            response = await client.get("/health")
            response.raise_for_status()
            return response.json()

    async def create_account(self, account_id: str = ADMIN_ACCOUNT_ID, admin_user_id: str = ADMIN_USER_ID) -> dict[str, Any]:
        async with self._client(self.root_key) as client:
            response = await client.post(
                "/api/v1/admin/accounts",
                json=self._create_account_payload(account_id, admin_user_id),
            )
            if response.status_code == 409:
                return response.json()
            response.raise_for_status()
            data = response.json()

        user_key = self._extract_user_key(data)
        if user_key:
            self.store.save_account_key(account_id, admin_user_id, user_key)
            self.store.save_user_key(admin_user_id, user_key, account_id=account_id)
        return data

    async def create_user(self, user_id: str, role: str = "user") -> dict[str, Any]:
        existing = self.store.get_user_key(user_id)
        account_id = self._account_id_for_user(user_id)
        if existing:
            return {
                "status": "ok",
                "result": {
                    "account_id": account_id,
                    "admin_user_id": user_id,
                    "user_id": user_id,
                    "user_key": existing,
                },
            }

        return await self.create_account(account_id, user_id)

    def credential_for_user(
        self,
        user_id: str,
        user_key: str,
        agent_id: str | None = None,
    ) -> OpenVikingCredential:
        if not self.store.user_key_matches(user_id, user_key):
            raise PermissionError("Invalid user key")
        return self.user_credential(user_key, user_id, agent_id=agent_id)

    def user_credential(
        self,
        user_key: str,
        user_id: str,
        agent_id: str | None = None,
    ) -> OpenVikingCredential:
        return OpenVikingCredential(
            api_key=user_key,
            account_id=self._account_id_for_user(user_id),
            user_id=user_id,
            agent_id=agent_id,
            user_key_auth=True,
        )

    async def ensure_session(self, credential: OpenVikingCredential | str, session_id: str) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.post("/api/v1/sessions", json={"session_id": session_id})
            if response.status_code in {409, 422}:
                self._save_session(credential, session_id)
                return {"session_id": session_id, "status": "exists"}
            response.raise_for_status()
            self._save_session(credential, session_id)
            return response.json()

    async def append_message(
        self, credential: OpenVikingCredential | str, session_id: str, role: str, content: str
    ) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.post(
                f"/api/v1/sessions/{session_id}/messages",
                json={"role": role, "content": content},
            )
            response.raise_for_status()
            return response.json()

    async def commit_session(self, credential: OpenVikingCredential | str, session_id: str) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.post(
                f"/api/v1/sessions/{session_id}/commit",
                json={"keep_recent_count": 0},
            )
            response.raise_for_status()
            data = response.json()
            self._save_commit_metadata(credential, session_id, data)
            return data

    async def extract_session(self, credential: OpenVikingCredential | str, session_id: str) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.post(f"/api/v1/sessions/{session_id}/extract")
            response.raise_for_status()
            return response.json()

    async def get_task(self, credential: OpenVikingCredential | str, task_id: str) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.get(f"/api/v1/tasks/{task_id}")
            response.raise_for_status()
            return response.json()

    async def upload_temp_file(self, credential: OpenVikingCredential | str, path: str | Path) -> dict[str, Any]:
        file_path = Path(path)
        async with self._credential_client(credential, json_content_type=False) as client:
            with file_path.open("rb") as file_obj:
                response = await client.post(
                    "/api/v1/resources/temp_upload",
                    files={"file": (file_path.name, file_obj)},
                )
            response.raise_for_status()
            return response.json()

    async def add_resource(
        self,
        credential: OpenVikingCredential | str,
        *,
        to: str,
        reason: str | None = None,
        wait: bool = True,
        directly_upload_media: bool = True,
        path: str | None = None,
        temp_file_id: str | None = None,
    ) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "to": to,
            "wait": wait,
            "directly_upload_media": directly_upload_media,
        }
        if reason is not None:
            payload["reason"] = reason
        if temp_file_id is not None:
            payload["temp_file_id"] = temp_file_id
        else:
            payload["path"] = path

        async with self._credential_client(credential) as client:
            response = await client.post("/api/v1/resources", json=payload)
            response.raise_for_status()
            return response.json()

    async def delete_resource(
        self,
        credential: OpenVikingCredential | str,
        uri: str,
        recursive: bool = True,
    ) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.delete(
                "/api/v1/fs",
                params={"uri": uri, "recursive": str(recursive).lower()},
            )
            response.raise_for_status()
            return response.json()

    async def list_memories(
        self,
        credential: OpenVikingCredential | str,
        uri: str = "viking://user/memories",
        recursive: bool = True,
    ) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.get(
                "/api/v1/fs/ls",
                params={"uri": uri, "recursive": str(recursive).lower()},
            )
            response.raise_for_status()
            return response.json()

    async def read_memory(self, credential: OpenVikingCredential | str, uri: str) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.get("/api/v1/content/read", params={"uri": uri})
            response.raise_for_status()
            return response.json()

    async def write_memory(
        self,
        credential: OpenVikingCredential | str,
        *,
        uri: str,
        content: str,
        mode: str = "create",
        wait: bool = True,
    ) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.post(
                "/api/v1/content/write",
                json={
                    "uri": uri,
                    "content": content,
                    "mode": mode,
                    "wait": wait,
                },
            )
            response.raise_for_status()
            return response.json()

    async def delete_memory(
        self,
        credential: OpenVikingCredential | str,
        uri: str,
        recursive: bool = False,
    ) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.delete(
                "/api/v1/fs",
                params={"uri": uri, "recursive": str(recursive).lower()},
            )
            response.raise_for_status()
            return response.json()

    async def find(self, credential: OpenVikingCredential | str, query: str, limit: int) -> dict[str, Any]:
        user_id = credential.user_id if isinstance(credential, OpenVikingCredential) else None
        target_uri = f"viking://user/{user_id}/memories/" if user_id else "viking://user/memories/"
        async with self._credential_client(credential) as client:
            response = await client.post(
                "/api/v1/search/find",
                json={
                    "query": query,
                    "target_uri": target_uri,
                    "limit": limit,
                },
            )
            response.raise_for_status()
            return response.json()

    async def search(
        self,
        credential: OpenVikingCredential | str,
        query: str,
        limit: int,
        level: int = 2,
        score_threshold: float = 0.8,
        target_uri: str = "viking://user/memories",
    ) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "query": query,
            "target_uri": target_uri,
            "limit": limit,
            "level": level,
            "score_threshold": score_threshold,
        }
        async with self._credential_client(credential) as client:
            response = await client.post("/api/v1/search/search", json=payload)
            response.raise_for_status()
            return response.json()

    async def search_profile_memories(
        self,
        credential: OpenVikingCredential | str,
        query: str,
        limit: int,
        level: int,
    ) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.post(
                "/api/v1/search/search",
                json={
                    "query": query,
                    "limit": limit,
                    "level": level,
                    "target_uri": "viking://user/memories",
                },
            )
            response.raise_for_status()
            return response.json()

    async def get_session_context(self, credential: OpenVikingCredential | str, session_id: str) -> dict[str, Any]:
        async with self._credential_client(credential) as client:
            response = await client.get(f"/api/v1/sessions/{session_id}/context")
            response.raise_for_status()
            return response.json()

    def _credential_client(
        self,
        credential: OpenVikingCredential | str,
        json_content_type: bool = True,
    ) -> httpx.AsyncClient:
        if isinstance(credential, str):
            return self._client(credential, json_content_type=json_content_type)
        if credential.user_key_auth:
            return self._client(credential.api_key, json_content_type=json_content_type)
        headers = {}
        if credential.account_id:
            headers["X-OpenViking-Account"] = credential.account_id
        if credential.user_id:
            headers["X-OpenViking-User"] = credential.user_id
        if credential.agent_id:
            headers["X-OpenViking-Agent"] = credential.agent_id
        return self._client(credential.api_key, headers, json_content_type=json_content_type)

    def _client(
        self,
        api_key: str,
        extra_headers: dict[str, str] | None = None,
        json_content_type: bool = True,
    ) -> httpx.AsyncClient:
        headers = {"X-API-Key": api_key}
        if json_content_type:
            headers["Content-Type"] = "application/json"
        if extra_headers:
            headers.update(extra_headers)
        return httpx.AsyncClient(
            base_url=self.base_url,
            headers=headers,
            timeout=self.timeout,
            verify=self.verify_ssl,
        )

    def _extract_user_key(self, data: dict[str, Any]) -> str | None:
        result = data.get("result") if isinstance(data.get("result"), dict) else data
        value = result.get("user_key") if isinstance(result, dict) else None
        return str(value) if value else None

    def _create_account_payload(self, account_id: str, admin_user_id: str) -> dict[str, Any]:
        return {
            "account_id": account_id,
            "admin_user_id": admin_user_id,
        }

    def _account_id_for_user(self, user_id: str) -> str:
        return f"{user_id}_account"

    def _save_session(self, credential: OpenVikingCredential | str, session_id: str) -> None:
        if isinstance(credential, OpenVikingCredential) and credential.user_id:
            self.store.save_session(credential.user_id, session_id)

    def _save_commit_metadata(
        self,
        credential: OpenVikingCredential | str,
        session_id: str,
        data: dict[str, Any],
    ) -> None:
        if not isinstance(credential, OpenVikingCredential) or not credential.user_id:
            return
        result = data.get("result") if isinstance(data.get("result"), dict) else data
        if not isinstance(result, dict):
            return
        task_id = result.get("task_id")
        if not task_id:
            return
        archive_uri = result.get("archive_uri")
        self.store.save_task(
            user_id=credential.user_id,
            session_id=session_id,
            task_id=str(task_id),
            archive_uri=str(archive_uri) if archive_uri else None,
        )


class EverOSMemorySystemClient:
    def __init__(self) -> None:
        config = get_config()
        self.base_url = config.everos.url.rstrip("/")
        self.api_key = config.everos.api_key
        self.timeout = config.everos.timeout
        self.verify_ssl = config.everos.verify_ssl
        self.health_path = config.everos.health_path

    async def health(self) -> dict[str, Any]:
        async with self._client() as client:
            response = await client.get(self.health_path)
            response.raise_for_status()
            return response.json()

    async def append_message(self, user_id: str, session_id: str, role: str, content: str) -> dict[str, Any]:
        async with self._client() as client:
            response = await client.post(
                "/api/v1/memories",
                json=self.build_message_payload(user_id=user_id, session_id=session_id, role=role, content=content),
            )
            response.raise_for_status()
            return response.json()

    def build_message_payload(self, user_id: str, session_id: str, role: str, content: str) -> dict[str, Any]:
        everos_role = "assistant" if role == "assistant" else "user"
        sender_id = "assistant" if everos_role == "assistant" else user_id
        timestamp = int(datetime.now(timezone.utc).timestamp() * 1000)
        return {
            "user_id": user_id,
            "session_id": session_id,
            "messages": [
                {
                    "message_id": f"msg_{timestamp}",
                    "timestamp": timestamp,
                    "sender_id": sender_id,
                    "sender_name": sender_id,
                    "role": everos_role,
                    "content": content,
                }
            ],
        }

    async def flush(self, user_id: str, session_id: str) -> dict[str, Any]:
        async with self._client() as client:
            response = await client.post("/api/v1/memories/flush", json={"user_id": user_id, "session_id": session_id})
            response.raise_for_status()
            return response.json()

    async def search(self, user_id: str, session_id: str | None, query: str, method: str, limit: int) -> dict[str, Any]:
        filters: dict[str, Any] = {"user_id": user_id}
        if session_id:
            filters["session_id"] = session_id
        async with self._client() as client:
            response = await client.post(
                "/api/v1/memories/search",
                json={
                    "query": query,
                    "method": method,
                    "memory_types": ["episodic_memory", "profile", "raw_message"],
                    "filters": filters,
                    "top_k": limit,
                    "include_original_data": True,
                },
            )
            response.raise_for_status()
            return response.json()

    async def get_profile(self, user_id: str) -> dict[str, Any]:
        async with self._client() as client:
            response = await client.post(
                "/api/v1/memories/get",
                json={
                    "memory_type": "profile",
                    "filters": {"user_id": user_id},
                    "page": 1,
                    "page_size": 20,
                },
            )
            response.raise_for_status()
            return response.json()

    def _client(self) -> httpx.AsyncClient:
        headers = {"Content-Type": "application/json"}
        if self.api_key:
            headers["X-API-Key"] = self.api_key
            headers["Authorization"] = f"Bearer {self.api_key}"
        return httpx.AsyncClient(
            base_url=self.base_url,
            headers=headers,
            timeout=self.timeout,
            verify=self.verify_ssl,
        )
