from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.admin.views.decorators import staff_member_required
from django.views.decorators.http import require_POST
from django.contrib import messages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.urls import reverse, get_resolver, URLPattern, URLResolver, NoReverseMatch
from functools import wraps
from typing import Any, Dict, List, Optional, Tuple

from admin_frontend.templatetags.public_urls import public_route
from admin_frontend.nav import _nav_items

from .models import Post, PostCategory
from .forms import PostForm, PostCategoryForm
from .filters import PostFilter


# --- Lightweight breadcrumbs decorator -------------------------------------

def _iter_patterns(resolver: URLResolver, ns_path: str = ""):
    for entry in resolver.url_patterns:
        if isinstance(entry, URLResolver):
            new_ns = ns_path
            if entry.namespace:
                new_ns = f"{ns_path}:{entry.namespace}" if ns_path else entry.namespace
            yield from _iter_patterns(entry, new_ns)
        elif isinstance(entry, URLPattern):
            full_name = entry.name
            if full_name and ns_path:
                full_name = f"{ns_path}:{full_name}"
            yield entry, full_name


def _find_pattern_by_breadcrumb_name(name: str) -> Tuple[Optional[URLPattern], Optional[str]]:
    resolver = get_resolver()
    for pattern, full_name in _iter_patterns(resolver):
        if not full_name:
            continue
        view = pattern.callback
        # unwrap to find metadata on inner functions
        meta = None
        cur = view
        while cur is not None and meta is None:
            meta = getattr(cur, "_breadcrumbs_meta", None)
            cur = getattr(cur, "__wrapped__", None)
        if meta and meta.get("name") == name:
            return pattern, full_name
    return None, None


def _build_breadcrumb_chain(meta: Dict[str, Any]) -> List[Tuple[str, Optional[str]]]:
    chain: List[Tuple[str, Optional[str]]] = []
    # Walk parent chain (by name) outward
    parent_name = meta.get("parent")
    while parent_name:
        pattern, full_name = _find_pattern_by_breadcrumb_name(parent_name)
        if not pattern:
            break
        view = pattern.callback
        vmeta = None
        cur = view
        while cur is not None and vmeta is None:
            vmeta = getattr(cur, "_breadcrumbs_meta", None)
            cur = getattr(cur, "__wrapped__", None)
        vmeta = vmeta or {}
        label = vmeta.get("label") or parent_name
        # Try to reverse parent URL (skip if args required)
        url: Optional[str] = None
        if full_name:
            try:
                url = reverse(full_name)
            except NoReverseMatch:
                url = None
        chain.insert(0, (label, url))
        parent_name = vmeta.get("parent")
    # Add current at the end (no URL)
    chain.append((meta.get("label") or "", None))
    return chain


def breadcrumbs(*, label: str, name: Optional[str] = None, parent: Optional[str] = None):
    """Decorator to attach breadcrumb metadata and expose a computed trail.

    Usage:
      @breadcrumbs(label="Posts", name="posts")
      def posts_list(...): ...

      @breadcrumbs(label="Post Edit", parent="posts")
      def post_edit(...): ...
    """

    def _decorator(view_func):
        meta = {"label": label, "name": name, "parent": parent}

        @wraps(view_func)
        def _wrapped(request, *args, **kwargs):
            # Expose breadcrumbs via request for templates/tags to consume
            request._breadcrumbs = _build_breadcrumb_chain(meta)
            return view_func(request, *args, **kwargs)

        # Make metadata discoverable on the resolved callback
        setattr(_wrapped, "_breadcrumbs_meta", meta)
        return _wrapped

    return _decorator


@staff_member_required
@breadcrumbs(label="Posts", name="posts")
@public_route(label="CMS Posts", order=70, icon="document-text")
def posts_list(request):
    if request.method == "POST":
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():
            obj = form.save(commit=False)
            # Scope to current organization if available
            org = getattr(request, "org", None)
            if org is not None:
                obj.organization = org
            obj.save()
            messages.success(request, "Post created.")
            return redirect("cms:posts_list")
        else:
            messages.error(request, "Please correct the errors below.")
    else:
        form = PostForm()

    # Restrict category choices to current organization
    org = getattr(request, "org", None)
    if org is not None:
        try:
            form.fields["category"].queryset = PostCategory.objects.filter(organization=org)
        except Exception:
            pass

    qs = Post.objects.select_related("category", "author").all().order_by("-published_at", "-created_at")
    if org is not None:
        qs = qs.filter(organization=org)
    post_filter = PostFilter(request.GET, queryset=qs)
    # Also scope the filter's category queryset
    if org is not None:
        try:
            post_filter.form.fields["category"].queryset = PostCategory.objects.filter(organization=org)
        except Exception:
            pass
    paginator = Paginator(post_filter.qs, 10)
    page = request.GET.get("page")
    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)

    context = {
        "nav": _nav_items(),
        "posts": page_obj.object_list,
        "page_obj": page_obj,
        "form": form,
        "filter": post_filter,
    }
    return render(request, "cms/posts_list.html", context)


