from __future__ import annotations

import functools
import os
from datetime import datetime
from typing import Dict, List, Optional, Set

import dataiku
from dataikuapi.dss.project import DSSProject
from typing_extensions import TypedDict

from backend.constants import DEFAULT_EXTRACTION_MODE, TEXT_EMBEDDING, CloudConnections
from backend.utils.agents_utils import filter_agents_per_user
from backend.utils.api_clients import AH_GLOBAL_API_CLIENT
from backend.utils.embed_url_utils import normalize_origin
from backend.utils.general_utils import time_cached
from backend.utils.local_dev import is_local_dev
from backend.utils.logging_utils import get_logger
from backend.utils.project_utils import list_project_agent_tools

logger = get_logger(__name__)


class AgentDetails(TypedDict):
    agent_id: str
    name: str
    agent_system_instructions: Optional[str]
    tool_agent_description: Optional[str]
    stories_workspace: Optional[str]
    agent_short_example_queries: Optional[List[str]]
    agent_example_queries: Optional[List[str]]
    pluginAgentType: Optional[str]


class ToolDetails(TypedDict):
    dss_id: str
    name: str
    description: Optional[str]
    short_description: Optional[str]
    type: Optional[str]
    configured: Optional[bool]


# Cache the single project key for the life of this process.
@functools.lru_cache(maxsize=1)
def get_project_key():
    if is_local_dev():
        return os.getenv("DKU_CURRENT_PROJECT_KEY")
    else:
        return dataiku.default_project_key()


def _token_for_item(item: dict) -> Optional[str]:
    """Build a legacy token like '{projectKey}:{type}:{id}'."""
    _id = item.get("id")
    _type = item.get("type") or "agent"
    _proj = item.get("projectKey")
    if _id and _proj:
        return f"{_proj}:{_type}:{_id}"
    return None


def _agent_refs_to_tokens(agent_refs: List[str], items: List[dict]) -> List[str]:
    """Convert agent refs to legacy 'PROJ:type:ID' tokens.

    Handles both the legacy bare-ID format and the current composite 'PROJ:agent:ID' format,
    so existing DB records continue to resolve correctly after the format change.
    """
    tokens = []
    for ref in agent_refs:
        if not isinstance(ref, str) or not ref:
            continue
        if ":agent:" in ref:
            proj_key, _, agent_id = ref.partition(":agent:")
            item = next((it for it in items if it.get("id") == agent_id and it.get("projectKey") == proj_key), None)
        else:
            item = next((it for it in items if it.get("id") == ref), None)
        if item:
            tok = _token_for_item(item)
            if tok:
                tokens.append(tok)
    return tokens


def _get_log_level() -> str:
    """
    Get log level from webapp config.
    Returns 'INFO' as default if not configured.
    """
    try:
        from dataiku.customwebapp import get_webapp_config

        params = get_webapp_config()
        return params.get("log_level", "INFO").upper()
    except Exception:
        return "INFO"


