from typing import Dict, List, Literal, Optional

from pydantic import ConfigDict, TypeAdapter
from typing_extensions import NotRequired, TypedDict


class TextCompletionModel(TypedDict):
    id: str
    name: str


class EnterpriseAgentItem(TypedDict):
    id: str
    projectKey: str
    type: NotRequired[Optional[str]]
    name: str
    sourceAgentName: NotRequired[Optional[str]]
    description: NotRequired[Optional[str]]
    exampleQuestions: NotRequired[Optional[List[str]]]
    additionalInstructions: NotRequired[Optional[str]]
    allowInsightsCreation: NotRequired[Optional[bool]]
    storiesWorkspace: NotRequired[Optional[str]]
    pluginAgentType: NotRequired[Optional[str]]


class ConversationStarterExample(TypedDict):
    id: str
    label: str
    query: str
    enterpriseAgents: List[str]


class AllowedEmbedRule(TypedDict, total=False):
    pathPrefix: str
    previewLabel: str
    showPreviewUrl: bool
    queryPolicy: Literal["none", "allow-list", "any"]
    allowedQueryParameters: List[str]


class AllowedEmbedOrigin(TypedDict, total=False):
    origin: str
    rules: List[AllowedEmbedRule]
    excludedPathPrefixes: List[str]


class PromptAttributes(TypedDict, total=False):
    enabled: bool


class ConfigMeta(TypedDict):
    etag: NotRequired[Optional[str]]


class _AdminConfigFields(TypedDict):
    version: NotRequired[Optional[int]]

    required: NotRequired[Optional[List[str]]]

    agentHubLLM: NotRequired[Optional[str]]
    agentHubOptionalInstructions: NotRequired[Optional[str]]
    orchestrationMode: NotRequired[Optional[Literal["tools", "manual"]]]
    docGenerationEnabled: NotRequired[Optional[bool]]
    orchestratorLLM: NotRequired[str | None]

    aswChatLLM: NotRequired[Optional[str]]
    aswChatOptionalInstructions: NotRequired[Optional[str]]

    smartMode: NotRequired[Optional[bool]]
    permanentlyDeleteMessages: NotRequired[Optional[bool]]
    dkuCallerTicket: NotRequired[Optional[bool]]
    advancedSettingsEnabled: NotRequired[Optional[bool]]
    includeUserAgentInContext: NotRequired[Optional[bool]]
    includeClientIPInContext: NotRequired[Optional[bool]]
    passOriginalRequestHeadersInContext: NotRequired[Optional[bool]]
    allowDisableAgents: NotRequired[Optional[bool]]
    enableByoForAgents: NotRequired[Optional[bool]]
    guardrailsEnabled: NotRequired[Optional[bool]]
    guardrailsPattern: NotRequired[Optional[str]]
    logsLevel: NotRequired[Optional[Literal["info", "debug"]]]
    conversationStarterExamples: NotRequired[Optional[List[ConversationStarterExample]]]
    allowedEmbedOriginsEnabled: NotRequired[Optional[bool]]
    allowedEmbedOrigins: NotRequired[Optional[List[AllowedEmbedOrigin]]]

    enableDocumentUpload: NotRequired[Optional[bool]]
    extractionMode: NotRequired[Optional[Literal["pagesScreenshots", "pagesText"]]]
    textExtractionType: NotRequired[Optional[Literal["IGNORE", "OCR", "VLM_ANNOTATE"]]]
    imageValidation: NotRequired[Optional[bool]]
    maxImagesInConversation: NotRequired[Optional[int]]
    uploadManagedFolder: NotRequired[Optional[str]]
    conversationVisionLLM: NotRequired[Optional[str]]

    myAgentsEnabled: NotRequired[Optional[bool]]
    myAgentsTextCompletionModels: NotRequired[Optional[List[TextCompletionModel]]]
    myAgentsEmbeddingModel: NotRequired[Optional[str]]
    myAgentsVisionLLM: NotRequired[Optional[str]]
    myAgentsFsConnection: NotRequired[Optional[str]]
    myAgentsSharepointConnection: NotRequired[Optional[str]]
    myAgentsSharepointSyncInterval: NotRequired[Optional[int]]
    myAgentsSharepointSyncFrequency: NotRequired[Optional[Literal["hourly", "daily", "weekly", "monthly"]]]
    myAgentsAllowSharepointAgentSharing: NotRequired[Optional[bool]]
    myAgentsFolder: NotRequired[Optional[str]]
    myAgentsNumDocs: NotRequired[Optional[int]]
    myAgentsMaxUploadSizeMb: NotRequired[Optional[int]]
    myAgentsManagedTools: NotRequired[Optional[List[str]]]
    myAgentsEnablePromptLibrary: NotRequired[Optional[bool]]
    myAgentsEnableChatWithTabularData: NotRequired[Optional[bool]]
    myAgentsSemModelsSQLConnection: NotRequired[Optional[str]]
    myAgentsSemModelsLLM: NotRequired[Optional[str]]
    myAgentsSemModelsInteractionMode: NotRequired[Optional[Literal["agentic", "simple"]]]
    myAgentsEnableDatasetSearchFromWorkspaces: NotRequired[Optional[bool]]
    myAgentsEnableDatasetSearchFromCatalogs: NotRequired[Optional[bool]]
    myAgentsUpdateSemModelsEndUserSQLExecution: NotRequired[Optional[bool]]

    enterpriseAgents: NotRequired[Optional[List[EnterpriseAgentItem]]]

    chartsGenerationMode: NotRequired[Optional[Literal["auto", "manual", "disabled"]]]
    chartsTextCompletionModel: NotRequired[Optional[str]]
    chartsMaxArtifactsSize: NotRequired[Optional[int]]
    chartsAdditionalInstructions: NotRequired[Optional[str]]

    promptsAttributesById: NotRequired[Optional[Dict[str, PromptAttributes]]]
    promptsAssetLibraryCollectionIds: NotRequired[Optional[List[str]]]

    myAgentsEnableGroupsRestriction: NotRequired[Optional[bool]]
    myAgentsExcludedShareGroups: NotRequired[Optional[List[str]]]

    leftPanelLogoPath: NotRequired[Optional[str]]
    homepageImagePath: NotRequired[Optional[str]]
    homepageTitle: NotRequired[Optional[str]]
    chatFooterText: NotRequired[Optional[str]]
    themeColor: NotRequired[Optional[str]]
    themeColorMode: NotRequired[Optional[str]]
    customCssPath: NotRequired[Optional[str]]
    customFontEnabled: NotRequired[Optional[bool]]
    adminUploadsManagedFolder: NotRequired[Optional[str]]

    # legacy:
    # kept here so configs saved by older clients don't fail validation with "extra_forbidden"
    _meta: NotRequired[Optional[ConfigMeta]]


