from __future__ import annotations

from types import SimpleNamespace

from django.db.models import Q

from ..constants import (
    DEFAULT_BUSINESS_ASSISTANT_MODE_LABEL,
    DEFAULT_ENABLED_BUSINESS_SKILLS,
    DEFAULT_ENABLED_ADMIN_SKILLS,
    DEFAULT_ENABLED_PUBLIC_SKILLS,
    DISPLAY_MODE_BOTH,
    DISPLAY_MODE_DISABLED,
    DISPLAY_MODE_PAGE,
    DISPLAY_MODE_WIDGET,
    MODE_ADMIN,
)


class FakeField:
    def __init__(self, name):
        self.name = name


class FakeMeta:
    def __init__(self, field_names):
        self._fields = [FakeField(name) for name in field_names]

    def get_fields(self):
        return self._fields


class FakeCategoryQuerySet:
    def __init__(self, categories):
        self._categories = list(categories)

    def values_list(self, field_name, flat=False):
        if field_name != "path" or not flat:
            raise AssertionError("Unexpected category values_list usage")
        return [category.path for category in self._categories]


class FakeSiteCategory:
    def __init__(self, categories):
        self.categories = FakeCategoryQuerySet(categories)


class FakeCategory:
    def __init__(self, name, path):
        self.name = name
        self.path = path


class FakeAttribute:
    def __init__(self, name):
        self.name = name


class FakeAttributeValue:
    def __init__(self, name, value):
        self.attribute = FakeAttribute(name)
        self.value_text = value


class FakeAttributeValueManager:
    def __init__(self, values):
        self._values = list(values)

    def all(self):
        return list(self._values)


class FakeChildManager:
    def __init__(self, children):
        self._children = list(children)

    def count(self):
        return len(self._children)


class FakeProduct:
    def __init__(
        self,
        *,
        title,
        slug,
        description="",
        long_description="",
        upc="",
        is_public=True,
        parent=None,
        categories=None,
        url=None,
        attributes=None,
        children=None,
    ):
        self.title = title
        self.slug = slug
        self.description = description
        self.long_description = long_description
        self.upc = upc
        self.is_public = is_public
        self.parent = parent
        self.categories = list(categories or [])
        self._url = url or f"/products/{slug}/"
        self.attribute_values = FakeAttributeValueManager(attributes or [])
        self.children = FakeChildManager(children or [])

    def get_absolute_url(self):
        return self._url

    def primary_category(self):
        return self.categories[0] if self.categories else None

    def add_to_basket(self):  # pragma: no cover - defensive test sentinel
        raise AssertionError("Mutation should not be called")

    def place_order(self):  # pragma: no cover - defensive test sentinel
        raise AssertionError("Mutation should not be called")


class FakeProductQuerySet:
    def __init__(self, products):
        self._products = list(products)

    def all(self):
        return self.__class__(self._products)

    def browsable(self):
        return self.__class__(
            [product for product in self._products if product.categories]
        )

    def filter(self, *args, **kwargs):
        products = list(self._products)
        if kwargs:
            products = [
                product for product in products if self._matches_kwargs(product, kwargs)
            ]
        if args:
            for query in args:
                products = [
                    product
                    for product in products
                    if self._matches_query(product, query)
                ]
        return self.__class__(products)

    def distinct(self):
        seen = set()
        products = []
        for product in self._products:
            marker = id(product)
            if marker in seen:
                continue
            seen.add(marker)
            products.append(product)
        return self.__class__(products)

    def __getitem__(self, value):
        if isinstance(value, slice):
            return list(self._products[value])
        return self._products[value]

    def _matches_kwargs(self, product, kwargs):
        for key, expected in kwargs.items():
            if key == "is_public":
                if product.is_public != expected:
                    return False
            elif key == "parent__isnull":
                if (product.parent is None) != expected:
                    return False
            elif key == "categories__path__startswith":
                if not any(
                    category.path.startswith(expected)
                    for category in product.categories
                ):
                    return False
            else:  # pragma: no cover - guard against unexpected query growth
                raise AssertionError(f"Unexpected queryset filter kwargs: {key}")
        return True

    def _matches_query(self, product, query):
        if not query.children:
            return True
        if query.connector == "OR":
            return any(self._matches_child(product, child) for child in query.children)
        return all(self._matches_child(product, child) for child in query.children)

    def _matches_child(self, product, child):
        if isinstance(child, Q):
            return self._matches_query(product, child)
        lookup, value = child
        haystack = self._resolve_lookup_value(product, lookup)
        if lookup.endswith("__icontains"):
            needle = str(value).lower()
            if isinstance(haystack, list):
                return any(needle in item.lower() for item in haystack)
            return needle in str(haystack).lower()
        if lookup.endswith("__startswith"):
            if isinstance(haystack, list):
                return any(str(item).startswith(value) for item in haystack)
            return str(haystack).startswith(value)
        raise AssertionError(f"Unexpected Q lookup: {lookup}")

    def _resolve_lookup_value(self, product, lookup):
        if lookup == "title__icontains":
            return product.title
        if lookup == "slug__icontains":
            return product.slug
        if lookup == "description__icontains":
            return product.description
        if lookup == "long_description__icontains":
            return product.long_description
        if lookup == "upc__icontains":
            return product.upc
        if lookup == "categories__name__icontains":
            return [category.name for category in product.categories]
        if lookup == "categories__path__startswith":
            return [category.path for category in product.categories]
        raise AssertionError(f"Unexpected lookup resolver request: {lookup}")