def get_config() -> dict:
    """
    ConvertAdminConfig (returned by get_config_v2) back to the legacy webapp
    'config' shape used by the application code.
    """
    flat = get_config_v2() or {}

    # --- Helpers -------------------------------------------------------------

    def mode_v2_to_old(mode: Optional[str]) -> str:
        return {"auto": "AUTO", "manual": "ON_DEMAND", "disabled": "NONE"}.get((mode or "auto").lower(), "AUTO")

    # --- LLMs ---------------------------------------------------------------

    config: Dict[str, object] = {}

    text_models = flat.get("myAgentsTextCompletionModels") or []
    config["LLMs"] = [{"llm_id": m.get("id")} for m in text_models if isinstance(m, dict) and m.get("id")]

    embedding_id = flat.get("myAgentsEmbeddingModel")
    if embedding_id:
        config["embedding_llm"] = embedding_id  # prefer the single-value legacy key

    config["default_llm_id"] = flat.get("agentHubLLM")
    if is_answers_mode_on():
        config["aswChatLLM"] = flat.get("aswChatLLM")

    # In legacy, this field existed and could be empty string
    config["globalSystemPrompt"] = flat.get("agentHubOptionalInstructions", "") or ""
    config["orchestration_mode"] = flat.get("orchestrationMode", "tools")

    # --- Enterprise Agents (tool & augmented LLMs) --------------------------

    tool_agent_cfgs: List[dict] = []
    augmented_llm_cfgs: List[dict] = []
    agents_ids: List[str] = []
    augmented_llms_ids: List[str] = []
    project_keys: Set[str] = set()

    items: List[dict] = flat.get("enterpriseAgents") or []

    for it in items:
        tok = _token_for_item(it)
        if not tok:
            # Cannot construct a valid legacy id; skip this item.
            continue

        # Collect project key for top-level projects list
        parts = tok.split(":", 2)
        if len(parts) == 3:
            project_keys.add(parts[0])

        _type = it.get("type") or "agent"
        name = it.get("name") or ""
        desc = it.get("description") or ""
        instr = it.get("additionalInstructions") or ""
        examples = it.get("exampleQuestions") or []
        allow_stories = bool(it.get("allowInsightsCreation", False))
        stories_ws = it.get("storiesWorkspace")

        if _type == "agent":
            agents_ids.append(tok)
            cfg = {
                "agent_id": tok,
                "tool_agent_display_name": name,
                "tool_agent_description": desc,
                "agent_system_instructions": instr,
                "agent_example_queries": examples,
                "enable_stories": allow_stories,
            }
            if allow_stories and stories_ws:
                cfg["stories_workspace"] = stories_ws
            tool_agent_cfgs.append(cfg)
        else:
            augmented_llms_ids.append(tok)
            cfg = {
                "augmented_llm_id": tok,
                "augmented_llm_display_name": name,
                "augmented_llm_description": desc,
                "augmented_llm_system_instructions": instr,
                "augllm_example_queries": examples,
            }
            # Note: legacy schema for augmented LLMs did not include stories settings
            # and there is no reliable source for "short" examples in v2.
            augmented_llm_cfgs.append(cfg)

    config["tool_agent_configurations"] = tool_agent_cfgs
    config["augmented_llms_configurations"] = augmented_llm_cfgs
    config["projects_keys"] = sorted(project_keys)
    config["agents_ids"] = agents_ids
    config["augmented_llms_ids"] = augmented_llms_ids

    # --- Charts / Visualization --------------------------------------------

    config["visualization_generation_mode"] = mode_v2_to_old(flat.get("chartsGenerationMode"))
    charts_llm = flat.get("chartsTextCompletionModel")
    if charts_llm:
        # Support both historical keys, some instances used one or the other
        config["charts_generation_llm_id"] = charts_llm
        config["graph_generation_llm_id"] = charts_llm
    if flat.get("chartsMaxArtifactsSize") is not None:
        config["max_artifacts_size_mb"] = flat.get("chartsMaxArtifactsSize")
    config["charts_prompt"] = flat.get("chartsAdditionalInstructions")
    # --- My Agents (User Agents) --------------------------------------------

    config["enable_quick_agents"] = bool(flat.get("myAgentsEnabled", False))
    config["enable_prompt_library"] = bool(flat.get("myAgentsEnablePromptLibrary", False))
    if "myAgentsFsConnection" in flat:
        config["default_fs_connection"] = flat.get("myAgentsFsConnection")
    if "myAgentsSharepointConnection" in flat:
        config["sharepoint_connection"] = flat.get("myAgentsSharepointConnection")
    if "myAgentsSharepointSyncInterval" in flat:
        config["sharepoint_sync_interval"] = flat.get("myAgentsSharepointSyncInterval") or 1
    if "myAgentsSharepointSyncFrequency" in flat:
        config["sharepoint_sync_frequency"] = flat.get("myAgentsSharepointSyncFrequency") or "daily"
    config["allow_sharepoint_agent_sharing"] = bool(flat.get("myAgentsAllowSharepointAgentSharing", False))
    if "myAgentsFolder" in flat:
        config["agents_folder"] = flat.get("myAgentsFolder")
    if "myAgentsNumDocs" in flat:
        config["number_of_documents_to_retrieve"] = flat.get("myAgentsNumDocs")
    if "myAgentsMaxUploadSizeMb" in flat:
        config["my_agents_max_upload_size_mb"] = flat.get("myAgentsMaxUploadSizeMb")
    if "myAgentsManagedTools" in flat:
        config["tools"] = flat.get("myAgentsManagedTools") or []

    # --- General Application Settings ---------------------------------------

    config["enable_app_smart_mode"] = bool(flat.get("smartMode", False))
    config["guardrails_enabled"] = bool(flat.get("guardrailsEnabled", False))
    config["guardrails_pattern"] = flat.get("guardrailsPattern", "")
    config["permanent_delete_messages"] = bool(flat.get("permanentlyDeleteMessages", False))
    config["logLevel"] = _get_log_level()

    legacy_examples: List[dict] = []
    for ex in flat.get("conversationStarterExamples") or []:
        legacy_examples.append(
            {
                "homepage_example_label": ex.get("label") or "",
                "homepage_example_query": ex.get("query") or "",
                "selected_agents": _agent_refs_to_tokens(ex.get("enterpriseAgents") or [], items),
            }
        )
    if legacy_examples:
        config["homepage_examples"] = legacy_examples

    config["allow_chat_to_llm"] = bool(flat.get("allowDisableAgents", True))
    config["extraction_mode"] = str(flat.get("extractionMode", "pagesScreenshots"))  # pagesText
    config["text_extraction_type"] = str(flat.get("textExtractionType", "IGNORE"))
    config["image_validation"] = bool(flat.get("imageValidation", True))
    config["quota_images_per_conversation"] = int(flat.get("maxImagesInConversation", 50))
    config["enable_document_upload"] = bool(flat.get("enableDocumentUpload", True))
    config["enable_byo_for_agents"] = bool(flat.get("enableByoForAgents", True))
    config["enable_groups_restriction_for_sharing"] = bool(flat.get("myAgentsEnableGroupsRestriction", False))
    config["exclude_groups_for_agent_sharing"] = flat.get("myAgentsExcludedShareGroups", [])
    # Uploads managed folder
    upload_folder = flat.get("uploadManagedFolder")
    if upload_folder:
        config["uploads"] = upload_folder
    # Admin uploads managed folder (for white label assets)
    admin_upload_folder = flat.get("adminUploadsManagedFolder")
    if admin_upload_folder:
        config["admin_uploads"] = admin_upload_folder
    return config


