from __future__ import annotations

import re
from collections import OrderedDict

from django.db.models import Q
from django.utils.timezone import localtime
from django.utils.translation import gettext as _
from wagtail.models import Page

from ..constants import MODE_ADMIN, SKILL_ADMIN_CONTENT, SKILL_SCOPE_ADMIN
from ..page_resolver import resolve_page_target
from .base import AiAssistantSkill, SkillContextResult, SkillMatch


class AdminContentSkill(AiAssistantSkill):
    key = SKILL_ADMIN_CONTENT
    label = "Admin content navigation"
    allowed_modes = (MODE_ADMIN,)
    public_safe = False
    skill_scope = SKILL_SCOPE_ADMIN

    _INTENT_PATTERNS = (
        r"\bpage(?:s)?\b",
        r"\bhomepage\b",
        r"\bhome page\b",
        r"\bcontact page\b",
        r"\bservices?\b",
        r"\bdraft(?:s)?\b",
        r"\blive\b",
        r"\bseo\b",
        r"\bdescription\b",
        r"\btitle\b",
        r"\bnavigation\b",
        r"\bhierarchy\b",
        r"\bstructure\b",
        r"\bslug\b",
        r"\bfind\b",
        r"\bwhere is\b",
        r"\bunder the homepage\b",
    )

    def is_enabled(self, site, settings_obj, mode: str) -> bool:
        return mode == MODE_ADMIN and self.key in settings_obj.enabled_skill_keys(mode)

    def match(
        self, request_text: str, request, site, mode: str, settings_obj
    ) -> SkillMatch:
        if mode != MODE_ADMIN or not site:
            return SkillMatch(score=0, reason="admin_only")
        text = (request_text or "").lower()
        if any(re.search(pattern, text) for pattern in self._INTENT_PATTERNS):
            return SkillMatch(score=40, reason="admin_content_intent")
        return SkillMatch(score=0, reason="no_admin_content_intent")

    def build_context(
        self, request_text: str, request, site, mode: str, settings_obj
    ) -> SkillContextResult:
        if mode != MODE_ADMIN or not site:
            return SkillContextResult()

        root_page = getattr(site, "root_page", None)
        if root_page is None:
            return SkillContextResult()

        resolved = resolve_page_target(request_text, site, request=request)
        if resolved is not None:
            return self._targeted_context(
                request_text=request_text,
                site=site,
                resolved=resolved,
                max_items=max(int(settings_obj.max_skill_context_items or 1), 1),
            )

        results = self._search_pages(
            site=site,
            request=request,
            request_text=request_text,
            max_items=max(int(settings_obj.max_skill_context_items or 1), 1),
        )
        if not results:
            return SkillContextResult()

        items = []
        homepage = results[0] if results and results[0].get("is_homepage") else None
        if homepage:
            items.append(
                _("Homepage: %(title)s (%(path)s)")
                % {"title": homepage["title"], "path": homepage["path"]}
            )

        matching_pages = [item for item in results if not item.get("is_homepage")]
        if matching_pages:
            lines = [_("Matching pages:")]
            for index, item in enumerate(matching_pages, start=1):
                lines.extend(
                    [
                        _("%(index)s. Title: %(title)s")
                        % {"index": index, "title": item["title"]},
                        _("   Type: %(page_type)s") % {"page_type": item["page_type"]},
                        _("   Status: %(status)s") % {"status": item["status"]},
                        _("   URL/path: %(path)s") % {"path": item["path"]},
                        _("   Last updated: %(updated)s")
                        % {"updated": item["updated"]},
                        _("   SEO description: %(seo_description)s")
                        % {"seo_description": item["seo_description"]},
                        _("   SEO title: %(seo_title)s")
                        % {"seo_title": item["seo_title"]},
                    ]
                )
            items.append("\n".join(lines))

        instruction = _(
            "Admin content context may be provided below. Use it to help staff understand site structure and locate pages. This context is read-only. Do not claim to edit, publish, unpublish, move, or delete pages. Suggest where to navigate in Wagtail when useful, and do not expose secrets or private configuration."
        )
        return SkillContextResult.from_items(
            items,
            instruction=instruction,
            metadata={"skill": self.key, "result_count": len(results)},
        )

    def _targeted_context(self, *, request_text: str, site, resolved, max_items: int):
        page = resolved.page
        metadata = self._page_metadata(
            page, site, is_homepage=page.id == site.root_page_id
        )
        if metadata is None:
            return SkillContextResult()

        lines = [
            _("Target page: %(title)s") % {"title": metadata["title"]},
            _("   Type: %(page_type)s") % {"page_type": metadata["page_type"]},
            _("   Status: %(status)s") % {"status": metadata["status"]},
            _("   URL/path: %(path)s") % {"path": metadata["path"]},
            _("   Last updated: %(updated)s") % {"updated": metadata["updated"]},
            _("   SEO description: %(seo_description)s")
            % {"seo_description": metadata["seo_description"]},
            _("   SEO title: %(seo_title)s") % {"seo_title": metadata["seo_title"]},
        ]

        lowered = (request_text or "").lower()
        if "under" in lowered or "children" in lowered:
            children = list(page.get_children().specific()[:max_items])
            if children:
                lines.append(_("Children:"))
                for index, child in enumerate(children, start=1):
                    child_meta = self._page_metadata(child, site)
                    if child_meta is None:
                        continue
                    lines.extend(
                        [
                            _("%(index)s. Title: %(title)s")
                            % {"index": index, "title": child_meta["title"]},
                            _("   Type: %(page_type)s")
                            % {"page_type": child_meta["page_type"]},
                            _("   Status: %(status)s")
                            % {"status": child_meta["status"]},
                            _("   URL/path: %(path)s") % {"path": child_meta["path"]},
                        ]
                    )

        instruction = _(
            "Admin content context may be provided below. Use it to help staff understand site structure and locate pages. This context is read-only. Do not claim to edit, publish, unpublish, move, or delete pages. Suggest where to navigate in Wagtail when useful, and do not expose secrets or private configuration."
        )
        return SkillContextResult.from_items(
            ["\n".join(lines)],
            instruction=instruction,
            metadata={
                "skill": self.key,
                "result_count": 1,
                "targeted_page_id": page.id,
                "resolution_reason": resolved.reason,
            },
        )

    def _search_pages(
        self, *, site, request, request_text: str, max_items: int
    ) -> list[dict]:
        queryset = self._base_queryset(site=site, request=request)
        if queryset is None:
            return []

        root_page = site.root_page
        request_text_lower = (request_text or "").lower()
        results = []

        homepage_item = self._page_metadata(root_page, site, is_homepage=True)
        if homepage_item:
            results.append(homepage_item)

        pages = self._select_pages(queryset, root_page, request_text_lower)[
            : max_items * 4
        ]
        ranked = []
        for page in pages:
            if page.id == root_page.id:
                continue
            metadata = self._page_metadata(page, site)
            if metadata is None:
                continue
            score = self._score_page(page, request_text_lower)
            ranked.append((score, metadata))

        ranked.sort(key=lambda item: (-item[0], item[1]["title"].lower()))
        results.extend(metadata for _, metadata in ranked[:max_items])
        return results[: max_items + 1]

    def _base_queryset(self, *, 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 _select_pages(self, queryset, root_page, request_text_lower: str):
        keywords = self._keywords(request_text_lower)
        if (
            "under the homepage" in request_text_lower
            or "homepage" in request_text_lower
        ):
            return list(root_page.get_children().specific())

        if "missing seo description" in request_text_lower or (
            "seo" in request_text_lower and "description" in request_text_lower
        ):
            return list(
                queryset.filter(
                    Q(search_description__isnull=True) | Q(search_description__exact="")
                )
            )

        if "draft" in request_text_lower:
            return list(queryset.filter(live=False))

        if "live" in request_text_lower:
            return list(queryset.filter(live=True))

        if not keywords:
            return list(root_page.get_children().specific())

        query = Q()
        for keyword in keywords:
            query |= (
                Q(title__icontains=keyword)
                | Q(slug__icontains=keyword)
                | Q(search_description__icontains=keyword)
                | Q(seo_title__icontains=keyword)
                | Q(url_path__icontains=keyword)
            )
        return list(queryset.filter(query))

    def _score_page(self, page, request_text_lower: str) -> int:
        keywords = self._keywords(request_text_lower)
        title = (page.title or "").lower()
        slug = (page.slug or "").lower()
        search_description = (getattr(page, "search_description", "") or "").lower()
        seo_title = (getattr(page, "seo_title", "") or "").lower()
        score = 0
        for keyword in keywords:
            if keyword in title:
                score += 5
            if keyword in slug:
                score += 4
            if keyword in search_description:
                score += 2
            if keyword in seo_title:
                score += 2
        if "draft" in request_text_lower and not page.live:
            score += 3
        if "live" in request_text_lower and page.live:
            score += 3
        if "seo" in request_text_lower and not getattr(page, "search_description", ""):
            score += 3
        return score

    def _page_metadata(self, page, site, is_homepage: bool = False) -> dict | None:
        title = (page.title or "").strip()
        if not title:
            return None
        return {
            "title": title,
            "page_type": self._page_type(page),
            "status": "live" if page.live else "draft",
            "path": self._relative_path(page, site),
            "updated": self._updated_label(page),
            "seo_description": (
                "present"
                if (getattr(page, "search_description", "") or "").strip()
                else "missing"
            ),
            "seo_title": (
                "present"
                if (getattr(page, "seo_title", "") or "").strip()
                else "missing"
            ),
            "is_homepage": is_homepage,
        }

    def _page_type(self, page) -> str:
        specific_class = getattr(page, "specific_class", None)
        if specific_class is not None:
            return specific_class.__name__
        content_type = getattr(page, "content_type", None)
        if content_type is not None:
            return content_type.model
        return page.__class__.__name__

    def _relative_path(self, 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}"

    def _updated_label(self, page) -> str:
        updated = getattr(page, "latest_revision_created_at", None) or getattr(
            page, "latest_revision_created_at", None
        )
        if updated is None:
            return _("unknown")
        return localtime(updated).strftime("%Y-%m-%d %H:%M")

    def _keywords(self, request_text: str) -> list[str]:
        seen = OrderedDict()
        for token in re.findall(r"[A-Za-z0-9]+", request_text.lower()):
            if len(token) < 3:
                continue
            seen[token] = None
            if len(seen) >= 8:
                break
        return list(seen.keys())
