"""Configuration for the OpenHands App Server."""

import os
from pathlib import Path
from typing import AsyncContextManager

import httpx
from fastapi import Depends, Request
from pydantic import Field
from sqlalchemy.ext.asyncio import AsyncSession

from openhands.agent_server.env_parser import from_env
from openhands.app_server.app_conversation.app_conversation_info_service import (
    AppConversationInfoService,
    AppConversationInfoServiceInjector,
)
from openhands.app_server.app_conversation.app_conversation_service import (
    AppConversationService,
    AppConversationServiceInjector,
)
from openhands.app_server.app_conversation.app_conversation_start_task_service import (
    AppConversationStartTaskService,
    AppConversationStartTaskServiceInjector,
)
from openhands.app_server.app_lifespan.app_lifespan_service import AppLifespanService
from openhands.app_server.app_lifespan.oss_app_lifespan_service import (
    OssAppLifespanService,
)
from openhands.app_server.event.event_service import EventService, EventServiceInjector
from openhands.app_server.event_callback.event_callback_service import (
    EventCallbackService,
    EventCallbackServiceInjector,
)
from openhands.app_server.sandbox.sandbox_service import (
    SandboxService,
    SandboxServiceInjector,
)
from openhands.app_server.sandbox.sandbox_spec_service import (
    SandboxSpecService,
    SandboxSpecServiceInjector,
)
from openhands.app_server.services.db_session_injector import (
    DbSessionInjector,
)
from openhands.app_server.services.httpx_client_injector import HttpxClientInjector
from openhands.app_server.services.injector import InjectorState
from openhands.app_server.services.jwt_service import JwtService, JwtServiceInjector
from openhands.app_server.user.user_context import UserContext, UserContextInjector
from openhands.sdk.utils.models import OpenHandsModel


def get_default_persistence_dir() -> Path:
    # Recheck env because this function is also used to generate other defaults
    persistence_dir = os.getenv('OH_PERSISTENCE_DIR')

    if persistence_dir:
        result = Path(persistence_dir)
    else:
        result = Path.home() / '.openhands'

    result.mkdir(parents=True, exist_ok=True)
    return result


def get_default_web_url() -> str | None:
    """Get legacy web host parameter.

    If present, we assume we are running under https."""
    web_host = os.getenv('WEB_HOST')
    if not web_host:
        return None
    return f'https://{web_host}'


def _get_default_lifespan():
    # Check legacy parameters for saas mode. If we are in SAAS mode do not apply
    # OSS alembic migrations
    if 'saas' in (os.getenv('OPENHANDS_CONFIG_CLS') or '').lower():
        return None
    return OssAppLifespanService()


class AppServerConfig(OpenHandsModel):
    persistence_dir: Path = Field(default_factory=get_default_persistence_dir)
    web_url: str | None = Field(
        default_factory=get_default_web_url,
        description='The URL where OpenHands is running (e.g., http://localhost:3000)',
    )
    # Dependency Injection Injectors
    event: EventServiceInjector | None = None
    event_callback: EventCallbackServiceInjector | None = None
    sandbox: SandboxServiceInjector | None = None
    sandbox_spec: SandboxSpecServiceInjector | None = None
    app_conversation_info: AppConversationInfoServiceInjector | None = None
    app_conversation_start_task: AppConversationStartTaskServiceInjector | None = None
    app_conversation: AppConversationServiceInjector | None = None
    user: UserContextInjector | None = None
    jwt: JwtServiceInjector | None = None
    httpx: HttpxClientInjector = Field(default_factory=HttpxClientInjector)
    db_session: DbSessionInjector = Field(
        default_factory=lambda: DbSessionInjector(
            persistence_dir=get_default_persistence_dir()
        )
    )

    # Services
    lifespan: AppLifespanService = Field(default_factory=_get_default_lifespan)


