from __future__ import annotations

import re
from dataclasses import dataclass

from django.db.models import Q
from wagtail.models import Page


@dataclass(frozen=True)
class ServiceRouteMatch:
    category: str
    matched_keywords: tuple[str, ...]
    page_id: int
    page_title: str
    page_url: str
    reason: str


SERVICE_CATEGORY_KEYWORDS: tuple[tuple[str, tuple[str, ...]], ...] = (
    ("webshop", ("webshop", "ecommerce", "e-commerce", "online shop", "store")),
    ("website", ("website", "site", "web site", "redesign")),
    ("support", ("support", "help", "issue", "problem", "assistance")),
    ("maintenance", ("maintenance", "maintain", "updates", "upkeep")),
    ("seo", ("seo", "search engine", "ranking", "metadata")),
    ("ai_automation", ("ai automation", "chatbot", "ai agent", "automation")),
    ("custom_work", ("custom work", "custom development", "bespoke", "tailored")),
    (
        "multilingual",
        ("multilingual", "multi language", "multi-language", "translation"),
    ),
    ("booking", ("booking", "appointment", "consultation", "demo", "reservation")),
)


def route_service_category(
    request_text: str, site, request=None
) -> ServiceRouteMatch | None:
    if not site or not getattr(site, "root_page", None):
        return None

    text = (request_text or "").lower()
    detected = _detect_categories(text)
    if not detected:
        return None

    queryset = _base_queryset(site=site, request=request)
    for category, keywords in detected:
        page = _find_page_for_keywords(queryset, keywords)
        if page is None:
            continue
        return ServiceRouteMatch(
            category=category,
            matched_keywords=keywords,
            page_id=page.id,
            page_title=(page.title or "").strip(),
            page_url=_relative_url(page, site),
            reason="public_page_keyword_match",
        )
    return None


def _detect_categories(text: str) -> list[tuple[str, tuple[str, ...]]]:
    matches: list[tuple[str, tuple[str, ...]]] = []
    for category, keywords in SERVICE_CATEGORY_KEYWORDS:
        matched = tuple(
            keyword
            for keyword in keywords
            if re.search(rf"\b{re.escape(keyword)}\b", text)
        )
        if matched:
            matches.append((category, matched))
    return matches


def _base_queryset(*, site, request):
    queryset = (
        Page.objects.live()
        .public()
        .descendant_of(site.root_page, inclusive=True)
        .specific()
    )
    language_code = (getattr(request, "LANGUAGE_CODE", "") or "").strip()
    if language_code and "locale" in {field.name for field in Page._meta.get_fields()}:
        queryset = queryset.filter(locale__language_code=language_code)
    return queryset


def _find_page_for_keywords(queryset, keywords: tuple[str, ...]):
    query = Q()
    for keyword in keywords:
        query |= (
            Q(title__icontains=keyword)
            | Q(search_description__icontains=keyword)
            | Q(seo_title__icontains=keyword)
            | Q(url_path__icontains=keyword.replace(" ", "-"))
            | Q(slug__icontains=keyword.replace(" ", "-"))
        )
    candidates = list(queryset.filter(query)[:25])
    ranked = []
    for page in candidates:
        score = _score_page(page, keywords)
        if score <= 0:
            continue
        ranked.append((score, page))
    ranked.sort(key=lambda item: (-item[0], (item[1].title or "").lower()))
    return ranked[0][1] if ranked else None


def _score_page(page, keywords: tuple[str, ...]) -> int:
    title = (page.title or "").lower()
    slug = (page.slug or "").lower()
    seo_title = (getattr(page, "seo_title", "") or "").lower()
    search_description = (getattr(page, "search_description", "") or "").lower()
    score = 0
    for keyword in keywords:
        normalized = keyword.lower()
        slug_keyword = normalized.replace(" ", "-")
        if normalized in title:
            score += 5
        if slug_keyword in slug:
            score += 4
        if normalized in seo_title:
            score += 3
        if normalized in search_description:
            score += 2
    return score


def _relative_url(page, site) -> str:
    root_path = (site.root_page.url_path or "/").rstrip("/")
    page_path = page.url_path or "/"
    if root_path and page_path.startswith(f"{root_path}/"):
        relative = page_path[len(root_path) :]
        return relative if relative.startswith("/") else f"/{relative}"
    if page_path.startswith("/"):
        return page_path
    return f"/{page_path}"