class AdminConfig(_AdminConfigFields):
    """Internal/storage shape of the admin config (used as a plain dict throughout the codebase)."""


class AdminConfigUpdate(_AdminConfigFields):
    """
    Request-validation schema for PUT /admin/config.
    """

    # these ignore comments are required for mypy
    __pydantic_config__ = ConfigDict(extra="forbid")  # type: ignore[misc]

    version: int  # type: ignore[misc]


ADMIN_CONFIG_UPDATE_ADAPTER = TypeAdapter(AdminConfigUpdate)


class Prompt(TypedDict):
    id: str
    name: str
    description: str
    prompt: str
    category: str
    tags: List[str]
    isDefault: bool
    createdAt: str
    updatedAt: str


class PromptsLibrary(TypedDict):
    version: str
    prompts: List[Prompt]


def is_field_required(config: Optional[AdminConfig], field_name: str) -> bool:
    if not config:
        return False
    required = config.get("required")
    return field_name in required if required else False


def get_required_fields(config: Optional[AdminConfig]) -> List[str]:
    if not config:
        return []
    return config.get("required") or []


def set_field_required(config: AdminConfig, field_name: str, required: bool) -> None:
    if "required" not in config or config["required"] is None:
        config["required"] = []

    req_list = config["required"]

    if required and field_name not in req_list:
        req_list.append(field_name)
    elif not required and field_name in req_list:
        req_list.remove(field_name)


def add_required_fields(config: AdminConfig, field_names: List[str]) -> None:
    if "required" not in config or config["required"] is None:
        config["required"] = []

    for field_name in field_names:
        if field_name not in config["required"]:
            config["required"].append(field_name)


def remove_required_fields(config: AdminConfig, field_names: List[str]) -> None:
    if "required" not in config or config["required"] is None:
        return

    config["required"] = [f for f in config["required"] if f not in field_names]
