"""Query helpers for filtering, sorting, and primary key resolution."""

from collections.abc import Callable, Collection
from typing import cast, get_args, get_origin

from sqlmodel import SQLModel

from auen.config import FilterConfig, FilterOp
from auen.exceptions import ConfigurationError
from auen.types import (
    FilterParams,
    FilterScalar,
    FilterValue,
    ModelFieldAnnotation,
    SelectQuery,
    TModel,
)


def resolve_pk_field(model: type[SQLModel], id_field: str | None) -> tuple[str, type]:
    """Resolve the primary key field name and type."""

    def _normalize_pk_type(annotation: object) -> type:
        if isinstance(annotation, type):
            return annotation
        origin = get_origin(annotation)
        if origin is None:
            return int
        args = [arg for arg in get_args(annotation) if arg is not type(None)]
        for arg in args:
            if isinstance(arg, type):
                return arg
        return int

    if id_field:
        info = model.model_fields.get(id_field)
        if info is None:
            msg = f"id_field={id_field!r} not found in {model.__name__}"
            raise ValueError(msg)
        return id_field, _normalize_pk_type(info.annotation)

    # Auto-detect from SQLAlchemy mapper
    from sqlalchemy import inspect as sa_inspect

    try:
        pk_cols = sa_inspect(model).mapper.primary_key
        if len(pk_cols) == 1:
            name = pk_cols[0].name
            info = model.model_fields.get(name)
            pk_type = info.annotation if info else int
            return name, _normalize_pk_type(pk_type)
    except Exception:
        pass

    # Fallback to "id"
    info = model.model_fields.get("id")
    if info:
        return "id", _normalize_pk_type(info.annotation)

    msg = f"Cannot detect primary key for {model.__name__}. Pass id_field explicitly."
    raise ValueError(msg)


def _normalize_filter_values(
    value: FilterValue, annotation: ModelFieldAnnotation
) -> list[FilterScalar]:
    values: list[FilterScalar]
    if isinstance(value, str):
        values = [v.strip() for v in value.split(",") if v.strip()]
    elif isinstance(value, Collection):
        values = list(cast(Collection[FilterScalar], value))
    else:
        values = [value]

    origin = get_origin(annotation)
    args = get_args(annotation)
    target: ModelFieldAnnotation = annotation
    if origin in {list, set, tuple}:
        target = args[0] if args else None
    elif origin is not None:
        non_none = [arg for arg in args if arg is not type(None)]
        if non_none:
            target = non_none[0]

    coercer: Callable[[FilterScalar], FilterScalar] | None
    if target in {int, float, str} and isinstance(target, type):
        coercer = target
    elif target is bool:

        def coercer(v: FilterScalar) -> FilterScalar:
            if isinstance(v, str):
                return v.lower() in {"1", "true", "yes", "on"}
            return bool(v)
    else:
        coercer = None

    if coercer is None:
        return values
    coerced: list[FilterScalar] = []
    for item in values:
        try:
            coerced.append(coercer(item))
        except (TypeError, ValueError):
            coerced.append(item)
    return coerced


def apply_filters(
    query: SelectQuery[TModel],
    model: type[TModel],
    filter_config: FilterConfig,
    params: FilterParams,
) -> SelectQuery[TModel]:
    """Apply filter parameters to a query."""
    for name, value in params.items():
        if value is None:
            continue
        if "__" in name:
            field_name, op_str = name.split("__", 1)
        else:
            field_name, op_str = name, "eq"

        # Validate op string against FilterOp enum
        try:
            op = FilterOp(op_str)
        except ValueError:
            msg = f"Unknown filter operation: {op_str!r}"
            raise ConfigurationError(msg) from None

        field_cfg = filter_config.fields.get(field_name)
        if field_cfg is None or op not in field_cfg.ops:
            continue
        col = getattr(model, field_name, None)
        if col is None:
            continue
        if op == FilterOp.EQ:
            query = query.where(col == value)
        elif op == FilterOp.LT:
            query = query.where(col < value)
        elif op == FilterOp.LTE:
            query = query.where(col <= value)
        elif op == FilterOp.GT:
            query = query.where(col > value)
        elif op == FilterOp.GTE:
            query = query.where(col >= value)
        elif op == FilterOp.CONTAINS:
            escaped = str(value).replace("%", r"\%").replace("_", r"\_")
            query = query.where(col.like(f"%{escaped}%", escape="\\"))
        elif op == FilterOp.ICONTAINS:
            escaped = str(value).replace("%", r"\%").replace("_", r"\_")
            query = query.where(col.ilike(f"%{escaped}%", escape="\\"))
        elif op == FilterOp.IN:
            annotation = model.model_fields.get(field_name)
            values = _normalize_filter_values(
                value, annotation.annotation if annotation else None
            )
            if values:
                query = query.where(col.in_(values))
    return query


def apply_sorting(
    query: SelectQuery[TModel],
    model: type[TModel],
    filter_config: FilterConfig | None,
    sort: str | None,
) -> SelectQuery[TModel]:
    """Apply sorting to a query."""
    if not sort or not filter_config:
        return query
    desc = sort.startswith("-")
    field_name = sort.lstrip("-")
    if field_name not in filter_config.sort_fields:
        from fastapi import HTTPException

        raise HTTPException(
            status_code=422, detail=f"Sorting on '{field_name}' is not allowed"
        )
    col = getattr(model, field_name, None)
    if col is None:
        return query
    if desc:
        return query.order_by(col.desc())
    return query.order_by(col)