def get_config_v2() -> dict:
    from backend.services.admin_config_service import get_cached_config

    config = get_cached_config()
    return config


def get_project_agents(current_project: DSSProject) -> List[Dict[str, str]]:
    agents_map = get_project_agent_mapping(current_project)
    return [
        {
            "value": current_project.project_key + ":" + agent_id,
            "label": agent_name,
            "description": "",
        }
        for agent_id, agent_name in agents_map.items()
    ]


def get_project_agent_mapping(current_project: DSSProject):
    return {
        llm.get("id"): llm.get("friendlyName")
        for llm in current_project.list_llms()
        if llm.get("id").startswith("agent")
    }


def list_agents_by_project(selected_projects) -> Dict[str, List[Dict[str, str]]]:
    client = dataiku.api_client()
    agents_by_project: Dict[str, List[Dict[str, str]]] = {}
    if not selected_projects:
        return agents_by_project
    for project_key in selected_projects:
        project_ob: DSSProject = client.get_project(project_key)
        agents = get_project_agents(project_ob)
        if agents:
            agents_by_project[project_key] = agents
    return agents_by_project


def get_enterprise_agent_last_modified(
    project_id: str, object_id: str, is_augmented_llm: bool
) -> tuple[str | None, str | None]:
    project = AH_GLOBAL_API_CLIENT.get_project(project_id)

    obj = project.get_retrieval_augmented_llm(object_id) if is_augmented_llm else project.get_agent(object_id)

    raw = obj.get_settings().get_raw()

    active = raw.get("activeVersion")
    versions_by_id = {v["versionId"]: v for v in raw.get("versions", [])}

    v = versions_by_id.get(active)

    last_modified_by, last_modified_on = None, None

    if v:
        tag = v.get("versionTag") or v.get("creationTag")
        if tag:
            last_modified_by = ((tag.get("lastModifiedBy") or {}).get("login")) or None
            try:
                ts = tag["lastModifiedOn"] / 1000.0
                last_modified_on = datetime.fromtimestamp(ts).strftime("%Y-%m-%dT%H:%M:%S.%f")
            except Exception:
                last_modified_on = None
    return last_modified_by, last_modified_on


