from datetime import timedelta
from enum import StrEnum
from typing import Literal

from pydantic import (
    AliasChoices,
    Field,
    HttpUrl,
    NegativeInt,
    NonNegativeInt,
    PositiveFloat,
    PositiveInt,
    computed_field,
)
from pydantic_settings import BaseSettings

from .hosted_service import HostedServiceConfig


class SecurityConfig(BaseSettings):
    """
    Security-related configurations for the application
    """

    SECRET_KEY: str = Field(
        description="Secret key for secure session cookie signing."
        "Make sure you are changing this key for your deployment with a strong key."
        "Generate a strong key using `openssl rand -base64 42` or set via the `SECRET_KEY` environment variable.",
        default="",
    )

    RESET_PASSWORD_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
        description="Duration in minutes for which a password reset token remains valid",
        default=5,
    )

    EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
        description="Duration in minutes for which a email register token remains valid",
        default=5,
    )

    CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
        description="Duration in minutes for which a change email token remains valid",
        default=5,
    )

    OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
        description="Duration in minutes for which a owner transfer token remains valid",
        default=5,
    )

    WEB_FORM_SUBMIT_RATE_LIMIT_MAX_ATTEMPTS: PositiveInt = Field(
        description="Maximum number of web form submissions allowed per IP within the rate limit window",
        default=30,
    )

    WEB_FORM_SUBMIT_RATE_LIMIT_WINDOW_SECONDS: PositiveInt = Field(
        description="Time window in seconds for web form submission rate limiting",
        default=60,
    )

    LOGIN_DISABLED: bool = Field(
        description="Whether to disable login checks",
        default=False,
    )

    ADMIN_API_KEY_ENABLE: bool = Field(
        description="Whether to enable admin api key for authentication",
        default=False,
    )

    ADMIN_API_KEY: str | None = Field(
        description="admin api key for authentication",
        default=None,
    )


class AppExecutionConfig(BaseSettings):
    """
    Configuration parameters for application execution
    """

    APP_MAX_EXECUTION_TIME: PositiveInt = Field(
        description="Maximum allowed execution time for the application in seconds",
        default=1200,
    )
    APP_DEFAULT_ACTIVE_REQUESTS: NonNegativeInt = Field(
        description="Default number of concurrent active requests per app (0 for unlimited)",
        default=0,
    )
    APP_MAX_ACTIVE_REQUESTS: NonNegativeInt = Field(
        description="Maximum number of concurrent active requests per app (0 for unlimited)",
        default=0,
    )

    HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS: PositiveInt = Field(
        description="Maximum seconds a workflow run can stay paused waiting for human input before global timeout.",
        default=int(timedelta(days=7).total_seconds()),
        ge=1,
    )


class CodeExecutionSandboxConfig(BaseSettings):
    """
    Configuration for the code execution sandbox environment
    """

    CODE_EXECUTION_ENDPOINT: HttpUrl = Field(
        description="URL endpoint for the code execution service",
        default=HttpUrl("http://sandbox:8194"),
    )

    CODE_EXECUTION_API_KEY: str = Field(
        description="API key for accessing the code execution service",
        default="dify-sandbox",
    )

    CODE_EXECUTION_CONNECT_TIMEOUT: float | None = Field(
        description="Connection timeout in seconds for code execution requests",
        default=10.0,
    )

    CODE_EXECUTION_READ_TIMEOUT: float | None = Field(
        description="Read timeout in seconds for code execution requests",
        default=60.0,
    )

    CODE_EXECUTION_WRITE_TIMEOUT: float | None = Field(
        description="Write timeout in seconds for code execution request",
        default=10.0,
    )

    CODE_EXECUTION_POOL_MAX_CONNECTIONS: PositiveInt = Field(
        description="Maximum number of concurrent connections for the code execution HTTP client",
        default=100,
    )

    CODE_EXECUTION_POOL_MAX_KEEPALIVE_CONNECTIONS: PositiveInt = Field(
        description="Maximum number of persistent keep-alive connections for the code execution HTTP client",
        default=20,
    )

    CODE_EXECUTION_POOL_KEEPALIVE_EXPIRY: PositiveFloat | None = Field(
        description="Keep-alive expiry in seconds for idle connections (set to None to disable)",
        default=5.0,
    )

    CODE_MAX_NUMBER: PositiveInt = Field(
        description="Maximum allowed numeric value in code execution",
        default=9223372036854775807,
    )

    CODE_MIN_NUMBER: NegativeInt = Field(
        description="Minimum allowed numeric value in code execution",
        default=-9223372036854775807,
    )

    CODE_MAX_DEPTH: PositiveInt = Field(
        description="Maximum allowed depth for nested structures in code execution",
        default=5,
    )

    CODE_MAX_PRECISION: PositiveInt = Field(
        description="Maximum number of decimal places for floating-point numbers in code execution",
        default=20,
    )

    CODE_MAX_STRING_LENGTH: PositiveInt = Field(
        description="Maximum allowed length for strings in code execution",
        default=400_000,
    )

    CODE_MAX_STRING_ARRAY_LENGTH: PositiveInt = Field(
        description="Maximum allowed length for string arrays in code execution",
        default=30,
    )

    CODE_MAX_OBJECT_ARRAY_LENGTH: PositiveInt = Field(
        description="Maximum allowed length for object arrays in code execution",
        default=30,
    )

    CODE_MAX_NUMBER_ARRAY_LENGTH: PositiveInt = Field(
        description="Maximum allowed length for numeric arrays in code execution",
        default=1000,
    )

    CODE_EXECUTION_SSL_VERIFY: bool = Field(
        description="Enable or disable SSL verification for code execution requests",
        default=True,
    )


