from __future__ import annotations

from typing import Any

from django.core.exceptions import ValidationError
from django.db import models

from django_anyllm.registry import ModelRegistry
from django_anyllm.serializers import deserialize_data, serialize_instance
from django_anyllm.settings import get_setting


def _validate_filter_keys(model: type[models.Model], filters: dict[str, Any]) -> None:
    """Ensure filter keys reference valid model fields or field lookups.

    Args:
        model: The model class to validate against.
        filters: The filter dictionary.

    Raises:
        ValueError: If a filter key references an unknown field.
    """
    valid_names = {f.name for f in model._meta.get_fields()}
    for key in filters:
        base = key.split("__", 1)[0]
        if base not in valid_names:
            msg = f"Invalid filter key '{key}': '{base}' is not a field on {model._meta.label}."
            raise ValueError(msg)


def query_instances(
    registry: ModelRegistry,
    app_label: str,
    model_name: str,
    filters: dict[str, Any] | None = None,
    limit: int = 20,
    offset: int = 0,
    ordering: list[str] | None = None,
) -> list[dict[str, Any]]:
    """Query instances of a registered model with optional filters.

    Args:
        app_label: The Django app label.
        model_name: The lower-case model name.
        filters: Optional ORM-style filters (e.g. ``{"status__in": ["active"]}``).
        limit: Maximum number of results.
        offset: Number of results to skip.
        ordering: List of field names to order by (prefix with ``-`` for descending).

    Returns:
        A list of serialised instance dictionaries.
    """
    registry.check_permission(app_label, model_name, "read")
    model = registry.get_model(app_label, model_name)

    qs = model.objects.all()

    if filters:
        _validate_filter_keys(model, filters)
        qs = qs.filter(**filters)

    if ordering:
        qs = qs.order_by(*ordering)

    max_limit = get_setting("MAX_QUERY_LIMIT")
    if limit > max_limit:
        limit = max_limit

    qs = qs[offset : offset + limit]
    return [serialize_instance(instance) for instance in qs]


def get_instance(
    registry: ModelRegistry, app_label: str, model_name: str, pk: int
) -> dict[str, Any] | None:
    """Retrieve a single instance by primary key.

    Args:
        app_label: The Django app label.
        model_name: The lower-case model name.
        pk: The primary key of the instance.

    Returns:
        The serialised instance, or ``None`` if not found.
    """
    registry.check_permission(app_label, model_name, "read")
    model = registry.get_model(app_label, model_name)

    try:
        instance = model.objects.get(pk=pk)
    except model.DoesNotExist:
        return None

    return serialize_instance(instance)


def create_instance(
    registry: ModelRegistry,
    app_label: str,
    model_name: str,
    data: dict[str, Any],
) -> dict[str, Any]:
    """Create a new model instance.

    Args:
        app_label: The Django app label.
        model_name: The lower-case model name.
        data: Field values for the new instance.

    Returns:
        The serialised created instance.

    Raises:
        ValueError: If validation fails.
    """
    registry.check_permission(app_label, model_name, "create")
    model = registry.get_model(app_label, model_name)

    cleaned = deserialize_data(model, data)
    instance = model(**cleaned)

    try:
        instance.full_clean()
    except ValidationError as exc:
        msg = f"Validation error: {exc.message_dict}"
        raise ValueError(msg) from exc

    instance.save()
    return serialize_instance(instance)


def update_instance(
    registry: ModelRegistry,
    app_label: str,
    model_name: str,
    pk: int,
    data: dict[str, Any],
) -> dict[str, Any]:
    """Update an existing model instance.

    Args:
        app_label: The Django app label.
        model_name: The lower-case model name.
        pk: The primary key of the instance to update.
        data: Field values to update.

    Returns:
        The serialised updated instance.

    Raises:
        ValueError: If the instance does not exist or validation fails.
    """
    registry.check_permission(app_label, model_name, "update")
    model = registry.get_model(app_label, model_name)

    try:
        instance = model.objects.get(pk=pk)
    except model.DoesNotExist as exc:
        msg = f"Instance with pk={pk} does not exist."
        raise ValueError(msg) from exc

    cleaned = deserialize_data(model, data)
    for key, value in cleaned.items():
        setattr(instance, key, value)

    try:
        instance.full_clean()
    except ValidationError as exc:
        msg = f"Validation error: {exc.message_dict}"
        raise ValueError(msg) from exc

    instance.save()
    return serialize_instance(instance)


def attach_file_to_instance(
    registry: ModelRegistry,
    app_label: str,
    model_name: str,
    pk: int,
    field_name: str,
    message_file_id: int,
) -> dict[str, Any]:
    """Attach a file uploaded in the chat to a model instance's FileField or ImageField.

    Use this tool when the user has uploaded a file (image, document, etc.) in the
    conversation and wants it saved to a file field on a model instance. The
    ``message_file_id`` is shown in the ``[Attached files: ...]`` metadata that
    accompanies uploaded files in the conversation.

    Args:
        app_label: The Django app label.
        model_name: The lower-case model name.
        pk: The primary key of the instance to update.
        field_name: The name of the FileField or ImageField on the model.
        message_file_id: The file_id from the ``[Attached files: ...]`` metadata.

    Returns:
        The serialised updated instance.

    Raises:
        ValueError: If the instance, field, or file does not exist, or if the
            target field is not a FileField or ImageField.
    """
    import os

    from django.core.files.base import ContentFile

    from django_anyllm.models import MessageFile

    registry.check_permission(app_label, model_name, "update")
    model = registry.get_model(app_label, model_name)

    try:
        field = model._meta.get_field(field_name)
    except Exception as exc:
        msg = f"Field '{field_name}' does not exist on {model._meta.label}."
        raise ValueError(msg) from exc

    if not isinstance(field, models.FileField):
        msg = f"'{field_name}' on {model._meta.label} is not a FileField or ImageField."
        raise ValueError(msg)

    try:
        instance = model.objects.get(pk=pk)
    except model.DoesNotExist as exc:
        msg = f"Instance with pk={pk} does not exist."
        raise ValueError(msg) from exc

    try:
        msg_file = MessageFile.objects.get(pk=message_file_id)
    except MessageFile.DoesNotExist as exc:
        msg = f"MessageFile with id={message_file_id} does not exist."
        raise ValueError(msg) from exc

    filename = os.path.basename(msg_file.file.name)
    msg_file.file.open("rb")
    try:
        data = msg_file.file.read()
    finally:
        msg_file.file.close()

    getattr(instance, field_name).save(filename, ContentFile(data), save=True)
    instance.refresh_from_db()
    return serialize_instance(instance)


def delete_instance(
    registry: ModelRegistry, app_label: str, model_name: str, pk: int
) -> bool:
    """Delete a model instance by primary key.

    Args:
        app_label: The Django app label.
        model_name: The lower-case model name.
        pk: The primary key of the instance to delete.

    Returns:
        ``True`` if the instance was deleted, ``False`` if it did not exist.
    """
    registry.check_permission(app_label, model_name, "delete")
    model = registry.get_model(app_label, model_name)

    try:
        instance = model.objects.get(pk=pk)
    except model.DoesNotExist:
        return False

    instance.delete()
    return True