def get_enterprise_agents(user: str):
    """
    WARNING: This function MUST ONLY be called if the "user" parameter is the
    same as the currently authenticated user, due to usage of WebappImpersonationContext in filter_agents_per_user  which relies on
    the current web session for impersonation.
    """
    cfg = get_config_v2()
    items = cfg.get("enterpriseAgents", []) or []
    if not items:
        return []

    # Filter by user access
    ids = [f"{it.get('projectKey')}:agent:{it.get('id')}" for it in items if it.get("id")]
    allowed_ids = set(filter_agents_per_user(user, ids)) if ids else set()

    available_agents: List[AgentDetails] = []
    for it in items:
        it_id = f"{it.get('projectKey')}:agent:{it.get('id')}"
        if not it_id or it_id not in allowed_ids:
            continue
        name = it.get("name") or it.get("sourceAgentName") or it_id
        agent_dict = {
            "id": it_id,
            "name": name,
            "agent_system_instructions": it.get("additionalInstructions", ""),
            "tool_agent_description": it.get("description", ""),
            "stories_workspace": it.get("storiesWorkspace"),
            "agent_example_queries": it.get("exampleQuestions", []) or [],
        }
        plugin_agent_type = it.get("pluginAgentType")
        if plugin_agent_type:
            agent_dict["pluginAgentType"] = plugin_agent_type
        available_agents.append(agent_dict)
    return available_agents


def agents_as_tools() -> bool:
    """
    Whether the orchestrator LLM can call agents as tools.
    """
    cfg = get_config_v2()
    return cfg.get("orchestrationMode", "tools") == "tools"


def get_charts_instructions() -> Optional[str]:
    """
    Get additional instructions for charts generation, if configured.
    """
    cfg = get_config_v2()
    return cfg.get("chartsAdditionalInstructions")


def get_tools_details(user: str) -> list[ToolDetails]:
    if not user:
        raise ValueError("A user must be provided to get tool details.")

    ids = tuple(sorted((get_config_v2().get("myAgentsManagedTools") or [])))
    if not ids:
        return []

    ids_set = set(ids)
    logger.info(f"Building structured tool for {user}")
    project = dataiku.api_client().get_default_project()
    tool_details = []

    for tool_def in list_project_agent_tools(project):
        tool_id = tool_def["id"]
        if tool_id not in ids_set:
            continue
        tool_id = tool_def["id"]
        if tool_id not in ids_set:
            continue

        tool_type = tool_def.get("type")
        tool_name = tool_def.get("name")
        tool_description = tool_def.get("description", "")
        short_description = tool_def.get("shortDesc", "")
        project_key = tool_def.get("projectKey")

        tool_details.append(
            {
                "dss_id": f"{project_key}:{tool_id}",
                "name": tool_name,
                "description": tool_description,
                "short_description": short_description,
                "type": tool_type,
                "configured": True,
            }
        )
        logger.debug(f"tool_details {tool_id} ({tool_details})")
    return list(tool_details)


# -------------------------------------------------------------------- #
# LLM helpers
# -------------------------------------------------------------------- #
@time_cached(ttl_seconds=30)
def _llm_friendly_names_by_purpose(purpose: str) -> Dict[str, str]:
    project = AH_GLOBAL_API_CLIENT.get_default_project()
    return {llm.id: llm.get("friendlyName") for llm in project.list_llms(purpose)}


@time_cached(ttl_seconds=30)
def _llm_friendly_short_names_by_purpose(purpose: str) -> Dict[str, str]:
    project = AH_GLOBAL_API_CLIENT.get_default_project()
    return {llm.id: llm.get("friendlyNameShort") for llm in project.list_llms(purpose)}


def get_llms(purpose="GENERIC_COMPLETION") -> list[dict]:
    id_to_name = _llm_friendly_names_by_purpose(purpose)
    out: List[dict] = []

    cfg = get_config_v2()

    if purpose == TEXT_EMBEDDING:
        emb_id = cfg.get("myAgentsEmbeddingModel")
        if emb_id:
            out.append({"id": emb_id, "name": id_to_name.get(emb_id, emb_id)})
        return out

    # Text completion models
    tc_models = cfg.get("myAgentsTextCompletionModels") or []
    # Support both list[str] and list[dict]
    for entry in tc_models:
        if isinstance(entry, str):
            llm_id, name = entry, id_to_name.get(entry, entry)
        elif isinstance(entry, dict):
            llm_id = entry.get("id")
            if not llm_id:
                continue
            name = entry.get("name") or id_to_name.get(llm_id, llm_id)
        else:
            continue
        out.append({"id": llm_id, "name": name})
    return out