class TriggerConfig(BaseSettings):
    """
    Configuration for trigger
    """

    WEBHOOK_REQUEST_BODY_MAX_SIZE: PositiveInt = Field(
        description="Maximum allowed size for webhook request bodies in bytes",
        default=10485760,
    )


class AsyncWorkflowConfig(BaseSettings):
    """
    Configuration for async workflow
    """

    ASYNC_WORKFLOW_SCHEDULER_GRANULARITY: int = Field(
        description="Granularity for async workflow scheduler, "
        "sometime, few users could block the queue due to some time-consuming tasks, "
        "to avoid this, workflow can be suspended if needed, to achieve"
        "this, a time-based checker is required, every granularity seconds, "
        "the checker will check the workflow queue and suspend the workflow",
        default=120,
        ge=1,
    )


class PluginConfig(BaseSettings):
    """
    Plugin configs
    """

    PLUGIN_DAEMON_URL: HttpUrl = Field(
        description="Plugin API URL",
        default=HttpUrl("http://localhost:5002"),
    )

    PLUGIN_DAEMON_KEY: str = Field(
        description="Plugin API key",
        default="plugin-api-key",
    )

    PLUGIN_DAEMON_TIMEOUT: PositiveFloat | None = Field(
        description="Timeout in seconds for requests to the plugin daemon (set to None to disable)",
        default=600.0,
    )

    INNER_API_KEY_FOR_PLUGIN: str = Field(description="Inner api key for plugin", default="inner-api-key")

    PLUGIN_REMOTE_INSTALL_HOST: str = Field(
        description="Plugin Remote Install Host",
        default="localhost",
    )

    PLUGIN_REMOTE_INSTALL_PORT: PositiveInt = Field(
        description="Plugin Remote Install Port",
        default=5003,
    )

    PLUGIN_MAX_PACKAGE_SIZE: PositiveInt = Field(
        description="Maximum allowed size for plugin packages in bytes",
        default=15728640,
    )

    PLUGIN_MAX_BUNDLE_SIZE: PositiveInt = Field(
        description="Maximum allowed size for plugin bundles in bytes",
        default=15728640 * 12,
    )

    PLUGIN_MODEL_SCHEMA_CACHE_TTL: PositiveInt = Field(
        description="TTL in seconds for caching plugin model schemas in Redis",
        default=60 * 60,
    )

    PLUGIN_MAX_FILE_SIZE: PositiveInt = Field(
        description="Maximum allowed size (bytes) for plugin-generated files",
        default=50 * 1024 * 1024,
    )


class MarketplaceConfig(BaseSettings):
    """
    Configuration for marketplace
    """

    MARKETPLACE_ENABLED: bool = Field(
        description="Enable or disable marketplace",
        default=True,
    )

    MARKETPLACE_API_URL: HttpUrl = Field(
        description="Marketplace API URL",
        default=HttpUrl("https://marketplace.dify.ai"),
    )


class EndpointConfig(BaseSettings):
    """
    Configuration for various application endpoints and URLs
    """

    CONSOLE_API_URL: str = Field(
        description="Base URL for the console API,"
        "used for login authentication callback or notion integration callbacks",
        default="",
    )

    CONSOLE_WEB_URL: str = Field(
        description="Base URL for the console web interface,used for frontend references and CORS configuration",
        default="",
    )

    SERVICE_API_URL: str = Field(
        description="Base URL for the service API, displayed to users for API access",
        default="",
    )

    APP_WEB_URL: str = Field(
        description="Base URL for the web application, used for frontend references",
        default="",
    )

    ENDPOINT_URL_TEMPLATE: str = Field(
        description="Template url for endpoint plugin", default="http://localhost:5002/e/{hook_id}"
    )

    TRIGGER_URL: str = Field(description="Template url for triggers", default="http://localhost:5001")


class FileAccessConfig(BaseSettings):
    """
    Configuration for file access and handling
    """

    FILES_URL: str = Field(
        description="Base URL for file preview or download,"
        " used for frontend display and multi-model inputs"
        "Url is signed and has expiration time.",
        validation_alias=AliasChoices("FILES_URL", "CONSOLE_API_URL"),
        alias_priority=1,
        default="",
    )

    INTERNAL_FILES_URL: str = Field(
        description="Internal base URL for file access within Docker network,"
        " used for plugin daemon and internal service communication."
        " Falls back to FILES_URL if not specified.",
        default="",
    )

    FILES_ACCESS_TIMEOUT: int = Field(
        description="Expiration time in seconds for file access URLs",
        default=300,
    )


