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 PageTargetResolution:
    page: Page
    reason: str
    query: str = ""


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

    text = (request_text or "").strip()
    lowered = text.lower()
    queryset = _base_queryset(site=site, request=request)
    root_page = site.root_page.specific

    if _references_homepage(lowered):
        return PageTargetResolution(page=root_page, reason="homepage", query="homepage")

    if "this page" in lowered:
        source_path = _request_source_path(request)
        if source_path:
            page = _resolve_by_path(queryset, site, source_path)
            if page is not None:
                return PageTargetResolution(
                    page=page, reason="request_path", query=source_path
                )

    explicit_path = _extract_path_reference(text)
    if explicit_path:
        page = _resolve_by_path(queryset, site, explicit_path)
        if page is not None:
            return PageTargetResolution(
                page=page, reason="explicit_path", query=explicit_path
            )

    for candidate in _extract_named_candidates(text):
        page = _resolve_by_name(queryset, candidate)
        if page is not None:
            return PageTargetResolution(page=page, reason="named_page", query=candidate)

    return None


def _base_queryset(*, site, request):
    queryset = Page.objects.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 _references_homepage(text: str) -> bool:
    return any(
        phrase in text
        for phrase in (
            "homepage",
            "home page",
            "home-page",
            "the home page",
            "the homepage",
        )
    )


def _request_source_path(request) -> str:
    raw_path = (getattr(request, "ai_assistant_source_path", "") or "").strip()
    if not raw_path:
        return ""
    if raw_path.startswith("/admin/ai-assistant"):
        return ""
    return raw_path


def _extract_path_reference(text: str) -> str:
    match = re.search(r"(/[-A-Za-z0-9_./]+)", text)
    if not match:
        return ""
    value = match.group(1).strip()
    if value.startswith("/admin/"):
        return ""
    return value


def _extract_named_candidates(text: str) -> list[str]:
    candidates: list[str] = []
    lowered = text.lower()

    quoted = re.findall(r'"([^"]+)"|\'([^\']+)\'', text)
    for left, right in quoted:
        value = (left or right).strip()
        if value:
            candidates.append(value)

    patterns = (
        r"\bpage called ([^?.!,]+)",
        r"\bpage named ([^?.!,]+)",
        r"\bpage titled ([^?.!,]+)",
        r"\bseo (?:for|title for|description for) ([^?.!,]+)",
        r"\bmeta description for ([^?.!,]+)",
        r"\bwhat is ([^?.!,]+)",
        r"\bshow me ([^?.!,]+)",
        r"\bwhere is ([^?.!,]+)",
        r"\bcheck seo for ([^?.!,]+)",
    )
    for pattern in patterns:
        match = re.search(pattern, lowered)
        if not match:
            continue
        value = match.group(1).strip()
        value = re.sub(r"\bthe page\b$", "", value).strip()
        value = re.sub(r"\bpage\b$", "", value).strip()
        if value:
            candidates.append(value)

    shortcuts = {
        "contact page": "contact",
        "services page": "services",
        "service page": "services",
        "contact": "contact",
        "services": "services",
    }
    for needle, candidate in shortcuts.items():
        if needle in lowered:
            candidates.append(candidate)

    deduped: list[str] = []
    seen = set()
    for candidate in candidates:
        normalized = candidate.strip().strip("\"'").strip()
        if not normalized:
            continue
        lowered_candidate = normalized.lower()
        if lowered_candidate in {"this", "this page", "the page"}:
            continue
        if lowered_candidate in seen:
            continue
        seen.add(lowered_candidate)
        deduped.append(normalized)
    return deduped


def _resolve_by_path(queryset, site, path: str) -> Page | None:
    normalized = _normalize_relative_path(path, site)
    matches = [
        page
        for page in queryset
        if _normalize_relative_path(page.url_path or "/", site) == normalized
    ]
    if len(matches) == 1:
        return matches[0]
    return None


def _resolve_by_name(queryset, candidate: str) -> Page | None:
    exact_matches = list(
        queryset.filter(
            Q(title__iexact=candidate) | Q(slug__iexact=_slugify_query(candidate))
        )[:5]
    )
    if len(exact_matches) == 1:
        return exact_matches[0]
    if len(exact_matches) > 1:
        return None

    contains_matches = list(
        queryset.filter(
            Q(title__icontains=candidate) | Q(slug__icontains=_slugify_query(candidate))
        )[:5]
    )
    if len(contains_matches) == 1:
        return contains_matches[0]
    return None


def _normalize_relative_path(path: str, site) -> str:
    root_path = (site.root_page.url_path or "/").rstrip("/")
    raw = (path or "/").strip()
    if not raw.startswith("/"):
        raw = f"/{raw}"
    if root_path and raw.startswith(f"{root_path}/"):
        raw = raw[len(root_path) :]
    raw = raw.rstrip("/") or "/"
    return raw


def _slugify_query(value: str) -> str:
    return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