def get_agent_hub_llm_id() -> str:
    cfg = get_config_v2()
    llm_id = cfg.get("agentHubLLM")
    if llm_id:
        return llm_id
    raise ValueError("No Agent Hub LLM configured")


def get_asw_chat_llm_id() -> str:
    """
    To use in answers mode only
    Return the llm configured by the user to chat with
    """

    cfg = get_config_v2()
    llm_id = cfg.get("aswChatLLM")
    if llm_id:
        return llm_id
    raise ValueError("No Agent Chat chat LLM configured")


def get_default_llm_id() -> str:
    """
    Return the current default base-model id
    in aswmode
        - the conversational llm is the default one
    in ah mode
        - there's only one llm_id (internal) which is the default one
    """
    if is_answers_mode_on():
        return get_asw_chat_llm_id()
    else:
        llm_id = get_agent_hub_llm_id()

        if llm_id is None:
            raise ValueError("No default LLM ID found")
        return llm_id


def get_title_generation_llm() -> str:
    """
    Return the user based llm in AH mode and
    title generation llm in asw mode

    Note :
    in aswmode:
        - asw chat llm id is the  conversational llm
        - default_llm_id (mainllm in AH) is the one for title generation

    in ah mode
        - default_llm_id is the only one for now
    """
    try:
        llm_id = get_agent_hub_llm_id()
    except ValueError:
        llm_id = None

    if llm_id is not None:
        return llm_id

    if is_answers_mode_on():
        return get_asw_chat_llm_id()

    raise ValueError("No title generation llm available")


def get_charts_generation_llm():
    llm_id = get_charts_generation_llm_id()
    if llm_id is None:
        raise ValueError("No Charts Generation LLM ID found")
    return llm_id


def get_charts_generation_llm_id() -> str:
    cfg = get_config_v2()
    llm_id = cfg.get("chartsTextCompletionModel")
    return llm_id or get_default_llm_id()


def get_conversation_vision_llm() -> str:
    """
    Get the Vision LLM model ID for conversation document uploads.
    Falls back to default LLM if not configured.
    """
    cfg = get_config_v2()
    llm_id = cfg.get("conversationVisionLLM")
    return llm_id or get_default_llm_id()


def get_my_agents_vision_llm() -> str:
    """
    Get the Vision LLM model ID for My Agents document indexing.
    Falls back to default LLM if not configured.
    """
    cfg = get_config_v2()
    llm_id = cfg.get("myAgentsVisionLLM")
    return llm_id or get_default_llm_id()


def get_default_embedding_llm() -> str:
    cfg = get_config_v2()
    return cfg.get("myAgentsEmbeddingModel")


def get_enable_upload_documents() -> str:
    return get_config().get("enable_document_upload", False)


def get_enable_byo_for_agents() -> bool:
    return get_config().get("enable_byo_for_agents", True)


def get_uploads_managedfolder_id() -> str:
    return get_config().get("uploads", "")


def get_admin_uploads_managedfolder_id() -> str:
    """Get the managed folder ID for admin uploads (white label assets, etc.)."""
    cfg = get_config_v2()
    folder_id = cfg.get("adminUploadsManagedFolder")
    if isinstance(folder_id, str):
        return folder_id
    return get_config().get("admin_uploads", "")


def get_quota_images_per_conversation() -> int:
    return get_config().get("quota_images_per_conversation", 50)


def get_extraction_mode() -> str:
    """
    Get the document extraction mode.

    Returns:
        DEFAULT_EXTRACTION_MODE (currently ``pagesScreenshots``) for screenshot generation,
        or ``pagesText`` for text-only extraction.
    """
    return get_config().get("extraction_mode", DEFAULT_EXTRACTION_MODE)


def get_guardrails_enabled() -> bool:
    """
    Check if guardrails are enabled.
    """
    return get_config().get("guardrails_enabled", False)


def get_guardrails_pattern() -> str:
    """
    Get the configured guardrails regular expression pattern.
    """
    return get_config().get("guardrails_pattern", "")


