"""Common configuration sections for :func:`relationalai.config.Config`.

Most projects configure PyRel using a YAML file (typically ``raiconfig.yaml``).
This module defines the Pydantic models that validate the nested sections in
that file (for example ``execution``, ``data``, and ``debug``).

You can also configure everything in code by passing dictionaries to
the :func:`~relationalai.config.create_config` factory function (useful for programmatic
setup or for overrides in tests).

For reasoner (engine) configuration, see :mod:`relationalai.config.config_reasoners_fields`.
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field, field_validator

from ..util.docutils import include_in_docs

MAX_DATA_FRESHNESS_MINS = 30240  # 3 weeks, as we might start cleaning up data after that

# include this module in documentation
__include_in_docs__ = True


@include_in_docs
class ExecutionConfig(BaseModel):
    """Configure execution middleware behavior.

    This controls client-side middleware features such as metrics/logging
    and (optionally) automatic retries with exponential backoff.

    Attributes
    ----------
    metrics : bool
        Enable execution metrics collection.
    logging : bool
        Enable structured execution logging.
    retries : ExecutionConfig.RetriesConfig
        Retry and backoff policy.

    Examples
    --------
    Minimal YAML (in ``raiconfig.yaml``):

    ```yaml
    default_connection: sf
    connections:
        sf:
            type: snowflake
            # ...
    execution:
        metrics: true
        retries:
            enabled: true
            max_attempts: 5
            base_delay_s: 0.5
            max_delay_s: 10.0
    ```

    Configure execution settings using a dict:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(
    ...     execution={
    ...         "metrics": True,
    ...         "retries": {"enabled": True, "max_attempts": 5, "base_delay_s": 0.5, "max_delay_s": 10.0},
    ...     },
    ... )

    Configure execution settings using an explicit ExecutionConfig instance:

    >>> from relationalai.config import create_config, ExecutionConfig
    >>> cfg = create_config(
    ...     execution=ExecutionConfig(
    ...         metrics=True,
    ...         retries=ExecutionConfig.RetriesConfig(enabled=True, max_attempts=5, base_delay_s=0.5, max_delay_s=10.0),
    ...     ),
    ... )
    """

    metrics: bool = Field(default=False, description="Enable execution metrics collection (via middleware)")
    logging: bool = Field(default=False, description="Enable execution structured logging (via middleware)")

    @include_in_docs
    class RetriesConfig(BaseModel):
        """Configure execution retries and exponential backoff.

        This model is used for the :attr:`~relationalai.config.ExecutionConfig.retries`
        nested section in :class:`~relationalai.config.ExecutionConfig` and
        controls whether retries are enabled and how delay grows between attempts.

        Attributes
        ----------
        enabled : bool
            Whether to retry failed requests.
        max_attempts : int
            Maximum number of attempts (including the first try).
        base_delay_s : float
            Base backoff delay in seconds.
        max_delay_s : float
            Maximum backoff delay in seconds.
        jitter : float
            Jitter factor applied to each delay (for example, ``0.2`` means ±20%).
        """

        enabled: bool = Field(default=False, description="Enable execution retries/backoff (via middleware)")
        max_attempts: int = Field(default=3, description="Max attempts for retries (when enabled)")
        base_delay_s: float = Field(default=0.25, description="Base delay for exponential backoff (seconds)")
        max_delay_s: float = Field(default=5.0, description="Max backoff delay (seconds)")
        jitter: float = Field(default=0.2, description="Jitter factor for backoff (0.2 => +/-20%)")

    retries: RetriesConfig = Field(default_factory=RetriesConfig)


@include_in_docs
class DataConfig(BaseModel):
    """Configure data loading, streaming, and export behavior.

    This controls client-side defaults used by helpers that load data,
    wait for streams to synchronize, and download exported results.

    Attributes
    ----------
    wait_for_stream_sync : bool
        Wait for stream synchronization before processing.
    ensure_change_tracking : bool
        Enable change tracking for data modifications.
    data_freshness_mins : int, optional
        Data freshness timeout in minutes.
    query_timeout_mins : int, optional
        Query timeout in minutes.
    download_url_type : {"internal", "external"}, optional
        Type of download URL used for data exports.
    check_column_types : bool
        Check column types during data loading.

    Examples
    --------
    Minimal YAML (in ``raiconfig.yaml``):

    ```yaml
    default_connection: sf
    connections:
        sf:
            type: snowflake
            # ...
    data:
        wait_for_stream_sync: false
        download_url_type: external
    ```

    Configure data settings using a dict:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(
    ...     data={"wait_for_stream_sync": False, "download_url_type": "external"},
    ... )

    Configure data settings using an explicit DataConfig instance:

    >>> from relationalai.config import create_config, DataConfig
    >>> cfg = create_config(
    ...     data=DataConfig(wait_for_stream_sync=False, download_url_type="external"),
    ... )
    """

    wait_for_stream_sync: bool = Field(default=True, description="Wait for stream synchronization before processing")
    ensure_change_tracking: bool = Field(default=False, description="Enable change tracking for data modifications")
    data_freshness_mins: int | None = Field(default=None, description="Data freshness timeout in minutes")

    @field_validator("data_freshness_mins")
    @classmethod
    def _clamp_data_freshness(cls, v: int | None) -> int | None:
        if v is not None and v > MAX_DATA_FRESHNESS_MINS:
            from relationalai.util.error import warn
            warn(
                "InvalidConfigValue",
                f"data_freshness_mins={v} exceeds the maximum of {MAX_DATA_FRESHNESS_MINS} "
                f"(3 weeks) and will be clamped to {MAX_DATA_FRESHNESS_MINS}.",
            )
            return MAX_DATA_FRESHNESS_MINS
        return v

    query_timeout_mins: int | None = Field(
        default=None, description="Query timeout in minutes - aborts queries that exceed this duration"
    )
    download_url_type: Literal["internal", "external"] | None = Field(
        default=None, description="Type of download URL for data exports (internal or external)"
    )
    check_column_types: bool = Field(default=True, description="Check column types during data loading")


@include_in_docs
class CompilerConfig(BaseModel):
    """Configure compiler behavior for query translation.

    This controls client-side compilation defaults such as strictness,
    output formatting, and a few experimental optimization flags.

    Attributes
    ----------
    dry_run : bool
        Run compilation in dry-run mode.
    strict : bool
        Enable strict validation mode.
    soft_type_errors : bool
        Treat type errors as warnings instead of failures.

    Examples
    --------
    Minimal YAML (in ``raiconfig.yaml``):

    ```yaml
    default_connection: sf
    connections:
        sf:
            type: snowflake
            # ...
    compiler:
        strict: true
        soft_type_errors: true
    ```

    Configure compiler settings using a dict:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(
    ...     compiler={"dry_run": True, "strict": True, "soft_type_errors": True},
    ... )

    Configure compiler settings using an explicit CompilerConfig instance:

    >>> from relationalai.config import create_config, CompilerConfig
    >>> cfg = create_config(
    ...     compiler=CompilerConfig(dry_run=True, strict=True, soft_type_errors=True),
    ... )
    """

    use_monotype_operators: bool = Field(default=False, description="Use monotype operators in compilation")
    show_corerel_errors: bool = Field(default=True, description="Show CoreRel error messages")
    dry_run: bool = Field(default=False, description="Run compilation in dry-run mode")
    use_value_types: bool = Field(default=False, description="Use value types in compilation")
    debug_hidden_keys: bool = Field(default=False, description="Debug hidden keys in compilation")
    wide_outputs: bool = Field(default=False, description="Use wide output format for query results")
    strict: bool = Field(default=False, description="Enable strict validation mode")
    soft_type_errors: bool = Field(default=False, description="Treat type errors as warnings instead of failures")
    safe_recursion: bool = Field(default=True, description="Enable safe recursion mode")

    # Experimental compiler optimizations
    use_inlined_intermediates: bool = Field(default=False, description="Use inlined intermediate results")
    inline_value_maps: bool = Field(default=False, description="Inline value maps in weaver")
    inline_entity_maps: bool = Field(default=False, description="Inline entity maps in weaver")


@include_in_docs
class ModelConfig(BaseModel):
    """Configure model execution defaults.

    This controls a few cross-cutting behaviors for running a
    :class:`~relationalai.semantics.frontend.base.Model`, including whether
    missing properties may be created implicitly and (for SQL execution) the
    schema used to install model-generated views and tables.

    Attributes
    ----------
    keep : bool
        Keep the model after execution.
    isolated : bool
        Run the model in isolated mode.
    nowait_durable : bool
        Do not wait for durable operations.
    schema_ : str, optional
        Install schema for model-generated SQL views and tables (config key is
        ``schema``).
    implicit_properties : bool
        Allow undeclared properties to be created on first access (when True).

    Examples
    --------
    Minimal YAML (in ``raiconfig.yaml``):

    ```yaml
    default_connection: sf
    connections:
        sf:
            type: snowflake
            # ...
    model:
        schema: analytics
        implicit_properties: false
    ```

    Configure model settings using a dict:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(
    ...     model={"schema": "analytics", "implicit_properties": False},
    ... )

    Configure model settings using an explicit ModelConfig instance:

    >>> from relationalai.config import create_config, ModelConfig
    >>> cfg = create_config(
    ...     model=ModelConfig(schema_="analytics", implicit_properties=False),
    ... )
    """

    keep: bool = Field(default=True, description="Keep model after execution")
    isolated: bool = Field(default=True, description="Run model in isolated mode")
    nowait_durable: bool = Field(default=True, description="Don't wait for durable operations")
    reuse_model: bool = Field(default=True, description="Keep database after releasing index for potential reuse")
    schema_: str | None = Field(default=None, alias="schema", description="Schema for model-generated SQL views and tables")
    implicit_properties: bool = Field(default=True, description="Enable implicit properties for concepts")


@include_in_docs
class DebugConfig(BaseModel):
    """Configure debug server and error-reporting behavior.

    This controls local debugging features such as the debug server host/port
    and whether extra debug logs or full stack traces are shown.

    Attributes
    ----------
    enabled : bool
        Enable debug mode.
    host : str, optional
        Debug server host. If omitted, a default host is used.
    port : int
        Debug server port.
    show_debug_logs : bool
        Show debug log messages.
    show_full_traces : bool
        Show full stack traces in errors.

    Examples
    --------
    Minimal YAML (in ``raiconfig.yaml``):

    ```yaml
    default_connection: sf
    connections:
        sf:
            type: snowflake
            # ...
    debug:
        show_full_traces: true
    ```

    Configure debug settings using a dict:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(debug={"show_full_traces": True})
    """

    enabled: bool = Field(default=True, description="Enable debug mode")
    host: str | None = Field(default=None, description="Debug server host")
    port: int = Field(default=8080, description="Debug server port")
    show_debug_logs: bool = Field(default=False, description="Show debug log messages")
    show_full_traces: bool = Field(default=False, description="Show full stack traces in errors")


@include_in_docs
class JobsConfig(BaseModel):
    """Configure job execution behavior.

    Controls progress reporting and safety guardrails during job execution.

    Attributes
    ----------
    direct_access : bool, optional
        Override the top-level ``direct_access`` default for jobs only.
    print_progress : bool
        Print job progress information.
    print_progress_internal : bool
        Print internal job progress information (detailed).
    enable_guard_rails : bool
        Enable safety guardrails during job execution.

    Examples
    --------
    Minimal YAML (in ``raiconfig.yaml``):

    ```yaml
    default_connection: sf
    connections:
        sf:
            type: snowflake
            # ...
    jobs:
        direct_access: true
        print_progress: true
        enable_guard_rails: true
    ```

    Configure jobs settings using a dict:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(
    ...     connections={"sf": {...}},
    ...     jobs={
    ...         "direct_access": True,
    ...         "print_progress": True,
    ...         "enable_guard_rails": True,
    ...     },
    ... )

    Configure jobs settings using an explicit JobsConfig instance:

    >>> from relationalai.config import create_config, JobsConfig
    >>> cfg = create_config(
    ...     connections={"sf": {...}},
    ...     jobs=JobsConfig(
    ...         direct_access=True,
    ...         print_progress=True,
    ...         enable_guard_rails=True,
    ...     ),
    ... )
    """

    direct_access: bool | None = Field(
        default=None,
        description="Preferred Direct Access toggle for jobs. When set, overrides the top-level direct_access default.",
    )

    print_progress: bool = Field(
        default=True,
        description="Print job progress information"
    )

    print_progress_internal: bool = Field(
        default=False,
        description="Print internal job progress information"
    )

    enable_guard_rails: bool = Field(
        default=False,
        description="Enable job execution guardrails"
    )
