from __future__ import annotations

import json
from pathlib import Path

from django.conf import settings
from django.contrib.auth.decorators import login_required, user_passes_test
from django.http import FileResponse, JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_POST

from .services import ConcordanceDraftStore, ConcordanceEditorError, ConcordanceEditorPaths


def _can_use_editor(user) -> bool:
    minimum = int(getattr(settings, "CONCORDANCE_EDITOR_MIN_USER_TYPE", 2))
    return bool(
        user.is_authenticated
        and (
            user.is_superuser
            or user.is_staff
            or int(getattr(user, "user_type", 0) or 0) >= minimum
        )
    )


def editor_required(view):
    return login_required(user_passes_test(_can_use_editor)(view))


def _store() -> ConcordanceDraftStore:
    live_root = Path(getattr(settings, "CONCORDANCE_EDITOR_LIVE_ROOT"))
    draft_root = Path(getattr(settings, "CONCORDANCE_EDITOR_DRAFT_ROOT"))
    return ConcordanceDraftStore(ConcordanceEditorPaths.from_roots(live_root, draft_root))


def _user_name(request) -> str:
    return (
        getattr(request.user, "get_username", lambda: "")()  # type: ignore[misc]
        or getattr(request.user, "username", "")
        or "unknown"
    )


def _json_body(request) -> dict:
    if not request.body:
        return {}
    try:
        payload = json.loads(request.body.decode("utf-8"))
    except (UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise ConcordanceEditorError(f"Request body must be valid JSON: {exc}") from exc
    if not isinstance(payload, dict):
        raise ConcordanceEditorError("Request body must be a JSON object.")
    return payload


def _error(message: str, *, status: int = 400) -> JsonResponse:
    return JsonResponse({"ok": False, "error": message}, status=status)


@editor_required
@require_GET
def editor_home(request):
    return render(
        request,
        "concordance_editor/editor.html",
        {
            "api_root": "/concordances/api/",
            "download_settings_url": "/concordances/download/settings_new.json",
            "download_manifest_url": "/concordances/download/settings_new.manifest.json",
        },
    )


@editor_required
@require_GET
def api_session(request):
    try:
        return JsonResponse({"ok": True, "session": _store().session_status()})
    except ConcordanceEditorError as exc:
        return _error(str(exc))


@csrf_exempt
@editor_required
@require_POST
def api_session_start(request):
    try:
        payload = _json_body(request)
        mode = str(payload.get("mode") or "resume").strip().lower()
        if mode not in {"resume", "discard"}:
            raise ConcordanceEditorError("mode must be either 'resume' or 'discard'.")
        session = _store().start_session(_user_name(request), discard=(mode == "discard"))
        return JsonResponse({"ok": True, "session": session})
    except ConcordanceEditorError as exc:
        return _error(str(exc))


@editor_required
@require_GET
def api_search(request):
    try:
        payload = _store().search_settings(
            request.GET.get("q", ""),
            limit=int(request.GET.get("limit", "25") or "25"),
        )
        return JsonResponse({"ok": True, **payload})
    except (ValueError, ConcordanceEditorError) as exc:
        return _error(str(exc))


@editor_required
@require_GET
def api_setting(request):
    try:
        setting_key_value = str(request.GET.get("setting_key") or "").strip()
        if not setting_key_value:
            raise ConcordanceEditorError("setting_key is required.")
        return JsonResponse(
            {
                "ok": True,
                "setting": _store().setting_detail(setting_key_value),
            }
        )
    except ConcordanceEditorError as exc:
        return _error(str(exc))


@csrf_exempt
@editor_required
@require_POST
def api_action(request):
    try:
        payload = _json_body(request)
        result = _store().apply_operation(
            left_setting_key=str(payload.get("left_setting_key") or "").strip(),
            operation=str(payload.get("operation") or "").strip(),
            right_setting_key=str(payload.get("right_setting_key") or "").strip(),
            user_name=_user_name(request),
        )
        return JsonResponse(result)
    except ConcordanceEditorError as exc:
        return _error(str(exc))


@csrf_exempt
@editor_required
@require_POST
def api_export(request):
    try:
        result = _store().prepare_export(_user_name(request))
        return JsonResponse(result)
    except ConcordanceEditorError as exc:
        return _error(str(exc))


@editor_required
@require_GET
def download_settings(request):
    store = _store()
    if not store.draft_exists():
        return _error("No draft settings_new.json is available yet.", status=404)
    return FileResponse(
        store.paths.draft_settings_path.open("rb"),
        as_attachment=True,
        filename="settings_new.json",
        content_type="application/json",
    )


@editor_required
@require_GET
def download_manifest(request):
    try:
        store = _store()
        if not store.draft_exists():
            return _error("No draft exists yet, so there is no manifest to download.", status=404)
        if not store.paths.draft_manifest_path.exists():
            store.prepare_export(_user_name(request))
        return FileResponse(
            store.paths.draft_manifest_path.open("rb"),
            as_attachment=True,
            filename="settings_new.manifest.json",
            content_type="application/json",
        )
    except ConcordanceEditorError as exc:
        return _error(str(exc))