def get_text_extraction_type() -> str:
    """
    Get the text extraction type for image handling.

    Returns:
        "IGNORE" (default), "OCR", or "VLM_ANNOTATE"
    """
    return get_config().get("text_extraction_type", "IGNORE")


def get_image_validation() -> bool:
    """Return whether extracted images should be classified/filtered before being passed to the LLM."""
    return get_config().get("image_validation", True)


def get_global_system_prompt() -> str:
    cfg = get_config_v2()
    if is_answers_mode_on():
        return get_asw_chat_system_prompt() or ""
    return cfg.get("agentHubOptionalInstructions", "") or ""


def get_doc_generation_enabled() -> bool:
    """Return whether Agent Hub exposes its generated-document tools to the orchestrator."""
    # Document generation is disabled
    return False


def has_orchestrator_llm() -> bool:
    return bool(get_config_v2().get("orchestratorLLM"))


def get_orchestrator_llm() -> str | None:
    """Return the orchestrator LLM ID, if configured."""
    return get_config_v2().get("orchestratorLLM")


def _normalize_allowed_origin_rules_from_list(rules: list) -> List[dict]:
    normalized_rules = []
    for rule in rules:
        if not isinstance(rule, dict):
            continue
        normalized_rules.append(
            {
                "pathPrefix": rule.get("pathPrefix") or "",
                "previewLabel": rule.get("previewLabel") or "",
                "showPreviewUrl": bool(rule.get("showPreviewUrl", True)),
                "queryPolicy": (
                    rule.get("queryPolicy") if rule.get("queryPolicy") in {"none", "allow-list", "any"} else "none"
                ),
                "allowedQueryParameters": [
                    parameter
                    for parameter in (rule.get("allowedQueryParameters") or [])
                    if isinstance(parameter, str) and parameter
                ],
            }
        )
    return normalized_rules


def _normalize_allowed_origin_rules(entry: dict) -> List[dict]:
    rules = entry.get("rules")
    if isinstance(rules, list):
        return _normalize_allowed_origin_rules_from_list(rules)
    return []


def _normalize_allowed_origin_entry(entry: object) -> Optional[dict]:
    if not isinstance(entry, dict):
        return None
    origin = normalize_origin(entry.get("origin"))
    if origin is None:
        return None
    return {
        "origin": origin.url,
        "rules": _normalize_allowed_origin_rules(entry),
        "excludedPathPrefixes": entry.get("excludedPathPrefixes") or [],
    }


def get_embed_link_settings() -> dict:
    cfg = get_config_v2()

    normalized_allowed_origins = []
    for entry in cfg.get("allowedEmbedOrigins") or []:
        normalized_entry = _normalize_allowed_origin_entry(entry)
        if normalized_entry is not None:
            normalized_allowed_origins.append(normalized_entry)

    return {
        "allowed_origins_enabled": bool(cfg.get("allowedEmbedOriginsEnabled", False)),
        "allowed_origins": normalized_allowed_origins,
    }


def get_asw_chat_system_prompt() -> str:
    cfg = get_config_v2()
    return cfg.get("aswChatOptionalInstructions", "") or ""


def _build_legacy_homepage_examples(examples: list, items: List[dict]) -> List[dict]:
    legacy_examples = []
    for ex in examples or []:
        agent_tokens = _agent_refs_to_tokens(ex.get("enterpriseAgents") or [], items)
        legacy_examples.append(
            {
                "homepage_example_label": ex.get("label") or "",
                "homepage_example_query": ex.get("query") or "",
                "selected_agents": agent_tokens,
            }
        )
    return legacy_examples


def _fetch_sharepoint_connection_details(connection_name: str) -> dict:
    client = dataiku.api_client()
    rs = client.get_connection(connection_name).get_settings().get_raw().get("params", {})
    return {
        "defaultSite": rs.get("defaultManagedSite"),
        "defaultDrive": rs.get("defaultManagedDrive"),
    }


def _build_my_agents_connections(cfg: dict) -> List[dict]:
    connection_name = cfg.get("myAgentsSharepointConnection")
    if not connection_name:
        return []
    sharepoint_connection = {
        "name": connection_name,
        "type": CloudConnections.SHAREPOINT_ONLINE.value,
    }
    try:
        sharepoint_connection.update(_fetch_sharepoint_connection_details(connection_name))
    except Exception as e:
        logger.error(f"Error fetching SharePoint connection details: {e}")
    return [sharepoint_connection]


