"""Lens dimensions configuration for the Lens chart."""

from enum import StrEnum
from typing import Annotated, Literal

from pydantic import Field, StringConstraints, model_validator

from kb_dashboard_core.queries.types import LegacyQueryTypes
from kb_dashboard_core.shared.config import BaseCfgModel, BaseIdentifiableModel, Sort

type LensDimensionTypes = (
    LensTermsDimension | LensMultiTermsDimension | LensDateHistogramDimension | LensFiltersDimension | LensIntervalsDimension
)

type LensDateMathMinimumInterval = (
    Literal['auto']
    | Annotated[
        str,
        StringConstraints(pattern=r'^[1-9][0-9]*(ms|s|m|h|d|w|M|q|y)$'),
    ]
)
"""Lens minimum interval using Elasticsearch date math format or `auto`."""


class BaseDimension(BaseIdentifiableModel):
    """Base model for defining dimensions."""

    # color: ColorValueMapping | None = Field(default=None)


class CollapseAggregationEnum(StrEnum):
    """The aggregation to use for the dimension."""

    SUM = 'sum'
    MIN = 'min'
    MAX = 'max'
    AVG = 'avg'


class BaseLensDimension(BaseDimension):
    """Base model for defining dimensions within a Lens chart."""

    label: str | None = Field(default=None)
    """The display label for the dimension. If not provided, a label may be inferred from the field and type."""


class LensFiltersDimensionFilter(BaseCfgModel):
    """A filter for a filters dimension."""

    query: LegacyQueryTypes = Field(default=...)
    """The query to use for the dimension."""

    label: str | None = Field(default=None)
    """The display label for the filter. If not provided, the query will be used as the label."""


class LensFiltersDimension(BaseLensDimension):
    """Represents a filters dimension configuration within a Lens chart.

    Filters dimensions are used for filtering data based on a field.
    """

    type: Literal['filters'] = 'filters'

    filters: list[LensFiltersDimensionFilter] = Field(default=...)
    """The filters to use for the dimension."""


class LensIntervalsDimensionInterval(BaseCfgModel):
    """A single interval for an intervals dimension."""

    from_value: int | None = Field(default=None, alias='from')
    """The start of the interval."""

    to_value: int | None = Field(default=None, alias='to')
    """The end of the interval."""

    label: str | None = Field(default=None)
    """The label for the interval."""


class LensIntervalsDimension(BaseLensDimension):
    """Represents an intervals dimension configuration within a Lens chart.

    Intervals dimensions are used for aggregating data based on numeric ranges.
    """

    type: Literal['intervals'] = 'intervals'

    field: str = Field(default=...)
    """The name of the field in the data view that this dimension is based on."""

    intervals: list[LensIntervalsDimensionInterval] | None = Field(default=None)
    """The intervals to use for the dimension. If not provided, intervals will be automatically picked."""

    granularity: int | None = Field(default=None, ge=1, le=7)
    """Interval granularity divides the field into evenly spaced intervals based on the minimum and maximum values for the field.
    Kibana defaults to 4 if not specified."""

    include_empty_intervals: bool | None = Field(default=None)
    """If `true`, include empty histogram buckets. Defaults to `true`."""


class BaseLensTermsDimension(BaseLensDimension):
    """Base class for top values dimensions (single and multi-field).

    Contains all common configuration fields for terms-based dimensions.
    """

    type: Literal['values'] = 'values'

    size: int | None = Field(default=None)
    """The number of top terms to display."""

    sort: Sort | None = Field(default=None)
    """The sort configuration for the terms."""

    show_other_bucket: bool | None = Field(default=None)
    """If `true`, show a bucket for terms not included in the top size. Defaults to `true`."""

    include_missing_values: bool | None = Field(default=None)
    """If `true`, show a bucket for documents with a missing value for the field. Defaults to `false`."""

    include: list[str] | None = Field(default=None)
    """A list of terms to include. Can be used with or without `include_is_regex`."""

    exclude: list[str] | None = Field(default=None)
    """A list of terms to exclude. Can be used with or without `exclude_is_regex`."""

    include_is_regex: bool | None = Field(default=None)
    """If `true`, treat the values in the `include` list as regular expressions. Defaults to `false`."""

    exclude_is_regex: bool | None = Field(default=None)
    """If `true`, treat the values in the `exclude` list as regular expressions. Defaults to `false`."""

    @model_validator(mode='after')
    def validate_regex_filters(self) -> 'BaseLensTermsDimension':
        """Validate regex include/exclude inputs use exactly one pattern."""
        include_count = len(self.include) if self.include is not None else 0
        if self.include_is_regex is True and include_count != 1:
            msg = '`include` must contain exactly one entry when `include_is_regex` is true'
            raise ValueError(msg)

        exclude_count = len(self.exclude) if self.exclude is not None else 0
        if self.exclude_is_regex is True and exclude_count != 1:
            msg = '`exclude` must contain exactly one entry when `exclude_is_regex` is true'
            raise ValueError(msg)

        return self


class LensTermsDimension(BaseLensTermsDimension):
    """Represents a single-field top values dimension configuration within a Lens chart.

    Terms dimensions are used for aggregating data based on unique values of a single field.
    """

    field: str = Field(default=...)
    """The name of the field in the data view that this dimension is based on."""


class LensMultiTermsDimension(BaseLensTermsDimension):
    """Represents a multi-field top values dimension configuration within a Lens chart.

    Multi-terms dimensions are used for aggregating data based on unique combinations
    of values across multiple fields.
    """

    fields: list[str] = Field(default=..., min_length=2)
    """List of field names for multi-field aggregation. Requires at least 2 fields."""


class LensDateHistogramDimension(BaseLensDimension):
    """Represents a histogram dimension configuration within a Lens chart.

    Date histogram dimensions are used for aggregating data into buckets based on numeric ranges.
    """

    type: Literal['date_histogram'] = 'date_histogram'

    field: str = Field(default=...)
    """The name of the field in the data view that this dimension is based on."""

    minimum_interval: LensDateMathMinimumInterval | None = Field(default=None)
    """The minimum interval using Elasticsearch date math format (e.g. `1m`, `1h`). Defaults to `auto`."""

    partial_intervals: bool | None = Field(default=None)
    """If `true`, show partial intervals. Kibana defaults to `true` if not specified."""