class FileUploadConfig(BaseSettings):
    """
    Configuration for file upload limitations
    """

    UPLOAD_FILE_SIZE_LIMIT: NonNegativeInt = Field(
        description="Maximum allowed file size for uploads in megabytes",
        default=15,
    )

    UPLOAD_FILE_BATCH_LIMIT: NonNegativeInt = Field(
        description="Maximum number of files allowed in a single upload batch",
        default=5,
    )

    UPLOAD_IMAGE_FILE_SIZE_LIMIT: NonNegativeInt = Field(
        description="Maximum allowed image file size for uploads in megabytes",
        default=10,
    )

    UPLOAD_VIDEO_FILE_SIZE_LIMIT: NonNegativeInt = Field(
        description="video file size limit in Megabytes for uploading files",
        default=100,
    )

    UPLOAD_AUDIO_FILE_SIZE_LIMIT: NonNegativeInt = Field(
        description="audio file size limit in Megabytes for uploading files",
        default=50,
    )

    BATCH_UPLOAD_LIMIT: NonNegativeInt = Field(
        description="Maximum number of files allowed in a batch upload operation",
        default=20,
    )

    WORKFLOW_FILE_UPLOAD_LIMIT: PositiveInt = Field(
        description="Maximum number of files allowed in a workflow upload operation",
        default=10,
    )

    IMAGE_FILE_BATCH_LIMIT: PositiveInt = Field(
        description="Maximum number of files allowed in a image batch upload operation",
        default=10,
    )

    SINGLE_CHUNK_ATTACHMENT_LIMIT: PositiveInt = Field(
        description="Maximum number of files allowed in a single chunk attachment",
        default=10,
    )

    ATTACHMENT_IMAGE_FILE_SIZE_LIMIT: NonNegativeInt = Field(
        description="Maximum allowed image file size for attachments in megabytes",
        default=2,
    )

    ATTACHMENT_IMAGE_DOWNLOAD_TIMEOUT: NonNegativeInt = Field(
        description="Timeout for downloading image attachments in seconds",
        default=60,
    )

    # Annotation Import Security Configurations
    ANNOTATION_IMPORT_FILE_SIZE_LIMIT: NonNegativeInt = Field(
        description="Maximum allowed CSV file size for annotation import in megabytes",
        default=2,
    )

    ANNOTATION_IMPORT_MAX_RECORDS: PositiveInt = Field(
        description="Maximum number of annotation records allowed in a single import",
        default=10000,
    )

    ANNOTATION_IMPORT_MIN_RECORDS: PositiveInt = Field(
        description="Minimum number of annotation records required in a single import",
        default=1,
    )

    ANNOTATION_IMPORT_RATE_LIMIT_PER_MINUTE: PositiveInt = Field(
        description="Maximum number of annotation import requests per minute per tenant",
        default=5,
    )

    ANNOTATION_IMPORT_RATE_LIMIT_PER_HOUR: PositiveInt = Field(
        description="Maximum number of annotation import requests per hour per tenant",
        default=20,
    )

    ANNOTATION_IMPORT_MAX_CONCURRENT: PositiveInt = Field(
        description="Maximum number of concurrent annotation import tasks per tenant",
        default=2,
    )

    inner_UPLOAD_FILE_EXTENSION_BLACKLIST: str = Field(
        description=(
            "Comma-separated list of file extensions that are blocked from upload. "
            "Extensions should be lowercase without dots (e.g., 'exe,bat,sh,dll'). "
            "Empty by default to allow all file types."
        ),
        validation_alias=AliasChoices("UPLOAD_FILE_EXTENSION_BLACKLIST"),
        default="",
    )

    @computed_field  # type: ignore[misc]
    @property
    def UPLOAD_FILE_EXTENSION_BLACKLIST(self) -> set[str]:
        """
        Parse and return the blacklist as a set of lowercase extensions.
        Returns an empty set if no blacklist is configured.
        """
        if not self.inner_UPLOAD_FILE_EXTENSION_BLACKLIST:
            return set()
        return {
            ext.strip().lower().strip(".")
            for ext in self.inner_UPLOAD_FILE_EXTENSION_BLACKLIST.split(",")
            if ext.strip()
        }


class HttpConfig(BaseSettings):
    """
    HTTP-related configurations for the application
    """

    COOKIE_DOMAIN: str = Field(
        description="Explicit cookie domain for console/service cookies when sharing across subdomains",
        default="",
    )

    API_COMPRESSION_ENABLED: bool = Field(
        description="Enable or disable gzip compression for HTTP responses",
        default=False,
    )

    inner_CONSOLE_CORS_ALLOW_ORIGINS: str = Field(
        description="Comma-separated list of allowed origins for CORS in the console",
        validation_alias=AliasChoices("CONSOLE_CORS_ALLOW_ORIGINS", "CONSOLE_WEB_URL"),
        default="",
    )

    @computed_field
    def CONSOLE_CORS_ALLOW_ORIGINS(self) -> list[str]:
        return self.inner_CONSOLE_CORS_ALLOW_ORIGINS.split(",")

    inner_WEB_API_CORS_ALLOW_ORIGINS: str = Field(
        description="",
        validation_alias=AliasChoices("WEB_API_CORS_ALLOW_ORIGINS"),
        default="*",
    )

    @computed_field
    def WEB_API_CORS_ALLOW_ORIGINS(self) -> list[str]:
        return self.inner_WEB_API_CORS_ALLOW_ORIGINS.split(",")

    HTTP_REQUEST_MAX_CONNECT_TIMEOUT: int = Field(
        ge=1, description="Maximum connection timeout in seconds for HTTP requests", default=10
    )

    HTTP_REQUEST_MAX_READ_TIMEOUT: int = Field(
        ge=1, description="Maximum read timeout in seconds for HTTP requests", default=600
    )

    HTTP_REQUEST_MAX_WRITE_TIMEOUT: int = Field(
        ge=1, description="Maximum write timeout in seconds for HTTP requests", default=600
    )

    HTTP_REQUEST_NODE_MAX_BINARY_SIZE: PositiveInt = Field(
        description="Maximum allowed size in bytes for binary data in HTTP requests",
        default=10 * 1024 * 1024,
    )

    HTTP_REQUEST_NODE_MAX_TEXT_SIZE: PositiveInt = Field(
        description="Maximum allowed size in bytes for text data in HTTP requests",
        default=1 * 1024 * 1024,
    )

    HTTP_REQUEST_NODE_SSL_VERIFY: bool = Field(
        description="Enable or disable SSL verification for HTTP requests",
        default=True,
    )

    SSRF_DEFAULT_MAX_RETRIES: PositiveInt = Field(
        description="Maximum number of retries for network requests (SSRF)",
        default=3,
    )

    SSRF_PROXY_ALL_URL: str | None = Field(
        description="Proxy URL for HTTP or HTTPS requests to prevent Server-Side Request Forgery (SSRF)",
        default=None,
    )

    SSRF_PROXY_HTTP_URL: str | None = Field(
        description="Proxy URL for HTTP requests to prevent Server-Side Request Forgery (SSRF)",
        default=None,
    )

    SSRF_PROXY_HTTPS_URL: str | None = Field(
        description="Proxy URL for HTTPS requests to prevent Server-Side Request Forgery (SSRF)",
        default=None,
    )

    SSRF_DEFAULT_TIME_OUT: PositiveFloat = Field(
        description="The default timeout period used for network requests (SSRF)",
        default=5,
    )

    SSRF_DEFAULT_CONNECT_TIME_OUT: PositiveFloat = Field(
        description="The default connect timeout period used for network requests (SSRF)",
        default=5,
    )

    SSRF_DEFAULT_READ_TIME_OUT: PositiveFloat = Field(
        description="The default read timeout period used for network requests (SSRF)",
        default=5,
    )

    SSRF_DEFAULT_WRITE_TIME_OUT: PositiveFloat = Field(
        description="The default write timeout period used for network requests (SSRF)",
        default=5,
    )

    SSRF_POOL_MAX_CONNECTIONS: PositiveInt = Field(
        description="Maximum number of concurrent connections for the SSRF HTTP client",
        default=100,
    )

    SSRF_POOL_MAX_KEEPALIVE_CONNECTIONS: PositiveInt = Field(
        description="Maximum number of persistent keep-alive connections for the SSRF HTTP client",
        default=20,
    )

    SSRF_POOL_KEEPALIVE_EXPIRY: PositiveFloat | None = Field(
        description="Keep-alive expiry in seconds for idle SSRF connections (set to None to disable)",
        default=5.0,
    )

    RESPECT_XFORWARD_HEADERS_ENABLED: bool = Field(
        description="Enable handling of X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Port headers"
        " when the app is behind a single trusted reverse proxy.",
        default=False,
    )


