"""
Database interaction utilities for django-mantle.
"""

import logging
from datetime import date
from decimal import Decimal
from typing import Any, Generic, Type, TypeVar

import attrs
import cattrs
from attrs import define, field
from django.db import connection
from django.db.models import QuerySet
from django_readers import specs

from .readers import to_spec

logger = logging.getLogger(__name__)

T = TypeVar("T")


def make_default_converter() -> cattrs.Converter:
    """
    Create a cattrs converter with sensible defaults for Django types.

    Handles:
    - date: Pass through date objects, parse ISO strings
    - Decimal: Convert through string to preserve precision
    """
    converter = cattrs.Converter()

    def convert_date(value: Any, _) -> date | None:
        if value is None:
            return None
        if isinstance(value, date):
            return value
        return date.fromisoformat(value)

    def convert_decimal(value: Any, _) -> Decimal:
        if isinstance(value, Decimal):
            return value
        return Decimal(str(value))

    converter.register_structure_hook(date, convert_date)
    converter.register_structure_hook(Decimal, convert_decimal)

    return converter


# Default converter instance
default_converter = make_default_converter()


@define
class QueryLogConfig:
    """
    Logging configuration for Query operations.

    Attributes:
        level: Logging level (e.g., logging.DEBUG, logging.INFO)
        log_spec: Log the prepared spec (via django_readers_debug.debug_print)
        log_sql: Log the generated SQL query
        log_queries: Log the number and actual queries made
        log_dicts: Log the projected dictionaries from ORM
        log_shapes: Log the final structured attrs instances
    """

    level: int = logging.INFO
    log_spec: bool = False
    log_sql: bool = False
    log_queries: bool = False
    log_dicts: bool = False
    log_shapes: bool = False


class Query(Generic[T]):
    """
    Query class for retrieving Django ORM data as type-safe attrs instances.

    Encapsulates the full django-readers → cattrs flow:
    1. Generate spec from attrs class
    2. Process spec into (prepare, project) pair
    3. Prepare queryset with optimizations
    4. Execute query and project to dicts
    5. Structure dicts as attrs instances

    Usage:
        query = Query(Bookmark.objects.all(), BookmarkAttrs)
        bookmarks = query.all()  # Returns list[BookmarkAttrs]
        bookmark = query.get()   # Returns BookmarkAttrs
    """

    def __init__(
        self,
        queryset: QuerySet,
        shape_class: Type[T],
        converter: cattrs.Converter | None = None,
        log_config: QueryLogConfig | None = None,
    ):
        """
        Initialize a Query.

        Args:
            queryset: Django QuerySet to query
            shape_class: attrs class defining the shape of results
            converter: Custom cattrs converter (uses default if None)
            log_config: Logging configuration (no logging if None)
        """
        self.queryset = queryset
        self.shape_class = shape_class
        self.converter = converter or default_converter
        self.log_config = log_config or QueryLogConfig()

        # Generate spec and process it once during initialization
        self.spec = to_spec(shape_class)
        self.prepare, self.project = specs.process(self.spec)

        # Log spec if configured
        if self.log_config.log_spec:
            self._log_spec()

    def _log_spec(self):
        """Log the prepared spec using django_readers_debug."""
        try:
            from django_readers_debug import debug_print

            logger.log(self.log_config.level, "Generated spec and prepare function:")
            debug_print(self.prepare)
        except ImportError:
            logger.log(self.log_config.level, f"Generated spec: {self.spec}")

    def _log_sql(self, prepared_qs: QuerySet):
        """Log the SQL query that will be executed."""
        if self.log_config.log_sql:
            logger.log(self.log_config.level, f"SQL Query: {prepared_qs.query}")

    def _log_queries(self, initial_queries: int):
        """Log the queries that were executed."""
        if self.log_config.log_queries:
            final_queries = len(connection.queries)
            num_queries = final_queries - initial_queries
            logger.log(self.log_config.level, f"Executed {num_queries} queries")
            for query in connection.queries[initial_queries:]:
                logger.log(self.log_config.level, f"  {query['sql']}")

    def _log_dicts(self, dicts: list[dict] | dict):
        """Log the projected dictionaries."""
        if self.log_config.log_dicts:
            logger.log(self.log_config.level, f"Projected dicts: {dicts}")

    def _log_shapes(self, shapes: list[T] | T):
        """Log the final structured shapes."""
        if self.log_config.log_shapes:
            logger.log(self.log_config.level, f"Final shapes: {shapes}")

    def all(self) -> list[T]:
        """
        Execute query and return all results as attrs instances.

        Returns:
            List of attrs instances of shape_class type
        """
        # Prepare queryset
        prepared_qs = self.prepare(self.queryset)

        # Log SQL if configured
        self._log_sql(prepared_qs)

        # Track queries if logging
        initial_queries = len(connection.queries) if self.log_config.log_queries else 0

        # Execute query and project to dicts
        instances = list(prepared_qs)
        dicts = [self.project(instance) for instance in instances]

        # Log queries if configured
        if self.log_config.log_queries:
            self._log_queries(initial_queries)

        # Log dicts if configured
        self._log_dicts(dicts)

        # Structure as a list of attrs instances
        shapes = self.converter.structure(dicts, list[self.shape_class])  # type: ignore[name-defined]

        # Log shapes if configured
        self._log_shapes(shapes)

        return shapes

    def get(self) -> T:
        """
        Execute query and return a single result as attrs instance.

        Raises:
            model.DoesNotExist: If no results found
            model.MultipleObjectsReturned: If multiple results found

        Returns:
            Single attrs instance of shape_class type
        """
        # Prepare queryset
        prepared_qs = self.prepare(self.queryset)

        # Log SQL if configured
        self._log_sql(prepared_qs)

        # Track queries if logging
        initial_queries = len(connection.queries) if self.log_config.log_queries else 0

        # Execute query and project to dict (using .get())
        instance = prepared_qs.get()
        data = self.project(instance)

        # Log queries if configured
        if self.log_config.log_queries:
            self._log_queries(initial_queries)

        # Log dict if configured
        self._log_dicts(data)

        # Structure as attrs instance
        shape = self.converter.structure(data, self.shape_class)

        # Log shape if configured
        self._log_shapes(shape)

        return shape