@staff_member_required
@breadcrumbs(label="Post Edit", parent="posts")
def post_edit(request, pk: int):
    org = getattr(request, "org", None)
    base_qs = Post.objects.all()
    if org is not None:
        base_qs = base_qs.filter(organization=org)
    item = get_object_or_404(base_qs, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST, request.FILES, instance=item)
        if form.is_valid():
            obj = form.save(commit=False)
            org = getattr(request, "org", None)
            if org is not None:
                obj.organization = org
            obj.save()
            messages.success(request, "Post updated.")
            return redirect("cms:post_edit", pk=item.pk)
    else:
        form = PostForm(instance=item)

    # Restrict category choices
    org = getattr(request, "org", None)
    if org is not None:
        try:
            form.fields["category"].queryset = PostCategory.objects.filter(organization=org)
        except Exception:
            pass

    context = {
        "nav": _nav_items(),
        "item": item,
        "form": form,
        "cancel_url": reverse("cms:posts_list"),
    }
    return render(request, "cms/post_form.html", context)


@staff_member_required
@require_POST
def post_delete(request, pk: int):
    org = getattr(request, "org", None)
    base_qs = Post.objects.all()
    if org is not None:
        base_qs = base_qs.filter(organization=org)
    item = get_object_or_404(base_qs, pk=pk)
    item.delete()
    messages.success(request, "Post deleted.")
    return redirect("cms:posts_list")


@staff_member_required
@public_route(label="CMS Categories", order=75, icon="tag")
def categories_list(request):
    if request.method == "POST":
        form = PostCategoryForm(request.POST)
        if form.is_valid():
            obj = form.save(commit=False)
            org = getattr(request, "org", None)
            if org is not None:
                obj.organization = org
            obj.save()
            messages.success(request, "Category created.")
            return redirect("cms:categories_list")
        else:
            messages.error(request, "Please correct the errors below.")
    else:
        form = PostCategoryForm()

    q = (request.GET.get("q") or "").strip()
    qs = PostCategory.objects.all().order_by("name")
    org = getattr(request, "org", None)
    if org is not None:
        qs = qs.filter(organization=org)
    if q:
        qs = qs.filter(name__icontains=q)

    paginator = Paginator(qs, 15)
    page = request.GET.get("page")
    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)

    context = {
        "nav": _nav_items(),
        "categories": qs,
        "page_obj": page_obj,
        "form": form,
        "query": q,
    }
    return render(request, "cms/categories_list.html", context)


@staff_member_required
def category_edit(request, pk: int):
    org = getattr(request, "org", None)
    base_qs = PostCategory.objects.all()
    if org is not None:
        base_qs = base_qs.filter(organization=org)
    item = get_object_or_404(base_qs, pk=pk)
    if request.method == "POST":
        form = PostCategoryForm(request.POST, instance=item)
        if form.is_valid():
            obj = form.save(commit=False)
            org = getattr(request, "org", None)
            if org is not None:
                obj.organization = org
            obj.save()
            messages.success(request, "Category updated.")
            return redirect("cms:category_edit", pk=item.pk)
    else:
        form = PostCategoryForm(instance=item)

    # Restrict parent choices
    org = getattr(request, "org", None)
    if org is not None:
        try:
            form.fields["parent"].queryset = PostCategory.objects.filter(organization=org)
        except Exception:
            pass

    context = {
        "nav": _nav_items(),
        "item": item,
        "form": form,
        "cancel_url": reverse("cms:categories_list"),
    }
    return render(request, "cms/category_form.html", context)


@staff_member_required
@require_POST
def category_delete(request, pk: int):
    org = getattr(request, "org", None)
    base_qs = PostCategory.objects.all()
    if org is not None:
        base_qs = base_qs.filter(organization=org)
    item = get_object_or_404(base_qs, pk=pk)
    item.delete()
    messages.success(request, "Category deleted.")
    return redirect("cms:categories_list")
