from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required, user_passes_test
from django.http import HttpResponse
from django.contrib import messages
from django.db.models import Q
from datetime import date, timedelta
import csv

from .models import Entry
from .forms import ImportForm, EntryForm
from .utils import terms, has_wildcards, wildcard_to_regex, import_csv_bytes

# Order + labels used in the Search UI
FIELD_ORDER = [
    ("subject", "Subject"),
    ("illustration", "Illustration"),
    ("application", "Application"),
    ("scripture_raw", "Scripture"),
    ("source", "Source"),
    ("talk_title", "Talk Title"),
    ("talk_number", "Talk Number"),
    ("entry_code", "Code"),
]


def is_admin(user):
    return user.is_superuser or user.is_staff


def login_view(request):
    if request.user.is_authenticated:
        return redirect("search")
    ctx = {}
    if request.method == "POST":
        u = request.POST.get("username")
        p = request.POST.get("password")
        user = authenticate(request, username=u, password=p)
        if user:
            login(request, user)
            return redirect("search")
        ctx["error"] = "Invalid credentials"
    return render(request, "login.html", ctx)


def entry_context(entry, result_ids):
    """
    Build the navigation + chips context for the entry pages.
    """
    count = len(result_ids or [])
    if entry and result_ids and entry.id in result_ids:
        position = result_ids.index(entry.id) + 1
    else:
        position = 1

    subject_list = [t.strip() for t in (entry.subject or "").split(",") if t.strip()]
    scripture_list = [
        t.strip() for t in (entry.scripture_raw or "").split(";") if t.strip()
    ]
    return {
        "entry": entry,
        "locked": True,
        "position": position,
        "count": count,
        "subject_list": subject_list,
        "scripture_list": scripture_list,
    }


@login_required
def search_page(request):
    """
    Search-first landing. Defaults to Subject, Illustration, Application.
    Supports:
      - quoted phrases
      - * and ? wildcards (regex); if regex returns zero, falls back to icontains
      - AND across tokens, OR across the selected fields
    """
    default_fields = {
        "subject": True,
        "illustration": True,
        "application": True,
        "scripture_raw": False,
        "source": False,
        "talk_title": False,
        "talk_number": False,
        "entry_code": False,
    }

    # robust checkbox parsing: present only if checked
    form_submitted = ("q" in request.GET) or any(k in request.GET for k in default_fields)
    if form_submitted:
        selected = {k: (k in request.GET) for k in default_fields}
    else:
        selected = default_fields.copy()

    field_options = [
        {"name": k, "label": label, "checked": bool(selected.get(k))}
        for k, label in FIELD_ORDER
    ]

    q = (request.GET.get("q") or "").strip()
    if q:
        tokens = terms(q)  # supports quoted phrases
        fields = [f for f, sel in selected.items() if sel] or ["subject"]

        # ---------- Pass A: regex for wildcard tokens ----------
        qs = Entry.objects.all()
        used_regex = False
        for tok in tokens:
            clause = Q()
            if has_wildcards(tok):
                used_regex = True
                pattern = wildcard_to_regex(tok)  # e.g. "*love*" -> ".*love.*"
                for f in fields:
                    clause |= Q(**{f + "__iregex": pattern})
            else:
                for f in fields:
                    clause |= Q(**{f + "__icontains": tok})
            qs = qs.filter(clause)

        ids = list(qs.order_by("-date_added", "-id").values_list("id", flat=True))

        # ---------- Pass B (fallback): plain icontains if regex yielded 0 ----------
        if used_regex and not ids:
            qs = Entry.objects.all()
            for tok in tokens:
                clause = Q()
                tok_stripped = tok.replace("*", "").replace("?", "")  # fallback normalize
                for f in fields:
                    clause |= Q(**{f + "__icontains": tok_stripped})
                qs = qs.filter(clause)
            ids = list(qs.order_by("-date_added", "-id").values_list("id", flat=True))

        # Debug to container logs
        try:
            print(f"[search] q={q!r} tokens={tokens} fields={fields} count={len(ids)}")
        except Exception:
            pass

        request.session["result_ids"] = ids
        count = len(ids)

        if count:
            entry = Entry.objects.get(pk=ids[0])
            ctx = entry_context(entry, ids)
            ctx.update({"from_search": True})
            return render(request, "entry_view.html", ctx)

        total = Entry.objects.count()
        return render(
            request,
            "search.html",
            {
                "q": q,
                "selected": selected,
                "field_options": field_options,
                "total": total,
                "ran_search": True,
                "result_count": 0,
            },
        )

    total = Entry.objects.count()
    return render(
        request,
        "search.html",
        {
            "q": q,
            "selected": selected,
            "field_options": field_options,
            "total": total,
            "ran_search": False,
        },
    )


@login_required
def nav_next(request):
    ids = request.session.get("result_ids", [])
    if not ids:
        return redirect("search")
    idx = int(request.GET.get("i", "0"))
    idx = min(idx + 1, len(ids) - 1)
    entry = get_object_or_404(Entry, pk=ids[idx])
    return render(request, "entry_view.html", entry_context(entry, ids))