class InnerAPIConfig(BaseSettings):
    """
    Configuration for internal API functionality
    """

    INNER_API: bool = Field(
        description="Enable or disable the internal API",
        default=False,
    )

    INNER_API_KEY: str | None = Field(
        description="API key for accessing the internal API",
        default=None,
    )


class LoggingConfig(BaseSettings):
    """
    Configuration for application logging
    """

    LOG_LEVEL: str = Field(
        description="Logging level, default to INFO. Set to ERROR for production environments.",
        default="INFO",
    )

    LOG_OUTPUT_FORMAT: Literal["text", "json"] = Field(
        description="Log output format: 'text' for human-readable, 'json' for structured JSON logs.",
        default="text",
    )

    LOG_FILE: str | None = Field(
        description="File path for log output.",
        default=None,
    )

    LOG_FILE_MAX_SIZE: PositiveInt = Field(
        description="Maximum file size for file rotation retention, the unit is megabytes (MB)",
        default=20,
    )

    LOG_FILE_BACKUP_COUNT: PositiveInt = Field(
        description="Maximum file backup count file rotation retention",
        default=5,
    )

    LOG_FORMAT: str = Field(
        description="Format string for log messages",
        default=(
            "%(asctime)s.%(msecs)03d %(levelname)s [%(threadName)s] "
            "[%(filename)s:%(lineno)d] %(trace_id)s - %(message)s"
        ),
    )

    LOG_DATEFORMAT: str | None = Field(
        description="Date format string for log timestamps",
        default=None,
    )

    LOG_TZ: str | None = Field(
        description="Timezone for log timestamps (e.g., 'America/New_York')",
        default="UTC",
    )


class ModelLoadBalanceConfig(BaseSettings):
    """
    Configuration for model load balancing and token counting
    """

    MODEL_LB_ENABLED: bool = Field(
        description="Enable or disable load balancing for models",
        default=False,
    )

    PLUGIN_BASED_TOKEN_COUNTING_ENABLED: bool = Field(
        description="Enable or disable plugin based token counting. If disabled, token counting will return 0.",
        default=False,
    )


class BillingConfig(BaseSettings):
    """
    Configuration for platform billing features
    """

    BILLING_ENABLED: bool = Field(
        description="Enable or disable billing functionality",
        default=False,
    )


class UpdateConfig(BaseSettings):
    """
    Configuration for application update checks
    """

    CHECK_UPDATE_URL: str = Field(
        description="URL to check for application updates",
        default="https://updates.dify.ai",
    )


class WorkflowVariableTruncationConfig(BaseSettings):
    WORKFLOW_VARIABLE_TRUNCATION_MAX_SIZE: PositiveInt = Field(
        # 1000 KiB
        1024_000,
        description="Maximum size for variable to trigger final truncation.",
    )
    WORKFLOW_VARIABLE_TRUNCATION_STRING_LENGTH: PositiveInt = Field(
        100000,
        description="maximum length for string to trigger tuncation, measure in number of characters",
    )
    WORKFLOW_VARIABLE_TRUNCATION_ARRAY_LENGTH: PositiveInt = Field(
        1000,
        description="maximum length for array to trigger truncation.",
    )


