# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
# SPDX-License-Identifier: AGPL-3.0
"""Synchronous HTTP Client for OpenViking.

Wraps AsyncHTTPClient with synchronous methods.
"""

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

from openviking.telemetry import TelemetryRequest
from openviking_cli.client.http import AsyncHTTPClient
from openviking_cli.utils import run_async


class SyncHTTPClient:
    """Synchronous HTTP Client for OpenViking Server.

    Wraps AsyncHTTPClient with synchronous methods.
    Supports auto-loading url/api_key from ovcli.conf when not provided.

    Examples:
        # Explicit url
        client = SyncHTTPClient(url="http://localhost:1933", api_key="key")
        client.initialize()

        # Auto-load from ~/.openviking/ovcli.conf
        client = SyncHTTPClient()
        client.initialize()
    """

    def __init__(
        self,
        url: Optional[str] = None,
        api_key: Optional[str] = None,
        user_id: Optional[str] = None,
        agent_id: Optional[str] = None,
        account: Optional[str] = None,
        user: Optional[str] = None,
        timeout: float = 60.0,
        extra_headers: Optional[Dict[str, str]] = None,
    ):
        self._async_client = AsyncHTTPClient(
            url=url,
            api_key=api_key,
            user_id=user_id,
            agent_id=agent_id,
            account=account,
            user=user,
            timeout=timeout,
            extra_headers=extra_headers,
        )
        self._initialized = False

    # ============= Lifecycle =============

    def initialize(self) -> None:
        """Initialize the HTTP client."""
        run_async(self._async_client.initialize())
        self._initialized = True

    def close(self) -> None:
        """Close the HTTP client and release resources."""
        run_async(self._async_client.close())
        self._initialized = False

    # ============= session =============

    def session(self, session_id: Optional[str] = None, must_exist: bool = False) -> Any:
        """Create a new session or load an existing one.

        Args:
            session_id: Session ID, creates a new session if None
            must_exist: If True and session_id is provided, raises NotFoundError
                        when the session does not exist.

        Returns:
            Session object
        """
        return self._async_client.session(session_id, must_exist=must_exist)

    def session_exists(self, session_id: str) -> bool:
        """Check whether a session exists in storage."""
        return run_async(self._async_client.session_exists(session_id))

    def create_session(
        self, session_id: Optional[str] = None, telemetry: TelemetryRequest = False
    ) -> Dict[str, Any]:
        """Create a new session.

        Args:
            session_id: Optional session ID. If provided, creates a session with the given ID.
                       If None, creates a new session with auto-generated ID.
        """
        return run_async(self._async_client.create_session(session_id, telemetry=telemetry))

    def list_sessions(self) -> List[Any]:
        """List all sessions."""
        return run_async(self._async_client.list_sessions())

    def get_session(self, session_id: str, *, auto_create: bool = False) -> Dict[str, Any]:
        """Get session details."""
        return run_async(self._async_client.get_session(session_id, auto_create=auto_create))

    def get_session_context(self, session_id: str, token_budget: int = 128_000) -> Dict[str, Any]:
        """Get assembled session context."""
        return run_async(self._async_client.get_session_context(session_id, token_budget))

    def get_session_archive(self, session_id: str, archive_id: str) -> Dict[str, Any]:
        """Get one completed archive for a session."""
        return run_async(self._async_client.get_session_archive(session_id, archive_id))

    def delete_session(self, session_id: str) -> None:
        """Delete a session."""
        run_async(self._async_client.delete_session(session_id))

    def add_message(
        self,
        session_id: str,
        role: str,
        content: str | None = None,
        parts: list[dict] | None = None,
        created_at: str | None = None,
        role_id: str | None = None,
        telemetry: TelemetryRequest = False,
    ) -> Dict[str, Any]:
        """Add a message to a session.

        Args:
            session_id: Session ID
            role: Message role ("user" or "assistant")
            content: Text content (simple mode)
            parts: Parts array (full Part support: TextPart, ContextPart, ToolPart)
            created_at: Message creation time (ISO format string)
            role_id: Optional explicit actor identity. Omit to let the server derive it.

        If both content and parts are provided, parts takes precedence.
        """
        return run_async(
            self._async_client.add_message(
                session_id,
                role,
                content,
                parts,
                created_at,
                role_id,
                telemetry,
            )
        )

    def get_task(self, task_id: str) -> Optional[Dict[str, Any]]:
        """Query background task status."""
        return run_async(self._async_client.get_task(task_id))

    def reindex(
        self,
        uri: str,
        mode: str = "vectors_only",
        wait: bool = True,
    ) -> Dict[str, Any]:
        """Trigger reindex for a URI."""
        return run_async(
            self._async_client.reindex(
                uri=uri,
                mode=mode,
                wait=wait,
            )
        )

    def commit_session(
        self, session_id: str, telemetry: TelemetryRequest = False
    ) -> Dict[str, Any]:
        """Commit a session (archive and extract memories)."""
        return run_async(self._async_client.commit_session(session_id, telemetry=telemetry))

    # ============= Resource =============

    def add_resource(
        self,
        path: str,
        to: Optional[str] = None,
        parent: Optional[str] = None,
        reason: str = "",
        instruction: str = "",
        wait: bool = False,
        timeout: Optional[float] = None,
        strict: bool = True,
        ignore_dirs: Optional[str] = None,
        include: Optional[str] = None,
        exclude: Optional[str] = None,
        directly_upload_media: bool = True,
        telemetry: TelemetryRequest = False,
    ) -> Dict[str, Any]:
        """Add resource to OpenViking."""
        if to and parent:
            raise ValueError("Cannot specify both 'to' and 'parent' at the same time.")
        return run_async(
            self._async_client.add_resource(
                path,
                to,
                parent,
                reason,
                instruction,
                wait,
                timeout,
                strict,
                ignore_dirs,
                include,
                exclude,
                directly_upload_media,
                telemetry=telemetry,
            )
        )

    def add_skill(
        self,
        data: Any,
        wait: bool = False,
        timeout: Optional[float] = None,
        telemetry: TelemetryRequest = False,
    ) -> Dict[str, Any]:
        """Add skill to OpenViking."""
        return run_async(
            self._async_client.add_skill(data, wait=wait, timeout=timeout, telemetry=telemetry)
        )

    def wait_processed(self, timeout: Optional[float] = None) -> Dict[str, Any]:
        """Wait for all processing to complete."""
        return run_async(self._async_client.wait_processed(timeout))

    # ============= Search =============

    def search(
        self,
        query: str,
        target_uri: Union[str, List[str]] = "",
        session: Optional[Any] = None,
        session_id: Optional[str] = None,
        limit: int = 10,
        node_limit: Optional[int] = None,
        score_threshold: Optional[float] = None,
        filter: Optional[Dict] = None,
        telemetry: TelemetryRequest = False,
    ):
        """Semantic search with optional session context."""
        return run_async(
            self._async_client.search(
                query=query,
                target_uri=target_uri,
                session=session,
                session_id=session_id,
                limit=limit,
                node_limit=node_limit,
                score_threshold=score_threshold,
                filter=filter,
                telemetry=telemetry,
            )
        )

    def find(
        self,
        query: str,
        target_uri: Union[str, List[str]] = "",
        limit: int = 10,
        node_limit: Optional[int] = None,
        score_threshold: Optional[float] = None,
        filter: Optional[Dict] = None,
        telemetry: TelemetryRequest = False,
    ):
        """Semantic search without session context."""
        return run_async(
            self._async_client.find(
                query,
                target_uri,
                limit,
                node_limit,
                score_threshold,
                filter,
                telemetry=telemetry,
            )
        )

    def grep(
        self,
        uri: str,
        pattern: str,
        case_insensitive: bool = False,
        node_limit: Optional[int] = None,
        exclude_uri: Optional[str] = None,
    ) -> Dict:
        """Content search with pattern."""
        return run_async(
            self._async_client.grep(uri, pattern, case_insensitive, node_limit, exclude_uri)
        )

    def glob(self, pattern: str, uri: str = "viking://") -> Dict:
        """File pattern matching."""
        return run_async(self._async_client.glob(pattern, uri))

    # ============= File System =============

    def ls(
        self,
        uri: str,
        simple: bool = False,
        recursive: bool = False,
        output: str = "original",
        abs_limit: int = 256,
        show_all_hidden: bool = False,
        node_limit: int = 1000,
    ) -> List[Any]:
        """List directory contents."""
        return run_async(
            self._async_client.ls(
                uri,
                simple=simple,
                recursive=recursive,
                output=output,
                abs_limit=abs_limit,
                show_all_hidden=show_all_hidden,
                node_limit=node_limit,
            )
        )

    def tree(
        self,
        uri: str,
        output: str = "original",
        abs_limit: int = 128,
        show_all_hidden: bool = False,
        node_limit: int = 1000,
    ) -> List[Dict[str, Any]]:
        """Get directory tree."""
        return run_async(
            self._async_client.tree(
                uri,
                output=output,
                abs_limit=abs_limit,
                show_all_hidden=show_all_hidden,
                node_limit=node_limit,
            )
        )

    def stat(self, uri: str) -> Dict:
        """Get resource status."""
        return run_async(self._async_client.stat(uri))

    def mkdir(self, uri: str, description: Optional[str] = None) -> None:
        """Create directory."""
        run_async(self._async_client.mkdir(uri, description=description))

    def rm(self, uri: str, recursive: bool = False) -> None:
        """Remove resource."""
        run_async(self._async_client.rm(uri, recursive))

    def mv(self, from_uri: str, to_uri: str) -> None:
        """Move resource."""
        run_async(self._async_client.mv(from_uri, to_uri))

    # ============= Content =============

    def read(self, uri: str, offset: int = 0, limit: int = -1) -> str:
        """Read file content."""
        return run_async(self._async_client.read(uri, offset=offset, limit=limit))

    def abstract(self, uri: str) -> str:
        """Read L0 abstract."""
        return run_async(self._async_client.abstract(uri))

    def overview(self, uri: str) -> str:
        """Read L1 overview."""
        return run_async(self._async_client.overview(uri))

    def write(
        self,
        uri: str,
        content: str,
        mode: str = "replace",
        wait: bool = False,
        timeout: Optional[float] = None,
        telemetry: TelemetryRequest = False,
    ) -> Dict[str, Any]:
        """Write text content to an existing file and refresh semantics/vectors."""
        return run_async(
            self._async_client.write(
                uri=uri,
                content=content,
                mode=mode,
                wait=wait,
                timeout=timeout,
                telemetry=telemetry,
            )
        )

    # ============= Relations =============

    def relations(self, uri: str) -> List[Dict[str, Any]]:
        """Get relations for a resource."""
        return run_async(self._async_client.relations(uri))

    def link(self, from_uri: str, uris: Union[str, List[str]], reason: str = "") -> None:
        """Create link between resources."""
        run_async(self._async_client.link(from_uri, uris, reason))

    def unlink(self, from_uri: str, uri: str) -> None:
        """Remove link between resources."""
        run_async(self._async_client.unlink(from_uri, uri))

    # ============= Pack =============

    def export_ovpack(
        self,
        uri: str,
        to: str,
        include_vectors: bool = False,
    ) -> str:
        """Export context as .ovpack file and save to local path.

        Args:
            uri: Viking URI to export
            to: Local file path where to save the .ovpack file

        Returns:
            Local file path where the .ovpack was saved
        """
        return run_async(self._async_client.export_ovpack(uri, to, include_vectors=include_vectors))

    def backup_ovpack(self, to: str, include_vectors: bool = False) -> str:
        """Back up public scopes as a restore-only .ovpack file."""
        return run_async(self._async_client.backup_ovpack(to, include_vectors=include_vectors))

    def import_ovpack(
        self,
        file_path: str,
        target: str,
        on_conflict: Optional[str] = None,
        vector_mode: Optional[str] = None,
    ) -> str:
        """Import .ovpack file."""
        return run_async(
            self._async_client.import_ovpack(
                file_path,
                target,
                on_conflict=on_conflict,
                vector_mode=vector_mode,
            )
        )

    def restore_ovpack(
        self,
        file_path: str,
        on_conflict: Optional[str] = None,
        vector_mode: Optional[str] = None,
    ) -> str:
        """Restore backup .ovpack file."""
        return run_async(
            self._async_client.restore_ovpack(
                file_path,
                on_conflict=on_conflict,
                vector_mode=vector_mode,
            )
        )

    def check_consistency(self, uri: str) -> Dict[str, Any]:
        """Check filesystem/vector-index consistency for a URI subtree."""
        return run_async(self._async_client.check_consistency(uri))

    # ============= Admin =============

    def admin_create_account(self, account_id: str, admin_user_id: str) -> Dict[str, Any]:
        """Create a new account with its first admin user."""
        return run_async(self._async_client.admin_create_account(account_id, admin_user_id))

    def admin_list_accounts(self) -> List[Any]:
        """List all accounts."""
        return run_async(self._async_client.admin_list_accounts())

    def admin_delete_account(self, account_id: str) -> Dict[str, Any]:
        """Delete an account and all associated users."""
        return run_async(self._async_client.admin_delete_account(account_id))

    def admin_register_user(
        self, account_id: str, user_id: str, role: str = "user"
    ) -> Dict[str, Any]:
        """Register a new user in an account."""
        return run_async(self._async_client.admin_register_user(account_id, user_id, role))

    def admin_list_users(self, account_id: str) -> List[Any]:
        """List all users in an account."""
        return run_async(self._async_client.admin_list_users(account_id))

    def admin_remove_user(self, account_id: str, user_id: str) -> Dict[str, Any]:
        """Remove a user from an account."""
        return run_async(self._async_client.admin_remove_user(account_id, user_id))

    def admin_set_role(self, account_id: str, user_id: str, role: str) -> Dict[str, Any]:
        """Change a user's role."""
        return run_async(self._async_client.admin_set_role(account_id, user_id, role))

    def admin_regenerate_key(self, account_id: str, user_id: str) -> Dict[str, Any]:
        """Regenerate a user's API key. Old key is immediately invalidated."""
        return run_async(self._async_client.admin_regenerate_key(account_id, user_id))

    # ============= Debug =============

    def health(self) -> bool:
        """Check server health."""
        return run_async(self._async_client.health())

    def get_status(self) -> Dict[str, Any]:
        """Get system status."""
        return self._async_client.get_status()

    def is_healthy(self) -> bool:
        """Quick health check."""
        return self._async_client.is_healthy()

    @property
    def observer(self):
        """Get observer service for component status."""
        return self._async_client.observer
