from __future__ import annotations

import json
import logging
import uuid

from django.contrib.admin.views.decorators import staff_member_required
from django.http import Http404
from django.http import HttpRequest, JsonResponse
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.decorators.http import require_GET, require_POST
from wagtail.models import Site

from .capabilities import build_capability_context
from .constants import (
    MAX_MESSAGE_LENGTH,
    MODE_ADMIN,
    MODE_CUSTOMER,
    ROLE_ASSISTANT,
    ROLE_USER,
    SESSION_CLOSED,
    VISITOR_COOKIE_MAX_AGE,
)
from .models import AiAssistantMessage, AiAssistantSession, AiAssistantSiteSettings
from .services import generate_reply
from .throttling import check_and_increment_throttle, get_client_ip

logger = logging.getLogger(__name__)
COOKIE_VISITOR_KEY = "mb_ai_vid"


def _customer_chat_strings() -> dict:
    return {
        "assistantLabel": _("Assistant"),
        "userLabel": _("You"),
        "copy": _("Copy"),
        "copied": _("Copied"),
        "unavailable": _("Unavailable"),
        "sending": _("Sending…"),
        "send": _("Send message"),
        "genericError": _(
            "The assistant is temporarily unavailable. Please try again."
        ),
        "emptyMessageError": _("Please enter a message before sending."),
        "tooLongError": _("Your message is too long. Please shorten it and try again."),
        "disabledError": _("The assistant is currently unavailable."),
        "requestError": _(
            "The assistant could not process your message. Please try again."
        ),
        "networkError": _("Network error. Please check your connection and try again."),
        "resetConfirmError": _(
            "The conversation was cleared locally, but the reset request could not be confirmed."
        ),
        "rateLimitedError": _(
            "You are sending messages too quickly. Please wait about {retry_after} seconds and try again."
        ),
        "copyAriaLabel": _("Copy assistant reply"),
        "openLauncher": _("Open chat"),
        "closeLauncher": _("Close chat"),
        "clearChat": _("Clear chat"),
        "startConversation": _("Start a conversation"),
        "sessionDescription": _(
            "Your messages stay in this browser session so you can continue the conversation."
        ),
        "retry": _("Retry"),
        "messageErrorTitle": _("Message could not be sent."),
        "typing": _("Assistant is typing…"),
    }


def _admin_chat_strings() -> dict:
    return {
        "assistantLabel": _("Assistant"),
        "userLabel": _("You"),
        "copy": _("Copy"),
        "copied": _("Copied"),
        "unavailable": _("Unavailable"),
        "sending": _("Sending…"),
        "send": _("Send message"),
        "genericError": _(
            "The assistant is temporarily unavailable. Please try again."
        ),
        "emptyMessageError": _("Please enter a message before sending."),
        "tooLongError": _("Your message is too long. Please shorten it and try again."),
        "disabledError": _("The assistant is currently unavailable for admin use."),
        "requestError": _(
            "The assistant could not process your request. Please try again."
        ),
        "networkError": _("Network error. Please check your connection and try again."),
        "resetConfirmError": _(
            "The chat was cleared locally, but the reset request could not be confirmed."
        ),
        "rateLimitedError": _(
            "You are sending messages too quickly. Please wait about {retry_after} seconds and try again."
        ),
        "copyAriaLabel": _("Copy assistant reply"),
        "businessModeLabel": _("Business Assistant Mode"),
        "businessModeHelp": _(
            "When enabled, the assistant may use internal business context that is available only to permitted staff."
        ),
    }


def _current_site(request: HttpRequest) -> Site:
    return Site.find_for_request(request) or Site.objects.get(is_default_site=True)


def _site_settings(site: Site) -> AiAssistantSiteSettings:
    return AiAssistantSiteSettings.for_site(site)


def customer_public_chat_enabled(settings_obj: AiAssistantSiteSettings) -> bool:
    return settings_obj.public_chat_enabled()


def customer_page_enabled(settings_obj: AiAssistantSiteSettings) -> bool:
    return settings_obj.customer_page_enabled()


def _load_payload(request: HttpRequest) -> dict:
    try:
        payload = json.loads(request.body.decode("utf-8"))
        return payload if isinstance(payload, dict) else None
    except Exception:
        return None


def _message_too_long(message_text: str) -> bool:
    return len(message_text) > MAX_MESSAGE_LENGTH


def _business_mode_requested(payload: dict, mode: str, request: HttpRequest) -> bool:
    if mode != MODE_ADMIN:
        return False
    if not getattr(request.user, "is_staff", False):
        return False
    return bool(payload.get("business_mode"))


