import io
import json
from functools import wraps
from urllib.parse import urlencode

import pandas as pd
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Q
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseNotAllowed, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse

from .access import allowed_columns, can_export, crm_can_assign, crm_level, visible_schools
from .backups import BackupError, build_backup, restore_backup
from .cache import finder_rows
from .forms import SchoolForm
from .finder_data import finder_result
from .models import School
from .pages import base_context

MAX_BACKUP_SIZE = 100 * 1024 * 1024


def api_login_required(view):
    """Like login_required, but answers APIs with JSON 401 instead of a redirect."""
    @wraps(view)
    def wrapper(request, *a, **kw):
        if not request.user.is_authenticated:
            return JsonResponse({"detail": "Login required"}, status=401)
        return view(request, *a, **kw)
    return wrapper


@login_required
def index(request):
    u = request.user
    ctx = base_context(request, nav="schools")
    config = {"dump_url": "/api/dump", "export_url": "/api/export", "login_url": reverse("login"), "can_export": can_export(u),
              "crm": None}
    if ctx["crm_enabled"]:
        from django.contrib.auth import get_user_model
        from django.db.models import Q

        from crm.constants import PRIORITIES, STAGES
        people = get_user_model().objects.filter(is_active=True).filter(
            Q(is_superuser=True) | Q(access_profile__crm_access="edit")).order_by("username")
        config["crm"] = {"can_edit": ctx["crm_can_edit"], "can_assign": crm_can_assign(u),
                         "stages": STAGES, "priorities": PRIORITIES, "me": u.pk,
                         "username": u.get_username(), "users": [[p.pk, p.get_username()] for p in people]}
    ctx.update({"school_count": visible_schools(u).count(), "config": config})
    resp = render(request, "finder.html", ctx)
    resp["Cache-Control"] = "no-store"
    return resp


@api_login_required
def me(request):
    u = request.user
    return JsonResponse({"username": u.get_username(), "is_admin": u.is_superuser,
                         "can_export": can_export(u),
                         "schools": visible_schools(u).count(),
                         "columns": allowed_columns(u)})


@api_login_required
def dump(request):
    if request.GET.get("ids_only") == "1" and crm_level(request.user) != "edit":
        return JsonResponse({"detail": "You don't have permission to update CRM records."}, status=403)
    resp = JsonResponse(finder_result(request.user, request.GET))
    resp["Cache-Control"] = "no-store"
    return resp