def get_ui_config() -> dict:
    cfg = get_config_v2()

    items: List[dict] = cfg.get("enterpriseAgents") or []
    legacy_examples = _build_legacy_homepage_examples(cfg.get("conversationStarterExamples", []), items)
    my_agents_connections = _build_my_agents_connections(cfg)

    return {
        "visualization_mode": cfg.get("chartsGenerationMode", "auto"),
        "enable_quick_agents": cfg.get("myAgentsEnabled", True),
        "enable_prompt_library": cfg.get("myAgentsEnablePromptLibrary", True),
        "enable_app_smart_mode": cfg.get("smartMode", True),
        "default_llm": get_default_llm_id(),
        "homepage_examples": legacy_examples,
        "allow_chat_to_llm": cfg.get("allowDisableAgents", True),
        "include_user_agent_in_context": cfg.get("includeUserAgentInContext", False),
        "include_client_ip_in_context": cfg.get("includeClientIPInContext", False),
        "quota_images_per_conversation": cfg.get("maxImagesInConversation", 50),
        "enable_document_upload": cfg.get("enableDocumentUpload", True),
        "doc_generation_enabled": get_doc_generation_enabled(),
        "orchestrator_llm_configured": bool(cfg.get("orchestratorLLM")),
        "enable_byo_for_agents": cfg.get("enableByoForAgents", True),
        "extraction_mode": cfg.get("extractionMode", DEFAULT_EXTRACTION_MODE),
        "text_extraction_type": cfg.get("textExtractionType", "IGNORE"),
        "enable_groups_restriction_for_sharing": cfg.get("myAgentsEnableGroupsRestriction", False),
        "exclude_groups_for_agent_sharing": cfg.get("myAgentsExcludedShareGroups", []),
        "my_agents_max_upload_size_mb": cfg.get("myAgentsMaxUploadSizeMb", 2),
        "my_agents_connections": my_agents_connections,
        "allow_sharepoint_agent_sharing": cfg.get("myAgentsAllowSharepointAgentSharing", False),
        "guardrails_enabled": cfg.get("guardrailsEnabled", False),
        "enable_chat_with_tabular_data": cfg.get("myAgentsEnableChatWithTabularData", False),
        "enable_dataset_search_from_catalogs": cfg.get("myAgentsEnableDatasetSearchFromCatalogs", True),
        "enable_dataset_search_from_workspaces": cfg.get("myAgentsEnableDatasetSearchFromWorkspaces", True),
        "sem_models_interaction_mode": cfg.get("myAgentsSemModelsInteractionMode", "simple"),
        "update_sem_models_end_user_sql_execution": cfg.get("myAgentsUpdateSemModelsEndUserSQLExecution", False),
        "sem_models_llm": cfg.get("myAgentsSemModelsLLM", ""),
        "my_agents_vision_llm": cfg.get("myAgentsVisionLLM", ""),
        "theme": {
            "color": cfg.get("themeColor"),
            "colorMode": cfg.get("themeColorMode", "automatic"),
            "leftPanelLogoPath": cfg.get("leftPanelLogoPath"),
            "homepageImagePath": cfg.get("homepageImagePath"),
            "homepageTitle": cfg.get("homepageTitle"),
            "chatFooterText": cfg.get("chatFooterText"),
            "customCssPath": cfg.get("customCssPath"),
            "assetsManagedFolder": cfg.get("adminUploadsManagedFolder"),
        },
    }


def get_my_agents_max_upload_size_bytes() -> int | None:
    raw_value = get_config().get("my_agents_max_upload_size_mb", 2)
    try:
        max_size_mb = int(raw_value)
    except (TypeError, ValueError):
        return None
    if max_size_mb < 1:
        return None
    return max_size_mb * 1024 * 1024


def is_answers_mode_on() -> bool:
    mode = current_app_mode()
    try:
        return str(mode).lower() == "answers"
    except Exception:
        logger.warning("Invalid APP_MODE value %r; defaulting to 'agenthub' (answers mode off)", mode)
        return False


def current_app_mode() -> str:
    return os.getenv("APP_MODE", "agenthub")