class WorkflowConfig(BaseSettings):
    """
    Configuration for workflow execution
    """

    WORKFLOW_MAX_EXECUTION_STEPS: PositiveInt = Field(
        description="Maximum number of steps allowed in a single workflow execution",
        default=500,
    )

    WORKFLOW_MAX_EXECUTION_TIME: PositiveInt = Field(
        description="Maximum execution time in seconds for a single workflow",
        default=1200,
    )

    WORKFLOW_CALL_MAX_DEPTH: PositiveInt = Field(
        description="Maximum allowed depth for nested workflow calls",
        default=5,
    )

    MAX_VARIABLE_SIZE: PositiveInt = Field(
        description="Maximum size in bytes for a single variable in workflows. Default to 200 KB.",
        default=200 * 1024,
    )

    TEMPLATE_TRANSFORM_MAX_LENGTH: PositiveInt = Field(
        description="Maximum number of characters allowed in Template Transform node output",
        default=400_000,
    )

    # GraphEngine Worker Pool Configuration
    GRAPH_ENGINE_MIN_WORKERS: PositiveInt = Field(
        description="Minimum number of workers per GraphEngine instance",
        default=1,
    )

    GRAPH_ENGINE_MAX_WORKERS: PositiveInt = Field(
        description="Maximum number of workers per GraphEngine instance",
        default=10,
    )

    GRAPH_ENGINE_SCALE_UP_THRESHOLD: PositiveInt = Field(
        description="Queue depth threshold that triggers worker scale up",
        default=3,
    )

    GRAPH_ENGINE_SCALE_DOWN_IDLE_TIME: float = Field(
        description="Seconds of idle time before scaling down workers",
        default=5.0,
        ge=0.1,
    )


class WorkflowNodeExecutionConfig(BaseSettings):
    """
    Configuration for workflow node execution
    """

    MAX_SUBMIT_COUNT: PositiveInt = Field(
        description="Maximum number of submitted thread count in a ThreadPool for parallel node execution",
        default=100,
    )

    WORKFLOW_NODE_EXECUTION_STORAGE: str = Field(
        default="rdbms",
        description="Storage backend for WorkflowNodeExecution. Options: 'rdbms', 'hybrid'",
    )


class RepositoryConfig(BaseSettings):
    """
    Configuration for repository implementations
    """

    CORE_WORKFLOW_EXECUTION_REPOSITORY: str = Field(
        description="Repository implementation for WorkflowExecution. Options: "
        "'core.repositories.sqlalchemy_workflow_execution_repository.SQLAlchemyWorkflowExecutionRepository' (default), "
        "'core.repositories.celery_workflow_execution_repository.CeleryWorkflowExecutionRepository'",
        default="core.repositories.sqlalchemy_workflow_execution_repository.SQLAlchemyWorkflowExecutionRepository",
    )

    CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY: str = Field(
        description="Repository implementation for WorkflowNodeExecution. Options: "
        "'core.repositories.sqlalchemy_workflow_node_execution_repository."
        "SQLAlchemyWorkflowNodeExecutionRepository' (default), "
        "'core.repositories.celery_workflow_node_execution_repository."
        "CeleryWorkflowNodeExecutionRepository'",
        default="core.repositories.sqlalchemy_workflow_node_execution_repository.SQLAlchemyWorkflowNodeExecutionRepository",
    )

    API_WORKFLOW_NODE_EXECUTION_REPOSITORY: str = Field(
        description="Service-layer repository implementation for WorkflowNodeExecutionModel operations. "
        "Specify as a module path",
        default="repositories.sqlalchemy_api_workflow_node_execution_repository.DifyAPISQLAlchemyWorkflowNodeExecutionRepository",
    )

    API_WORKFLOW_RUN_REPOSITORY: str = Field(
        description="Service-layer repository implementation for WorkflowRun operations. Specify as a module path",
        default="repositories.sqlalchemy_api_workflow_run_repository.DifyAPISQLAlchemyWorkflowRunRepository",
    )


class AuthConfig(BaseSettings):
    """
    Configuration for authentication and OAuth
    """

    OAUTH_REDIRECT_PATH: str = Field(
        description="Redirect path for OAuth authentication callbacks",
        default="/console/api/oauth/authorize",
    )

    GITHUB_CLIENT_ID: str | None = Field(
        description="GitHub OAuth client ID",
        default=None,
    )

    GITHUB_CLIENT_SECRET: str | None = Field(
        description="GitHub OAuth client secret",
        default=None,
    )

    GOOGLE_CLIENT_ID: str | None = Field(
        description="Google OAuth client ID",
        default=None,
    )

    GOOGLE_CLIENT_SECRET: str | None = Field(
        description="Google OAuth client secret",
        default=None,
    )

    ACCESS_TOKEN_EXPIRE_MINUTES: PositiveInt = Field(
        description="Expiration time for access tokens in minutes",
        default=60,
    )

    REFRESH_TOKEN_EXPIRE_DAYS: PositiveFloat = Field(
        description="Expiration time for refresh tokens in days",
        default=30,
    )

    LOGIN_LOCKOUT_DURATION: PositiveInt = Field(
        description="Time (in seconds) a user must wait before retrying login after exceeding the rate limit.",
        default=86400,
    )

    FORGOT_PASSWORD_LOCKOUT_DURATION: PositiveInt = Field(
        description="Time (in seconds) a user must wait before retrying password reset after exceeding the rate limit.",
        default=86400,
    )

    CHANGE_EMAIL_LOCKOUT_DURATION: PositiveInt = Field(
        description="Time (in seconds) a user must wait before retrying change email after exceeding the rate limit.",
        default=86400,
    )

    OWNER_TRANSFER_LOCKOUT_DURATION: PositiveInt = Field(
        description="Time (in seconds) a user must wait before retrying owner transfer after exceeding the rate limit.",
        default=86400,
    )

    EMAIL_REGISTER_LOCKOUT_DURATION: PositiveInt = Field(
        description="Time (in seconds) a user must wait before retrying email register after exceeding the rate limit.",
        default=86400,
    )