class FakeProductManager:
    def __init__(self, products):
        self._products = products

    def all(self):
        return FakeProductQuerySet(self._products)


def build_fake_product_model(products):
    return SimpleNamespace(
        objects=FakeProductManager(products),
        _meta=FakeMeta(
            [
                "title",
                "slug",
                "description",
                "long_description",
                "upc",
                "is_public",
                "parent",
                "categories",
            ]
        ),
    )


class FakeSiteSettings(SimpleNamespace):
    def public_chat_enabled(self):
        return (
            self.customer_enabled
            and self.customer_display_mode != DISPLAY_MODE_DISABLED
        )

    def customer_page_enabled(self):
        return self.public_chat_enabled() and self.customer_display_mode in {
            DISPLAY_MODE_PAGE,
            DISPLAY_MODE_BOTH,
        }

    def customer_widget_enabled(self):
        return self.public_chat_enabled() and self.customer_display_mode in {
            DISPLAY_MODE_WIDGET,
            DISPLAY_MODE_BOTH,
        }

    def effective_widget_launcher_label(self):
        return (self.widget_launcher_label or "").strip() or "Chat"

    def effective_widget_title(self):
        return (self.widget_title or "").strip() or "AI Assistant"

    def effective_widget_welcome_text(self):
        return (self.widget_welcome_text or "").strip() or (
            "Ask a question about this website or webshop and get a quick answer."
        )

    def enabled_skill_keys(self, mode):
        raw_value = (
            self.enabled_admin_skills
            if mode == MODE_ADMIN
            else self.enabled_public_skills
        )
        return [key.strip() for key in raw_value.split(",") if key.strip()]

    def enabled_business_skill_keys(self):
        raw_value = self.enabled_business_skills
        return [key.strip() for key in raw_value.split(",") if key.strip()]

    def safe_mode_instructions(self, mode):
        value = (
            self.admin_assistant_instructions
            if mode == MODE_ADMIN
            else self.public_assistant_instructions
        )
        return (value or "").strip()


def make_settings(**overrides):
    defaults = {
        "customer_enabled": True,
        "customer_display_mode": DISPLAY_MODE_PAGE,
        "admin_enabled": True,
        "openai_api_key": "",
        "openai_model": "gpt-4o-mini",
        "customer_system_prompt": "Customer prompt",
        "admin_system_prompt": "Admin prompt",
        "escalation_email": "",
        "max_context_messages": 12,
        "business_description": "",
        "assistant_tone": "",
        "contact_page_url": "",
        "quote_page_url": "",
        "booking_page_url": "",
        "public_support_email": "",
        "contact_instructions": "",
        "public_assistant_instructions": "",
        "admin_assistant_instructions": "",
        "business_assistant_enabled": False,
        "business_assistant_requires_staff": True,
        "business_assistant_mode_label": DEFAULT_BUSINESS_ASSISTANT_MODE_LABEL,
        "business_assistant_instructions": "",
        "enabled_public_skills": ",".join(DEFAULT_ENABLED_PUBLIC_SKILLS),
        "enabled_admin_skills": ",".join(DEFAULT_ENABLED_ADMIN_SKILLS),
        "enabled_business_skills": ",".join(DEFAULT_ENABLED_BUSINESS_SKILLS),
        "max_skill_context_items": 6,
        "widget_launcher_label": "",
        "widget_title": "",
        "widget_welcome_text": "",
        "widget_position": "bottom-right",
        "hide_widget_on_checkout": True,
    }
    defaults.update(overrides)
    return FakeSiteSettings(**defaults)