def _resolve_session(
    payload: dict, site_id: int, mode: str, user, visitor_id: str
) -> AiAssistantSession:
    session = _resolve_existing_session(payload, site_id, mode, user, visitor_id)
    if session:
        return session

    return AiAssistantSession.objects.create(
        site_id=site_id,
        mode=mode,
        user=user if getattr(user, "is_authenticated", False) else None,
        visitor_id=visitor_id,
        language_code=(payload.get("language") or "")[:12],
        source_path=(payload.get("path") or "")[:255],
    )


def _resolve_existing_session(
    payload: dict, site_id: int, mode: str, user, visitor_id: str
) -> AiAssistantSession | None:
    session_uuid = payload.get("session_id")
    if not session_uuid:
        return None

    session = AiAssistantSession.objects.filter(
        uuid=session_uuid,
        site_id=site_id,
        mode=mode,
    ).first()
    if not session:
        return None

    if mode == MODE_ADMIN:
        user_id = getattr(user, "id", None)
        if session.user_id and user_id and session.user_id == user_id:
            return session
        logger.warning(
            "ai_assistant.session_rejected",
            extra={
                "assistant_mode": mode,
                "session_uuid": str(session.uuid),
                "reason": "admin_user_mismatch",
            },
        )
        return None

    if session.visitor_id and session.visitor_id == visitor_id:
        return session
    logger.warning(
        "ai_assistant.session_rejected",
        extra={
            "assistant_mode": mode,
            "session_uuid": str(session.uuid),
            "reason": "visitor_mismatch",
        },
    )
    return None


def _chat_response(request: HttpRequest, mode: str, require_staff: bool = False):
    site = _current_site(request)
    settings_obj = _site_settings(site)

    if mode == MODE_CUSTOMER and not customer_public_chat_enabled(settings_obj):
        return JsonResponse({"error": "customer_assistant_disabled"}, status=403)
    if mode == MODE_ADMIN and (
        not settings_obj.admin_enabled or (require_staff and not request.user.is_staff)
    ):
        return JsonResponse({"error": "admin_assistant_disabled"}, status=403)

    payload = _load_payload(request)
    if payload is None:
        logger.warning(
            "ai_assistant.invalid_payload",
            extra={"assistant_mode": mode, "reason": "invalid_json"},
        )
        return JsonResponse({"error": "invalid_json"}, status=400)

    message_text = (payload.get("message") or "").strip()
    request.ai_assistant_source_path = (payload.get("path") or "").strip()[:255]
    if not message_text:
        logger.warning(
            "ai_assistant.invalid_payload",
            extra={"assistant_mode": mode, "reason": "empty_message"},
        )
        return JsonResponse({"error": "message_required"}, status=400)
    if _message_too_long(message_text):
        logger.warning(
            "ai_assistant.invalid_payload",
            extra={
                "assistant_mode": mode,
                "reason": "message_too_long",
                "message_length": len(message_text),
            },
        )
        return JsonResponse(
            {"error": "message_too_long", "max_length": MAX_MESSAGE_LENGTH},
            status=400,
        )

    visitor_id = request.COOKIES.get(COOKIE_VISITOR_KEY) or str(uuid.uuid4())
    client_ip = get_client_ip(request)
    throttle = check_and_increment_throttle(
        mode=mode,
        site_id=site.id,
        session_id=str(payload.get("session_id") or ""),
        visitor_id=visitor_id if mode == MODE_CUSTOMER else "",
        user_id=(
            getattr(request.user, "id", None)
            if getattr(request.user, "is_authenticated", False)
            else None
        ),
        client_ip=client_ip,
    )
    if not throttle["allowed"]:
        logger.warning(
            "ai_assistant.rate_limited",
            extra={
                "assistant_mode": mode,
                "site_id": site.id,
                "client_ip": client_ip,
                "retry_after": throttle["retry_after"],
            },
        )
        return JsonResponse(
            {
                "error": "rate_limited",
                "retry_after": throttle["retry_after"],
            },
            status=429,
        )

    session = _resolve_session(payload, site.id, mode, request.user, visitor_id)
    if not getattr(request, "ai_assistant_source_path", ""):
        request.ai_assistant_source_path = (session.source_path or "").strip()
    logger.info(
        "ai_assistant.request",
        extra={
            "assistant_mode": mode,
            "site_id": site.id,
            "session_uuid": str(session.uuid),
            "has_user": bool(getattr(request.user, "is_authenticated", False)),
            "visitor_id": visitor_id if mode == MODE_CUSTOMER else "",
            "client_ip": client_ip,
            "message_length": len(message_text),
        },
    )

    max_context = max(int(settings_obj.max_context_messages or 12), 1)
    context_messages = session.messages.order_by("-created_at")[:max_context]
    context_messages = reversed(list(context_messages))
    capability_context = build_capability_context(
        mode=mode,
        settings_obj=settings_obj,
        request=request,
        business_mode_requested=_business_mode_requested(payload, mode, request),
    )
    reply_text, meta = generate_reply(
        settings_obj,
        mode,
        context_messages,
        message_text,
        request=request,
        capability_context=capability_context,
    )

    AiAssistantMessage.objects.create(
        session=session, role=ROLE_USER, content=message_text
    )
    assistant_msg = AiAssistantMessage.objects.create(
        session=session,
        role=ROLE_ASSISTANT,
        content=reply_text,
        tokens_used=meta.get("tokens"),
        raw=meta,
    )

    data = {
        "session_id": str(session.uuid),
        "message": {
            "id": assistant_msg.id,
            "role": assistant_msg.role,
            "content": assistant_msg.content,
            "created_at": assistant_msg.created_at.isoformat(),
        },
    }
    response = JsonResponse(data)
    response.set_cookie(
        COOKIE_VISITOR_KEY, visitor_id, max_age=VISITOR_COOKIE_MAX_AGE, samesite="Lax"
    )
    return response


