from collections import namedtuple
from functools import lru_cache
import logging
from typing import Any, TYPE_CHECKING

from django.db.models import Model
from django.db.backends.utils import CursorWrapper
from ..typehints import LookupField

if TYPE_CHECKING:
    from ..models.models import BaseUUIDPKModel

logger = logging.getLogger(__name__)


# ----------------------------------------------------------------------
@lru_cache(128)
def get_model_fields(model: type[Model]) -> set[str]:
    """
    Get fields from model so we can check if it's safe to order_by
    Use lru_cache to reduce any performance impact.
    """
    try:
        return {f.name for f in model._meta.get_fields()}
    except AttributeError:
        return set()


# ---------------------------------------------------------------------------
@lru_cache(128)
def lookup_field(model: Model, name: str) -> LookupField:
    """
    Most models have a Company or Policy field and we usually want
    to limit querysets based on one or both of those fields. However,
    related objects should be limited by a "through" field.

    Check to see if the model has a through-field defined,
    or an actual field of the specified name.

    Return field name string, and bool of whether field is required.
    Filtering by required fields don't need to include NULL values
    for more efficient queries.
    """
    field = getattr(model, f'{name}_field', None)
    if field:
        is_required = getattr(model, f'{name}_field_required', False)
        return LookupField(field, is_required or field in ('id', 'pk'))

    for field in model._meta.concrete_fields:  # type: ignore[attr-defined]
        if field.name == name or field.attname == name:
            # Since this is used for query generation, allow a model's
            # "required" state to be manually overridden by a property
            # on the model, should it be necessary to allow nulls in the
            # database, yet set the field as "required" for the purposes
            # of sync querying.
            is_required = (
                not field.blank
                or field.name in ('id', 'pk')
                or getattr(model, f'{name}_field_required', False)
            )
            return LookupField(name, is_required)

    return LookupField('', False)


# ---------------------------------------------------------------------------
def namedtuplefetchall(cursor: CursorWrapper) -> Any:
    """Return all rows from a cursor as a namedtuple"""
    desc = cursor.description
    nt_result = namedtuple('nt_result', [col[0] for col in desc])  # type: ignore[misc]
    return [nt_result(*row) for row in cursor.fetchall()]


# ---------------------------------------------------------------------------
def tenant_query_params(record: 'BaseUUIDPKModel') -> dict:
    """
    Generate tenant headers from record, for passing to Unpoly URLs
    so that linked layers can have access to the Parent record tenants.
    """
    tenants = {}

    try:
        if record.company_id:
            tenants['company'] = record.company_id
    except AttributeError:
        pass

    try:
        if record.policy_id:
            tenants['policy'] = record.policy_id
    except AttributeError:
        pass

    if not tenants:
        return {}

    return tenants


__all__ = (
    'get_model_fields',
    'lookup_field',
    'namedtuplefetchall',
    'tenant_query_params',
)