class ModerationConfig(BaseSettings):
    """
    Configuration for content moderation
    """

    MODERATION_BUFFER_SIZE: PositiveInt = Field(
        description="Size of the buffer for content moderation processing",
        default=300,
    )


class ToolConfig(BaseSettings):
    """
    Configuration for tool management
    """

    TOOL_ICON_CACHE_MAX_AGE: PositiveInt = Field(
        description="Maximum age in seconds for caching tool icons",
        default=3600,
    )


class TemplateMode(StrEnum):
    # unsafe mode allows flexible operations in templates, but may cause security vulnerabilities
    UNSAFE = "unsafe"

    # sandbox mode restricts some unsafe operations like accessing __class__.
    # however, it is still not 100% safe, for example, cpu exploitation can happen.
    SANDBOX = "sandbox"

    # templating is disabled
    DISABLED = "disabled"


class MailConfig(BaseSettings):
    """
    Configuration for email services
    """

    MAIL_TEMPLATING_MODE: TemplateMode = Field(
        description="Template mode for email services",
        default=TemplateMode.SANDBOX,
    )

    MAIL_TEMPLATING_TIMEOUT: int = Field(
        description="""
        Timeout for email templating in seconds. Used to prevent infinite loops in malicious templates.
        Only available in sandbox mode.""",
        default=3,
    )

    MAIL_TYPE: str | None = Field(
        description="Email service provider type ('smtp' or 'resend' or 'sendGrid), default to None.",
        default=None,
    )

    MAIL_DEFAULT_SEND_FROM: str | None = Field(
        description="Default email address to use as the sender",
        default=None,
    )

    RESEND_API_KEY: str | None = Field(
        description="API key for Resend email service",
        default=None,
    )

    RESEND_API_URL: str | None = Field(
        description="API URL for Resend email service",
        default=None,
    )

    SMTP_SERVER: str | None = Field(
        description="SMTP server hostname",
        default=None,
    )

    SMTP_PORT: int | None = Field(
        description="SMTP server port number",
        default=465,
    )

    SMTP_USERNAME: str | None = Field(
        description="Username for SMTP authentication",
        default=None,
    )

    SMTP_PASSWORD: str | None = Field(
        description="Password for SMTP authentication",
        default=None,
    )

    SMTP_USE_TLS: bool = Field(
        description="Enable TLS encryption for SMTP connections",
        default=False,
    )

    SMTP_OPPORTUNISTIC_TLS: bool = Field(
        description="Enable opportunistic TLS for SMTP connections",
        default=False,
    )

    SMTP_LOCAL_HOSTNAME: str | None = Field(
        description="Override the local hostname used in SMTP HELO/EHLO. "
        "Useful behind NAT or when the default hostname causes rejections.",
        default=None,
    )

    EMAIL_SEND_IP_LIMIT_PER_MINUTE: PositiveInt = Field(
        description="Maximum number of emails allowed to be sent from the same IP address in a minute",
        default=50,
    )

    SENDGRID_API_KEY: str | None = Field(
        description="API key for SendGrid service",
        default=None,
    )

    ENABLE_TRIAL_APP: bool = Field(
        description="Enable trial app",
        default=False,
    )

    ENABLE_EXPLORE_BANNER: bool = Field(
        description="Enable explore banner",
        default=False,
    )


class RagEtlConfig(BaseSettings):
    """
    Configuration for RAG ETL processes
    """

    # TODO: This config is not only for rag etl, it is also for file upload, we should move it to file upload config
    ETL_TYPE: str = Field(
        description="RAG ETL type ('dify' or 'Unstructured'), default to 'dify'",
        default="dify",
    )

    KEYWORD_DATA_SOURCE_TYPE: str = Field(
        description="Data source type for keyword extraction"
        " ('database' or other supported types), default to 'database'",
        default="database",
    )

    UNSTRUCTURED_API_URL: str | None = Field(
        description="API URL for Unstructured.io service",
        default=None,
    )

    UNSTRUCTURED_API_KEY: str | None = Field(
        description="API key for Unstructured.io service",
        default="",
    )

    SCARF_NO_ANALYTICS: str | None = Field(
        description="This is about whether to disable Scarf analytics in Unstructured library.",
        default="false",
    )


class DataSetConfig(BaseSettings):
    """
    Configuration for dataset management
    """

    PLAN_SANDBOX_CLEAN_DAY_SETTING: PositiveInt = Field(
        description="Interval in days for dataset cleanup operations - plan: sandbox",
        default=30,
    )

    PLAN_PRO_CLEAN_DAY_SETTING: PositiveInt = Field(
        description="Interval in days for dataset cleanup operations - plan: pro and team",
        default=7,
    )

    DATASET_OPERATOR_ENABLED: bool = Field(
        description="Enable or disable dataset operator functionality",
        default=False,
    )

    TIDB_SERVERLESS_NUMBER: PositiveInt = Field(
        description="number of tidb serverless cluster",
        default=500,
    )

    CREATE_TIDB_SERVICE_JOB_ENABLED: bool = Field(
        description="Enable or disable create tidb service job",
        default=False,
    )

    PLAN_SANDBOX_CLEAN_MESSAGE_DAY_SETTING: PositiveInt = Field(
        description="Interval in days for message cleanup operations - plan: sandbox",
        default=30,
    )

    DSL_EXPORT_ENCRYPT_DATASET_ID: bool = Field(
        description="Enable or disable dataset ID encryption when exporting DSL files",
        default=True,
    )

    DATASET_MAX_SEGMENTS_PER_REQUEST: NonNegativeInt = Field(
        description="Maximum number of segments for dataset segments API (0 for unlimited)",
        default=0,
    )