def _reset_response(request: HttpRequest, mode: str, require_staff: bool = False):
    site = _current_site(request)
    settings_obj = _site_settings(site)

    if mode == MODE_CUSTOMER and not customer_public_chat_enabled(settings_obj):
        return JsonResponse({"error": "customer_assistant_disabled"}, status=403)
    if mode == MODE_ADMIN and (
        not settings_obj.admin_enabled or (require_staff and not request.user.is_staff)
    ):
        return JsonResponse({"error": "admin_assistant_disabled"}, status=403)

    payload = _load_payload(request)
    if payload is None:
        return JsonResponse({"error": "invalid_json"}, status=400)

    visitor_id = request.COOKIES.get(COOKIE_VISITOR_KEY) or str(uuid.uuid4())
    session = _resolve_existing_session(
        payload, site.id, mode, request.user, visitor_id
    )
    if session and session.status != SESSION_CLOSED:
        session.status = SESSION_CLOSED
        session.save(update_fields=["status", "updated_at"])
        logger.info(
            "ai_assistant.session_closed",
            extra={
                "assistant_mode": mode,
                "site_id": site.id,
                "session_uuid": str(session.uuid),
            },
        )

    response = JsonResponse({"cleared": True})
    response.set_cookie(
        COOKIE_VISITOR_KEY, visitor_id, max_age=VISITOR_COOKIE_MAX_AGE, samesite="Lax"
    )
    return response


@require_GET
def customer_chat_page(request: HttpRequest):
    site = _current_site(request)
    settings_obj = _site_settings(site)
    if not customer_page_enabled(settings_obj):
        raise Http404
    return render(
        request,
        "ai_assistant/customer_chat.html",
        {
            "title": "AI Assistant",
            "assistant_enabled": settings_obj.public_chat_enabled(),
            "assistant_endpoint": "ai_assistant:customer_chat_api",
            "assistant_reset_endpoint": "ai_assistant:customer_chat_reset_api",
            "chat_api_url": reverse("ai_assistant:customer_chat_api"),
            "reset_api_url": reverse("ai_assistant:customer_chat_reset_api"),
            "chat_strings": _customer_chat_strings(),
        },
    )


@require_POST
def customer_chat_api(request: HttpRequest):
    return _chat_response(request, MODE_CUSTOMER, require_staff=False)


@require_POST
def customer_chat_reset_api(request: HttpRequest):
    return _reset_response(request, MODE_CUSTOMER, require_staff=False)


@staff_member_required
@require_GET
def admin_chat_page(request: HttpRequest):
    site = _current_site(request)
    settings_obj = _site_settings(site)
    return render(
        request,
        "wagtailadmin/ai_assistant/dashboard.html",
        {
            "title": "AI Assistant",
            "assistant_endpoint": "ai_assistant_admin:chat_api",
            "assistant_reset_endpoint": "ai_assistant_admin:reset_api",
            "chat_api_url": reverse("ai_assistant_admin:chat_api"),
            "reset_api_url": reverse("ai_assistant_admin:reset_api"),
            "chat_strings": _admin_chat_strings(),
            "business_mode_available": bool(
                settings_obj.business_assistant_enabled
                and (
                    not settings_obj.business_assistant_requires_staff
                    or request.user.is_staff
                )
            ),
            "business_mode_label": settings_obj.business_assistant_mode_label
            or _("Business Assistant"),
        },
    )


@staff_member_required
@require_POST
def admin_chat_api(request: HttpRequest):
    return _chat_response(request, MODE_ADMIN, require_staff=True)


@staff_member_required
@require_POST
def admin_chat_reset_api(request: HttpRequest):
    return _reset_response(request, MODE_ADMIN, require_staff=True)