@api_login_required
def export(request):
    u = request.user
    if not can_export(u):
        return JsonResponse({"detail": "Downloads are not enabled for your account."}, status=403)
    params = request.GET.copy()
    fmt = params.pop("fmt", ["xlsx"])[0]
    result = finder_result(u, params, include_all=True)
    df = pd.DataFrame(result["results"])
    if fmt == "csv":
        resp = HttpResponse(df.to_csv(index=False).encode("utf-8-sig"), content_type="text/csv")
        resp["Content-Disposition"] = 'attachment; filename="schools.csv"'
        return resp
    buf = io.BytesIO()
    with pd.ExcelWriter(buf, engine="openpyxl") as w:
        df.to_excel(w, sheet_name="Schools", index=False)
    resp = HttpResponse(buf.getvalue(),
                        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    resp["Content-Disposition"] = 'attachment; filename="schools.xlsx"'
    return resp


@login_required
def school_admin(request):
    if not request.user.is_superuser:
        return HttpResponseForbidden("Only administrators can manage schools.")

    edit_id = request.GET.get("edit")
    school_obj = get_object_or_404(School, pk=edit_id) if edit_id else None
    show_form = bool(edit_id or request.GET.get("new") == "1")

    if request.method == "POST":
        action = request.POST.get("action")
        pk = request.POST.get("school_id")
        if action == "restore":
            school = get_object_or_404(School.all_objects, pk=pk, deleted_at__isnull=False)
            school.restore()
            messages.success(request, f"Restored {school.name}.")
            return redirect("school_admin")
        if action == "delete":
            school = get_object_or_404(School, pk=pk)
            school.delete()
            messages.success(request, f"Moved {school.name} to Recently deleted.")
            return redirect("school_admin")

        school_pk = request.POST.get("school_id")
        if school_pk:
            school_obj = get_object_or_404(School, pk=school_pk)
            form = SchoolForm(request.POST, instance=school_obj)
        else:
            form = SchoolForm(request.POST)
        show_form = True

        if form.is_valid():
            form.save()
            messages.success(request, "School saved successfully.")
            return redirect("school_admin")
    else:
        form = SchoolForm(instance=school_obj)

    search_query = (request.GET.get("q") or "").strip()[:100]
    search_filter = Q()
    if search_query:
        search_filter = (
            Q(name__icontains=search_query)
            | Q(district_name__icontains=search_query)
            | Q(municipality__icontains=search_query)
            | Q(school_type__icontains=search_query)
        )
        if search_query.isdigit():
            search_filter |= Q(organization_id=int(search_query)) | Q(district_id=int(search_query))

    school_page = Paginator(
        School.objects.filter(search_filter).order_by("district_id", "organization_id"), 25
    ).get_page(request.GET.get("page"))
    deleted_page = Paginator(
        School.all_objects.filter(deleted_at__isnull=False).filter(search_filter)
        .order_by("-deleted_at", "organization_id"), 15
    ).get_page(request.GET.get("deleted_page"))

    previous_school_url = next_school_url = None
    if school_page.has_previous():
        previous_school_url = "?" + urlencode({"q": search_query, "page": school_page.previous_page_number()})
    if school_page.has_next():
        next_school_url = "?" + urlencode({"q": search_query, "page": school_page.next_page_number()})

    deleted_page_urls = {"previous": None, "next": None}
    if deleted_page.has_previous():
        deleted_page_urls["previous"] = "?" + urlencode({
            "q": search_query, "deleted_page": deleted_page.previous_page_number(), "show_deleted": "1",
        })
    if deleted_page.has_next():
        deleted_page_urls["next"] = "?" + urlencode({
            "q": search_query, "deleted_page": deleted_page.next_page_number(), "show_deleted": "1",
        })

    ctx = base_context(request, nav="school-admin")
    ctx.update({
        "schools": school_page.object_list,
        "school_page": school_page,
        "school_search": search_query,
        "school_previous_url": previous_school_url,
        "school_next_url": next_school_url,
        "deleted_schools": deleted_page.object_list,
        "deleted_page": deleted_page,
        "deleted_count": School.all_objects.filter(deleted_at__isnull=False).count(),
        "deleted_previous_url": deleted_page_urls["previous"],
        "deleted_next_url": deleted_page_urls["next"],
        "show_deleted_dialog": request.GET.get("show_deleted") == "1",
        "form": form,
        "school": school_obj,
        "editing_id": edit_id,
        "show_form": show_form,
    })
    return render(request, "school_admin.html", ctx)


@login_required
def school_backup(request):
    if not request.user.is_superuser:
        return HttpResponseForbidden("Only administrators can import or export full backups.")
    if request.method != "GET":
        return HttpResponseNotAllowed(["GET"])

    payload = json.dumps(build_backup(), ensure_ascii=False, separators=(",", ":"))
    response = HttpResponse(payload, content_type="application/json; charset=utf-8")
    response["Content-Disposition"] = 'attachment; filename="school-portal-backup.json"'
    response["Cache-Control"] = "no-store"
    return response


@login_required
def data_backup(request):
    if not request.user.is_superuser:
        return HttpResponseForbidden("Only administrators can import or export full backups.")
    if request.method == "POST":
        upload = request.FILES.get("backup_file")
        if not upload:
            messages.error(request, "Choose a backup file to import.")
            return redirect("data_backup")
        if upload.size > MAX_BACKUP_SIZE:
            messages.error(request, "Backup file is too large (maximum 100 MB).")
            return redirect("data_backup")
        try:
            payload = json.loads(upload.read().decode("utf-8-sig"))
            counts = restore_backup(payload)
        except (UnicodeDecodeError, json.JSONDecodeError, BackupError) as exc:
            messages.error(request, f"Backup was not imported: {exc}")
            return redirect("data_backup")

        messages.success(
            request,
            "Imported {schools} schools, {leads} leads, {activities} activities, and {contacts} contacts."
            .format(**counts),
        )
        return redirect("data_backup")
    if request.method != "GET":
        return HttpResponseNotAllowed(["GET", "POST"])

    ctx = base_context(request, nav="data-backup")
    return render(request, "data_backup.html", ctx)
