from functools import lru_cache
from typing import Literal

from dotenv import load_dotenv
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict

# Load .env into os.environ as well.
# Pydantic Settings reads .env for Settings fields, but parts of the calibrated
# guardrails intentionally use os.getenv for compatibility with the original
# guardrails package. Loading here keeps both paths consistent.
load_dotenv(override=False)

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', extra='ignore')

    APP_NAME: str = 'ai-agent-template'
    APP_ENV: str = 'local'
    LOG_LEVEL: str = 'INFO'
    API_HOST: str = '0.0.0.0'
    API_PORT: int = 8000
    CORS_ORIGINS: str = 'http://localhost:5173'

    LLM_PROVIDER: Literal['mock','oci_openai','oci_sdk','openai_compatible'] = 'mock'
    LLM_TEMPERATURE: float = 0.2
    LLM_MAX_TOKENS: int = 2048
    LLM_TIMEOUT_SECONDS: int = 120
    LLM_PROFILES_PATH: str = './llm_profiles.yaml'
    # Reasoning controls. When absent from .env, auto is the default.
    # auto = enable only when the provider/model capability resolver says it is supported.
    # true = force-enable (the provider still performs SDK/request safety checks).
    # false = never send reasoning_effort.
    LLM_REASONING_ENABLED: Literal['auto','true','false'] = 'auto'
    LLM_REASONING_EFFORT: str | None = None

    OCI_GENAI_BASE_URL: str = ''
    OCI_GENAI_MODEL: str = 'openai.gpt-4.1'
    OCI_GENAI_API_KEY: str | None = None
    OCI_GENAI_PROJECT_OCID: str | None = None
    # OCI SDK authentication mode.
    # config_file = ~/.oci/config profile (default/local development)
    # instance_principal = OCI Instance Principal signer (Compute/OKE without API key)
    # resource_principal = OCI Resource Principal signer (Functions/resource principal contexts)
    OCI_AUTH_MODE: Literal['config_file','instance_principal','resource_principal', 'oke_workload_identity'] = 'config_file'
    OCI_CONFIG_FILE: str = '~/.oci/config'
    OCI_PROFILE: str = 'DEFAULT'
    OCI_COMPARTMENT_ID: str | None = None
    OCI_REGION: str = ''
    OCI_GENAI_ENDPOINT: str | None = None
    OCI_EMBEDDING_ENDPOINT: str | None = None

    SESSION_REPOSITORY_PROVIDER: Literal['memory','sqlite','autonomous','oracle','mongodb'] = 'memory'
    MEMORY_REPOSITORY_PROVIDER: Literal['memory','sqlite','autonomous','oracle','mongodb'] = 'memory'
    CHECKPOINT_REPOSITORY_PROVIDER: Literal['memory','sqlite','autonomous','oracle','mongodb'] = 'memory'

    # ConversationSummaryMemory: compressão de contexto conversacional.
    # none    = não injeta histórico no prompt
    # window  = injeta somente últimas mensagens
    # summary = resumo acumulado + últimas mensagens completas
    ENABLE_CONVERSATION_SUMMARY_MEMORY: bool = False
    MEMORY_CONTEXT_STRATEGY: Literal['none','window','summary'] = 'window'
    MEMORY_HISTORY_LIMIT: int = 80
    MEMORY_RECENT_MESSAGES_LIMIT: int = 8
    MEMORY_SUMMARY_TRIGGER_MESSAGES: int = 20
    MEMORY_MAX_SUMMARY_CHARS: int = 6000
    MEMORY_SUMMARY_USE_LLM: bool = True
    MEMORY_INJECT_RECENT_MESSAGES: bool = True
    MEMORY_INJECT_SUMMARY: bool = True

    ENABLE_LONG_TERM_MEMORY: bool = False
    LONG_TERM_MEMORY_PROVIDER: Literal['memory','sqlite','autonomous','oracle'] = 'sqlite'
    LONG_TERM_MEMORY_SQLITE_PATH: str | None = None
    LONG_TERM_MEMORY_TABLE: str = 'agentfw_long_term_memory'
    LONG_TERM_MEMORY_ORACLE_TABLE: str | None = None
    LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
    LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
    LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
    LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True

    # LangGraph enterprise checkpointing
    ENABLE_RESILIENT_CHECKPOINTER: bool = True
    ENABLE_CHECKPOINT_INTEGRITY: bool = True
    ENABLE_CHECKPOINT_COMPACTION: bool = True
    CHECKPOINT_COMPACT_EVERY: int = 50
    CHECKPOINT_KEEP_LAST: int = 20
    CHECKPOINT_RECOVERY_SCAN_LIMIT: int = 25
    CHECKPOINT_RETRY_MAX_ATTEMPTS: int = 3
    CHECKPOINT_RETRY_BASE_DELAY_SECONDS: float = 0.05
    CHECKPOINT_RETRY_MAX_DELAY_SECONDS: float = 1.0
    CHECKPOINT_RETRY_JITTER_SECONDS: float = 0.05
    USAGE_REPOSITORY_PROVIDER: Literal['sqlite','autonomous','oracle'] = 'sqlite'

    ADB_USER: str | None = None
    ADB_PASSWORD: str | None = None
    ADB_DSN: str | None = None
    ADB_WALLET_LOCATION: str | None = None
    ADB_WALLET_PASSWORD: str | None = None
    ADB_TABLE_PREFIX: str = 'AGENTFW'

    MONGODB_URI: str = 'mongodb://localhost:27017'
    MONGODB_DATABASE: str = 'agent_platform'
    REDIS_URL: str = 'redis://localhost:6379/0'
    ENABLE_REDIS_CACHE: bool = False
    CACHE_KEY_PREFIX: str = 'agentfw'

    # RAG backend selector. 'standard' preserves the existing framework RAG;
    # 'kbdb' consumes the enterprise KBDB stable serving facade without changing agents.
    RAG_PROVIDER: Literal['standard','kbdb'] = 'standard'
    VECTOR_STORE_PROVIDER: Literal['memory','sqlite','autonomous','oracle','mongodb'] = 'memory'
    GRAPH_STORE_PROVIDER: Literal['memory','autonomous','oracle'] = 'memory'
    ORACLE_GRAPH_NAME: str = 'AGENTFW_GRAPH'
    ORACLE_GRAPH_AUTO_CREATE: bool = False
    RAG_TOP_K: int = 5
    KBDB_DB_USER: str | None = None
    KBDB_DB_PASSWORD: str | None = None
    KBDB_DB_DSN: str | None = None
    KBDB_DB_WALLET_LOCATION: str | None = None
    KBDB_DB_WALLET_PASSWORD: str | None = None
    KBDB_SEARCH_TYPE: Literal['hybrid','vector','keyword'] = 'hybrid'
    KBDB_NODE_EXPANSION: bool = True
    KBDB_NODE_MAX_RELATED: int = 8
    KBDB_GRAPH_CROSS_REF: bool = False
    KBDB_MAX_CROSS_REF_HOPS: int = 1
    KBDB_DOCUMENT_TYPE: str | None = 'customer_safe'
    KBDB_METADATA_JSON: str | None = None
    KBDB_MIN_SCORE: float | None = None
    # MCP só pula retrieval quando declarar explicitamente rag_sufficient/knowledge_sufficient.
    SKIP_RAG_WHEN_MCP_SUFFICIENT: bool = True
    # Preserva comportamento legado do RAG standard; KBDB enterprise é grounded-only por padrão.
    RAG_GROUNDED_ONLY: bool = False
    KBDB_GROUNDED_ONLY: bool = True
    ENABLE_RAG_QUERY_REWRITE: bool = False
    ENABLE_RAG_CONTEXT_COMPRESSION: bool = False
    ENABLE_RAG_GENERATION: bool = False
    EMBEDDING_PROVIDER: Literal['mock','oci'] = 'mock'
    OCI_EMBEDDING_MODEL: str = 'cohere.embed-multilingual-v3.0'

    ENABLE_LANGFUSE: bool = False
    LANGFUSE_TRACE_MODE: Literal['verbose','compact'] = 'verbose'
    LANGFUSE_ROOT_SPAN_NAME: str = 'agent.gateway_message'
    LANGFUSE_LEGACY_IO_FALLBACK: bool = True
    LANGFUSE_PUBLIC_KEY: str | None = None
    LANGFUSE_SECRET_KEY: str | None = None
    LANGFUSE_HOST: str = 'https://cloud.langfuse.com'
    MODEL_PRICES_JSON: str | None = None
    USD_BRL_RATE: str | None = None
    ENABLE_OTEL: bool = False
    OTEL_EXPORTER_OTLP_ENDPOINT: str | None = None
    OTEL_SERVICE_NAME: str = 'ai-agent-template'
    # Dedicated NOC OpenTelemetry Logs channel. This is separate from trace/span OTel.
    ENABLE_NOC_OTEL_LOGS: bool = False
    OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: str | None = None
    OTEL_EXPORTER_OTLP_HOST_HEADER: str | None = None

    ENABLE_ANALYTICS: bool = False
    ANALYTICS_PROVIDERS: str = 'oci_streaming'
    # Framework compatibility registry is loaded by default so legacy agents can
    # adopt a newer framework without changing their observability/guardrail behavior.
    OBSERVABILITY_DEFAULT_MAPPING_ENABLED: bool = True
    OBSERVABILITY_DEFAULT_MAPPING_PATH: str | None = None
    # Optional agent/deployment overlay applied on top of the framework defaults.
    OBSERVABILITY_CODE_MAPPING_ENABLED: bool = False
    OBSERVABILITY_CODE_MAPPING_PATH: str | None = None
    GCP_PUBSUB_TOPIC_PATH: str | None = None
    AGENT_PUBSUB_TOPIC: str | None = None
    GCP_PROJECT_ID: str | None = None
    GCP_PUBSUB_TOPIC: str | None = None
    GCP_PUBSUB_TIMEOUT_SECONDS: float = 30.0
    # Payload shape is a transport concern. Domain-specific adapters must be selected by the embedding application.
    PUBSUB_PAYLOAD_MODE: Literal['flat','legacy','envelope','wrapped'] = 'flat'
    # Match the old Observer behavior: NOC.* goes to OTel Logs, not Pub/Sub.
    PUBSUB_EXCLUDE_NOC: bool = True

    # Automatic Pub/Sub sequence generation.
    # auto: Redis if configured; otherwise MongoDB if configured; otherwise memory fallback.
    # mongodb: atomic find_one_and_update/$inc.
    PUBSUB_SEQUENCE_ENABLED: bool = True
    PUBSUB_SEQUENCE_PROVIDER: Literal['auto','redis','mongodb','mongo','memory','none'] = 'auto'
    PUBSUB_SEQUENCE_REDIS_URL: str | None = None
    PUBSUB_SEQUENCE_MONGODB_URI: str | None = None
    PUBSUB_SEQUENCE_MONGODB_DATABASE: str | None = None
    PUBSUB_SEQUENCE_MONGODB_COLLECTION: str = 'observer_sequences'
    PUBSUB_SEQUENCE_TTL_SECONDS: int = 86400
    PUBSUB_SEQUENCE_MEMORY_FALLBACK: bool = True
    PUBSUB_SEQUENCE_KEY_PREFIX: str = 'observer:sequence'

    ANALYTICS_FAIL_SILENT: bool = True

    ENABLE_OCI_STREAMING: bool = False
    OCI_STREAM_ENDPOINT: str | None = None
    OCI_STREAM_OCID: str | None = None
    OCI_STREAM_PARTITION_KEY: str = 'agent-events'

    ENABLE_INPUT_GUARDRAILS: bool = True
    ENABLE_OUTPUT_GUARDRAILS: bool = True
    ENABLE_PARALLEL_GUARDRAILS: bool = True
    GUARDRAILS_FAIL_FAST: bool = True
    # Optional LLM inference points. Defaults keep the current deterministic behavior.
    ENABLE_JUDGES: bool = True
    ENABLE_SUPERVISOR: bool = True
    ENABLE_OUTPUT_SUPERVISOR: bool = True
    OUTPUT_SUPERVISOR_MAX_RETRIES: int = 3
    GUARDRAILS_CONFIG_PATH: str = './config/guardrails.yaml'
    JUDGES_CONFIG_PATH: str = './config/judges.yaml'
    PROMPT_POLICY_PATH: str = './config/prompt_policy.yaml'
    AGENTS_CONFIG_PATH: str = './config/agents.yaml'
    ROUTING_CONFIG_PATH: str = './config/routing.yaml'
    ENABLE_LLM_ROUTER: bool = False
    ROUTING_MODE: Literal['router','supervisor'] = 'router'
    # Semantic route stickiness. Uses an LLM profile; no regex or language rules.
    ENABLE_ROUTE_STICKINESS: bool = False
    ROUTE_STICKINESS_LLM_PROFILE: str = 'route_continuity'
    ROUTE_STICKINESS_CONFIDENCE_THRESHOLD: float = 0.90
    ROUTE_STICKINESS_HISTORY_TURNS: int = 2
    ROUTE_STICKINESS_MAX_TOKENS: int = 80
    HUMAN_HANDOFF_MESSAGE: str = 'Vou encaminhar seu atendimento para uma pessoa.'
    END_SESSION_MESSAGE: str = 'Atendimento encerrado. Obrigado pelo contato.'
    POST_FINALIZE_REPLAY_MESSAGE: str = (
        'Por aqui finalizamos o tratamento da sua solicitação. '
        'Aguarde um instante na linha.'
    )
    SESSION_ALREADY_ENDED_MESSAGE: str = 'Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.'

    # MCP / Tooling
    ENABLE_MCP_TOOLS: bool = True
    ENABLE_MCP_CACHE: bool = True
    MCP_CACHE_TTL_SECONDS: int = 300
    MCP_SERVERS_CONFIG_PATH: str = './config/mcp_servers.yaml'
    TOOLS_CONFIG_PATH: str = './config/tools.yaml'
    # Opcional. Se ausente, permanecem válidas as políticas legadas de tools.yaml.
    TOOL_POLICIES_PATH: str | None = './config/tool_policies.yaml'
    ENABLE_TRANSACTIONAL_WORKFLOWS: bool = False
    WORKFLOWS_PATH: str = './workflows'
    IDENTITY_CONFIG_PATH: str = './config/identity.yaml'
    MCP_PARAMETER_MAPPING_PATH: str = './config/mcp_parameter_mapping.yaml'
    MCP_TOOL_TIMEOUT_SECONDS: int = 30
    # When enabled, the framework routes tool calls to the dedicated MCP Gateway
    # instead of calling individual MCP servers directly. The gateway then owns
    # server selection, retry, cache and policy enforcement.
    MCP_GATEWAY_ENABLED: bool = False
    MCP_GATEWAY_URL: str = 'http://localhost:8300'
    MCP_GATEWAY_TIMEOUT_SECONDS: int = 60
    MCP_GATEWAY_TOKEN: str | None = None
    MCP_GATEWAY_AGENT_ID: str = 'telecom_contas'
    MCP_GATEWAY_TENANT_ID: str = 'default'

    DEFAULT_CHANNEL: str = 'web'
    # Agent Framework channel input mode.
    # embedded = backend may use internal adapters to interpret simple/native payloads.
    # external = backend accepts only GatewayRequest payloads already normalized by an external Channel Gateway.
    FRAMEWORK_CHANNEL_INPUT_MODE: Literal['embedded','external'] = 'embedded'
    # Legacy alias kept for compatibility with older .env files. Prefer FRAMEWORK_CHANNEL_INPUT_MODE.
    CHANNEL_GATEWAY_MODE: str | None = None
    ENABLE_VOICE_ADAPTER: bool = True
    ENABLE_WHATSAPP_ADAPTER: bool = True
    ENABLE_TEXT_ADAPTER: bool = True


    # FIRST-ready runtime options
    SQLITE_DB_PATH: str = './data/agent_framework.db'
    ENABLE_SSE: bool = True
    SSE_KEEPALIVE_SECONDS: float = 15.0
    SSE_EVENT_REPLAY_LIMIT: int = 100
    ENABLE_MESSAGE_IDEMPOTENCY: bool = True
    ENABLE_LOCAL_CACHE: bool = True
    CACHE_TTL_SECONDS: int = 300
    CACHE_BACKEND_PROVIDER: Literal['memory','sqlite','autonomous','oracle'] = 'memory'
    SSE_STORE_PROVIDER: Literal['sqlite','autonomous','oracle'] | None = None

@lru_cache
def get_settings() -> Settings:
    return Settings()

settings = get_settings()