class WorkspaceConfig(BaseSettings):
    """
    Configuration for workspace management
    """

    INVITE_EXPIRY_HOURS: PositiveInt = Field(
        description="Expiration time in hours for workspace invitation links",
        default=72,
    )


class IndexingConfig(BaseSettings):
    """
    Configuration for indexing operations
    """

    INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: PositiveInt = Field(
        description="Maximum token length for text segmentation during indexing",
        default=4000,
    )

    CHILD_CHUNKS_PREVIEW_NUMBER: PositiveInt = Field(
        description="Maximum number of child chunks to preview",
        default=50,
    )


class MultiModalTransferConfig(BaseSettings):
    MULTIMODAL_SEND_FORMAT: Literal["base64", "url"] = Field(
        description="Format for sending files in multimodal contexts ('base64' or 'url'), default is base64",
        default="base64",
    )


class CeleryBeatConfig(BaseSettings):
    CELERY_BEAT_SCHEDULER_TIME: int = Field(
        description="Interval in days for Celery Beat scheduler execution, default to 1 day",
        default=1,
    )


class CeleryScheduleTasksConfig(BaseSettings):
    ENABLE_CLEAN_EMBEDDING_CACHE_TASK: bool = Field(
        description="Enable clean embedding cache task",
        default=False,
    )
    ENABLE_CLEAN_UNUSED_DATASETS_TASK: bool = Field(
        description="Enable clean unused datasets task",
        default=False,
    )
    ENABLE_CREATE_TIDB_SERVERLESS_TASK: bool = Field(
        description="Enable create tidb service job task",
        default=False,
    )
    ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK: bool = Field(
        description="Enable update tidb service job status task",
        default=False,
    )
    ENABLE_CLEAN_MESSAGES: bool = Field(
        description="Enable clean messages task",
        default=False,
    )
    ENABLE_WORKFLOW_RUN_CLEANUP_TASK: bool = Field(
        description="Enable scheduled workflow run cleanup task",
        default=False,
    )
    ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK: bool = Field(
        description="Enable mail clean document notify task",
        default=False,
    )
    ENABLE_DATASETS_QUEUE_MONITOR: bool = Field(
        description="Enable queue monitor task",
        default=False,
    )
    ENABLE_HUMAN_INPUT_TIMEOUT_TASK: bool = Field(
        description="Enable human input timeout check task",
        default=True,
    )
    HUMAN_INPUT_TIMEOUT_TASK_INTERVAL: PositiveInt = Field(
        description="Human input timeout check interval in minutes",
        default=1,
    )
    ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK: bool = Field(
        description="Enable check upgradable plugin task",
        default=True,
    )
    ENABLE_WORKFLOW_SCHEDULE_POLLER_TASK: bool = Field(
        description="Enable workflow schedule poller task",
        default=True,
    )
    WORKFLOW_SCHEDULE_POLLER_INTERVAL: int = Field(
        description="Workflow schedule poller interval in minutes",
        default=1,
    )
    WORKFLOW_SCHEDULE_POLLER_BATCH_SIZE: int = Field(
        description="Maximum number of schedules to process in each poll batch",
        default=100,
    )
    WORKFLOW_SCHEDULE_MAX_DISPATCH_PER_TICK: int = Field(
        description="Maximum schedules to dispatch per tick (0=unlimited, circuit breaker)",
        default=0,
    )

    # API token last_used_at batch update
    ENABLE_API_TOKEN_LAST_USED_UPDATE_TASK: bool = Field(
        description="Enable periodic batch update of API token last_used_at timestamps",
        default=True,
    )
    API_TOKEN_LAST_USED_UPDATE_INTERVAL: int = Field(
        description="Interval in minutes for batch updating API token last_used_at (default 30)",
        default=30,
    )

    # Trigger provider refresh (simple version)
    ENABLE_TRIGGER_PROVIDER_REFRESH_TASK: bool = Field(
        description="Enable trigger provider refresh poller",
        default=True,
    )
    TRIGGER_PROVIDER_REFRESH_INTERVAL: int = Field(
        description="Trigger provider refresh poller interval in minutes",
        default=1,
    )
    TRIGGER_PROVIDER_REFRESH_BATCH_SIZE: int = Field(
        description="Max trigger subscriptions to process per tick",
        default=200,
    )
    TRIGGER_PROVIDER_CREDENTIAL_THRESHOLD_SECONDS: int = Field(
        description="Proactive credential refresh threshold in seconds",
        default=60 * 60,
    )
    TRIGGER_PROVIDER_SUBSCRIPTION_THRESHOLD_SECONDS: int = Field(
        description="Proactive subscription refresh threshold in seconds",
        default=60 * 60,
    )