def config_from_env() -> AppServerConfig:
    # Import defaults...
    from openhands.app_server.app_conversation.live_status_app_conversation_service import (  # noqa: E501
        LiveStatusAppConversationServiceInjector,
    )
    from openhands.app_server.app_conversation.sql_app_conversation_info_service import (  # noqa: E501
        SQLAppConversationInfoServiceInjector,
    )
    from openhands.app_server.app_conversation.sql_app_conversation_start_task_service import (  # noqa: E501
        SQLAppConversationStartTaskServiceInjector,
    )
    from openhands.app_server.event.filesystem_event_service import (
        FilesystemEventServiceInjector,
    )
    from openhands.app_server.event_callback.sql_event_callback_service import (
        SQLEventCallbackServiceInjector,
    )
    from openhands.app_server.sandbox.docker_sandbox_service import (
        DockerSandboxServiceInjector,
    )
    from openhands.app_server.sandbox.docker_sandbox_spec_service import (
        DockerSandboxSpecServiceInjector,
    )
    from openhands.app_server.sandbox.process_sandbox_service import (
        ProcessSandboxServiceInjector,
    )
    from openhands.app_server.sandbox.process_sandbox_spec_service import (
        ProcessSandboxSpecServiceInjector,
    )
    from openhands.app_server.sandbox.remote_sandbox_service import (
        RemoteSandboxServiceInjector,
    )
    from openhands.app_server.sandbox.remote_sandbox_spec_service import (
        RemoteSandboxSpecServiceInjector,
    )
    from openhands.app_server.user.auth_user_context import (
        AuthUserContextInjector,
    )

    config: AppServerConfig = from_env(AppServerConfig, 'OH')  # type: ignore

    if config.event is None:
        config.event = FilesystemEventServiceInjector()

    if config.event_callback is None:
        config.event_callback = SQLEventCallbackServiceInjector()

    if config.sandbox is None:
        # Legacy fallback
        if os.getenv('RUNTIME') == 'remote':
            config.sandbox = RemoteSandboxServiceInjector(
                api_key=os.environ['SANDBOX_API_KEY'],
                api_url=os.environ['SANDBOX_REMOTE_RUNTIME_API_URL'],
            )
        elif os.getenv('RUNTIME') in ('local', 'process'):
            config.sandbox = ProcessSandboxServiceInjector()
        else:
            config.sandbox = DockerSandboxServiceInjector()

    if config.sandbox_spec is None:
        if os.getenv('RUNTIME') == 'remote':
            config.sandbox_spec = RemoteSandboxSpecServiceInjector()
        elif os.getenv('RUNTIME') in ('local', 'process'):
            config.sandbox_spec = ProcessSandboxSpecServiceInjector()
        else:
            config.sandbox_spec = DockerSandboxSpecServiceInjector()

    if config.app_conversation_info is None:
        config.app_conversation_info = SQLAppConversationInfoServiceInjector()

    if config.app_conversation_start_task is None:
        config.app_conversation_start_task = (
            SQLAppConversationStartTaskServiceInjector()
        )

    if config.app_conversation is None:
        config.app_conversation = LiveStatusAppConversationServiceInjector()

    if config.user is None:
        config.user = AuthUserContextInjector()

    if config.jwt is None:
        config.jwt = JwtServiceInjector(persistence_dir=config.persistence_dir)

    return config


_global_config: AppServerConfig | None = None


def get_global_config() -> AppServerConfig:
    """Get the default local server config shared across the server."""
    global _global_config
    if _global_config is None:
        # Load configuration from environment...
        _global_config = config_from_env()

    return _global_config  # type: ignore


def get_event_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[EventService]:
    injector = get_global_config().event
    assert injector is not None
    return injector.context(state, request)


def get_event_callback_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[EventCallbackService]:
    injector = get_global_config().event_callback
    assert injector is not None
    return injector.context(state, request)


def get_sandbox_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[SandboxService]:
    injector = get_global_config().sandbox
    assert injector is not None
    return injector.context(state, request)


def get_sandbox_spec_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[SandboxSpecService]:
    injector = get_global_config().sandbox_spec
    assert injector is not None
    return injector.context(state, request)


def get_app_conversation_info_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[AppConversationInfoService]:
    injector = get_global_config().app_conversation_info
    assert injector is not None
    return injector.context(state, request)


def get_app_conversation_start_task_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[AppConversationStartTaskService]:
    injector = get_global_config().app_conversation_start_task
    assert injector is not None
    return injector.context(state, request)


def get_app_conversation_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[AppConversationService]:
    injector = get_global_config().app_conversation
    assert injector is not None
    return injector.context(state, request)


def get_user_context(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[UserContext]:
    injector = get_global_config().user
    assert injector is not None
    return injector.context(state, request)


def get_httpx_client(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[httpx.AsyncClient]:
    return get_global_config().httpx.context(state, request)


def get_jwt_service(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[JwtService]:
    injector = get_global_config().jwt
    assert injector is not None
    return injector.context(state, request)


def get_db_session(
    state: InjectorState, request: Request | None = None
) -> AsyncContextManager[AsyncSession]:
    return get_global_config().db_session.context(state, request)


def get_app_lifespan_service() -> AppLifespanService:
    config = get_global_config()
    return config.lifespan


def depends_event_service():
    injector = get_global_config().event
    assert injector is not None
    return Depends(injector.depends)


def depends_event_callback_service():
    injector = get_global_config().event_callback
    assert injector is not None
    return Depends(injector.depends)


def depends_sandbox_service():
    injector = get_global_config().sandbox
    assert injector is not None
    return Depends(injector.depends)


def depends_sandbox_spec_service():
    injector = get_global_config().sandbox_spec
    assert injector is not None
    return Depends(injector.depends)


def depends_app_conversation_info_service():
    injector = get_global_config().app_conversation_info
    assert injector is not None
    return Depends(injector.depends)


def depends_app_conversation_start_task_service():
    injector = get_global_config().app_conversation_start_task
    assert injector is not None
    return Depends(injector.depends)


def depends_app_conversation_service():
    injector = get_global_config().app_conversation
    assert injector is not None
    return Depends(injector.depends)


def depends_user_context():
    injector = get_global_config().user
    assert injector is not None
    return Depends(injector.depends)


def depends_httpx_client():
    return Depends(get_global_config().httpx.depends)


def depends_jwt_service():
    injector = get_global_config().jwt
    assert injector is not None
    return Depends(injector.depends)


def depends_db_session():
    return Depends(get_global_config().db_session.depends)