@login_required
def nav_prev(request):
    ids = request.session.get("result_ids", [])
    if not ids:
        return redirect("search")
    idx = int(request.GET.get("i", "0"))
    idx = max(idx - 1, 0)
    entry = get_object_or_404(Entry, pk=ids[idx])
    return render(request, "entry_view.html", entry_context(entry, ids))


@login_required
def entry_view(request, entry_id):
    ids = request.session.get("result_ids", [])
    entry = get_object_or_404(Entry, pk=entry_id)
    return render(request, "entry_view.html", entry_context(entry, ids))


@login_required
def entry_edit(request, entry_id):
    ids = request.session.get("result_ids", [])
    entry = get_object_or_404(Entry, pk=entry_id)

    if request.method == "POST":
        form = EntryForm(request.POST)
        if form.is_valid():
            for k, v in form.cleaned_data.items():
                setattr(entry, k, v)
            entry.save()
            messages.success(request, "Entry saved.")
            return redirect("entry_view", entry_id=entry.id)
    else:
        form = EntryForm(
            initial={
                "subject": entry.subject,
                "illustration": entry.illustration,
                "application": entry.application,
                "scripture_raw": entry.scripture_raw,
                "source": entry.source,
                "talk_number": entry.talk_number,
                "talk_title": entry.talk_title,
                "entry_code": entry.entry_code,
                "date_added": entry.date_added,
                "date_edited": entry.date_edited,
            }
        )

    ctx = {"entry": entry, "form": form}
    ctx.update(entry_context(entry, ids))
    return render(request, "entry_edit.html", ctx)


@login_required
def entry_delete(request, entry_id):
    entry = get_object_or_404(Entry, pk=entry_id)
    if request.method == "POST":
        entry.delete()
        messages.success(request, "Entry deleted.")
        return redirect("search")
    return render(request, "entry_delete_confirm.html", {"entry": entry})


@login_required
@user_passes_test(is_admin)
def import_wizard(request):
    if request.method == "POST":
        form = ImportForm(request.POST, request.FILES)
        if form.is_valid():
            try:
                report = import_csv_bytes(
                    form.cleaned_data["file"].read(),
                    dry_run=form.cleaned_data["dry_run"],
                )
                return render(
                    request,
                    "import_result.html",
                    {"report": report, "dry_run": form.cleaned_data["dry_run"]},
                )
            except Exception as e:
                messages.error(request, f"Import failed: {e}")
    else:
        form = ImportForm()
    return render(request, "import_wizard.html", {"form": form})


@login_required
@user_passes_test(is_admin)
def export_csv(request):
    ts = date.today().strftime("%Y-%m-%d")
    response = HttpResponse(content_type="text/csv")
    response["Content-Disposition"] = (
        f'attachment; filename="illustrations_backup_{ts}.csv"'
    )
    w = csv.writer(response)
    w.writerow(
        [
            "Subject",
            "Illustration",
            "Application",
            "Scripture",
            "Source",
            "Talk Number",
            "Talk Title",
            "Code",
            "Date",
            "Date Edited",
        ]
    )
    for e in Entry.objects.all().order_by("id"):
        w.writerow(
            [
                e.subject,
                e.illustration,
                e.application,
                e.scripture_raw,
                e.source,
                e.talk_number if e.talk_number is not None else "",
                e.talk_title,
                e.entry_code,
                e.date_added.isoformat() if e.date_added else "",
                e.date_edited.isoformat() if e.date_edited else "",
            ]
        )
    return response


@login_required
def stats_page(request):
    total = Entry.objects.count()
    today = date.today()
    last30 = Entry.objects.filter(date_added__gte=today - timedelta(days=30)).count()
    last365 = Entry.objects.filter(date_added__gte=today - timedelta(days=365)).count()

    # last 12 months
    months = []
    y = today.year
    m = today.month
    for i in range(12):
        mm = m - i
        yy = y
        while mm <= 0:
            mm += 12
            yy -= 1
        from datetime import date as _d

        start = _d(yy, mm, 1)
        end = _d(yy + 1, 1, 1) if mm == 12 else _d(yy, mm + 1, 1)
        label = f"{yy}-{mm:02d}"
        months.append((label, start, end))
    months = list(reversed(months))

    series = [
        (label, Entry.objects.filter(date_added__gte=start, date_added__lt=end).count())
        for label, start, end in months
    ]
    peak = max((v for _, v in series), default=1)
    heights = [
        (label, value, 8 + int((value / peak) * 100) if peak else 8)
        for label, value in series
    ]

    # top subjects
    from collections import Counter

    counts = Counter()
    for subj in Entry.objects.exclude(subject="").values_list("subject", flat=True):
        for tag in [t.strip() for t in subj.split(",") if t.strip()]:
            counts[tag.lower()] += 1
    top_subjects = [{"name": n.title(), "count": c} for n, c in counts.most_common(10)]

    return render(
        request,
        "stats.html",
        {
            "total": total,
            "last30": last30,
            "last365": last365,
            "series": series,
            "heights": heights,
            "top_subjects": top_subjects,
        },
    )