class PositionConfig(BaseSettings):
    POSITION_PROVIDER_PINS: str = Field(
        description="Comma-separated list of pinned model providers",
        default="",
    )

    POSITION_PROVIDER_INCLUDES: str = Field(
        description="Comma-separated list of included model providers",
        default="",
    )

    POSITION_PROVIDER_EXCLUDES: str = Field(
        description="Comma-separated list of excluded model providers",
        default="",
    )

    POSITION_TOOL_PINS: str = Field(
        description="Comma-separated list of pinned tools",
        default="",
    )

    POSITION_TOOL_INCLUDES: str = Field(
        description="Comma-separated list of included tools",
        default="",
    )

    POSITION_TOOL_EXCLUDES: str = Field(
        description="Comma-separated list of excluded tools",
        default="",
    )

    @property
    def POSITION_PROVIDER_PINS_LIST(self) -> list[str]:
        return [item.strip() for item in self.POSITION_PROVIDER_PINS.split(",") if item.strip() != ""]

    @property
    def POSITION_PROVIDER_INCLUDES_SET(self) -> set[str]:
        return {item.strip() for item in self.POSITION_PROVIDER_INCLUDES.split(",") if item.strip() != ""}

    @property
    def POSITION_PROVIDER_EXCLUDES_SET(self) -> set[str]:
        return {item.strip() for item in self.POSITION_PROVIDER_EXCLUDES.split(",") if item.strip() != ""}

    @property
    def POSITION_TOOL_PINS_LIST(self) -> list[str]:
        return [item.strip() for item in self.POSITION_TOOL_PINS.split(",") if item.strip() != ""]

    @property
    def POSITION_TOOL_INCLUDES_SET(self) -> set[str]:
        return {item.strip() for item in self.POSITION_TOOL_INCLUDES.split(",") if item.strip() != ""}

    @property
    def POSITION_TOOL_EXCLUDES_SET(self) -> set[str]:
        return {item.strip() for item in self.POSITION_TOOL_EXCLUDES.split(",") if item.strip() != ""}


class LoginConfig(BaseSettings):
    ENABLE_EMAIL_CODE_LOGIN: bool = Field(
        description="whether to enable email code login",
        default=False,
    )
    ENABLE_EMAIL_PASSWORD_LOGIN: bool = Field(
        description="whether to enable email password login",
        default=True,
    )
    ENABLE_SOCIAL_OAUTH_LOGIN: bool = Field(
        description="whether to enable github/google oauth login",
        default=False,
    )
    EMAIL_CODE_LOGIN_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
        description="expiry time in minutes for email code login token",
        default=5,
    )
    ALLOW_REGISTER: bool = Field(
        description="whether to enable register",
        default=False,
    )
    ALLOW_CREATE_WORKSPACE: bool = Field(
        description="whether to enable create workspace",
        default=False,
    )


class AccountConfig(BaseSettings):
    ACCOUNT_DELETION_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
        description="Duration in minutes for which a account deletion token remains valid",
        default=5,
    )

    EDUCATION_ENABLED: bool = Field(
        description="whether to enable education identity",
        default=False,
    )


class WorkflowLogConfig(BaseSettings):
    WORKFLOW_LOG_CLEANUP_ENABLED: bool = Field(default=False, description="Enable workflow run log cleanup")
    WORKFLOW_LOG_RETENTION_DAYS: int = Field(default=30, description="Retention days for workflow run logs")
    WORKFLOW_LOG_CLEANUP_BATCH_SIZE: int = Field(
        default=100, description="Batch size for workflow run log cleanup operations"
    )
    WORKFLOW_LOG_CLEANUP_SPECIFIC_WORKFLOW_IDS: str = Field(
        default="", description="Comma-separated list of workflow IDs to clean logs for"
    )


class SwaggerUIConfig(BaseSettings):
    SWAGGER_UI_ENABLED: bool = Field(
        description="Whether to enable Swagger UI in api module",
        default=True,
    )

    SWAGGER_UI_PATH: str = Field(
        description="Swagger UI page path in api module",
        default="/swagger-ui.html",
    )


class TenantIsolatedTaskQueueConfig(BaseSettings):
    TENANT_ISOLATED_TASK_CONCURRENCY: int = Field(
        description="Number of tasks allowed to be delivered concurrently from isolated queue per tenant",
        default=1,
    )


class SandboxExpiredRecordsCleanConfig(BaseSettings):
    SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD: NonNegativeInt = Field(
        description="Graceful period in days for sandbox records clean after subscription expiration",
        default=21,
    )
    SANDBOX_EXPIRED_RECORDS_CLEAN_BATCH_SIZE: PositiveInt = Field(
        description="Maximum number of records to process in each batch",
        default=1000,
    )
    SANDBOX_EXPIRED_RECORDS_CLEAN_BATCH_MAX_INTERVAL: PositiveInt = Field(
        description="Maximum interval in milliseconds between batches",
        default=200,
    )
    SANDBOX_EXPIRED_RECORDS_RETENTION_DAYS: PositiveInt = Field(
        description="Retention days for sandbox expired workflow_run records and message records",
        default=30,
    )
    SANDBOX_EXPIRED_RECORDS_CLEAN_TASK_LOCK_TTL: PositiveInt = Field(
        description="Lock TTL for sandbox expired records clean task in seconds",
        default=90000,
    )


class FeatureConfig(
    # place the configs in alphabet order
    AppExecutionConfig,
    AuthConfig,  # Changed from OAuthConfig to AuthConfig
    BillingConfig,
    CodeExecutionSandboxConfig,
    TriggerConfig,
    AsyncWorkflowConfig,
    PluginConfig,
    MarketplaceConfig,
    DataSetConfig,
    EndpointConfig,
    FileAccessConfig,
    FileUploadConfig,
    HttpConfig,
    InnerAPIConfig,
    IndexingConfig,
    LoggingConfig,
    MailConfig,
    ModelLoadBalanceConfig,
    ModerationConfig,
    MultiModalTransferConfig,
    PositionConfig,
    RagEtlConfig,
    RepositoryConfig,
    SandboxExpiredRecordsCleanConfig,
    SecurityConfig,
    TenantIsolatedTaskQueueConfig,
    ToolConfig,
    UpdateConfig,
    WorkflowConfig,
    WorkflowNodeExecutionConfig,
    WorkspaceConfig,
    LoginConfig,
    AccountConfig,
    SwaggerUIConfig,
    # hosted services config
    HostedServiceConfig,
    CeleryBeatConfig,
    CeleryScheduleTasksConfig,
    WorkflowLogConfig,
    WorkflowVariableTruncationConfig,
):
    pass
