import os
import random
import secrets
import string
from datetime import timedelta

from django.contrib.auth import authenticate
from django.http import JsonResponse
from django.utils import timezone
from django.views.decorators.http import require_GET
from django.views.decorators.csrf import csrf_exempt
from passlib.context import CryptContext
from rest_framework.decorators import api_view
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response

from app.services.email_service import send_otp_email, send_reset_password_email
from integrations.client_context import resolve_client_from_host, resolve_forced_workspace_client
from integrations.models import Client, ClientUserMembership
from .models import DashboardSession, Role, User, UserPermission

SESSION_COOKIE_NAME = "session_token"
SESSION_DAYS_DEFAULT = 7
SESSION_DAYS_REMEMBER = 30
OTP_EXPIRE_MINUTES = 10
pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")


def _session_days(remember_me: bool) -> int:
    return int(os.getenv("SESSION_REMEMBER_DAYS", str(SESSION_DAYS_REMEMBER))) if remember_me else int(
        os.getenv("SESSION_MAX_DAYS", str(SESSION_DAYS_DEFAULT))
    )


def _cookie_max_age(remember_me: bool) -> int:
    return _session_days(remember_me) * 24 * 60 * 60


def _secure_cookie() -> bool:
    return os.getenv("SECURE_COOKIES", "false").lower() in ("1", "true", "yes")


def _session_user(request):
    token = request.COOKIES.get(SESSION_COOKIE_NAME)
    if not token:
        return None, None
    now = timezone.now()
    session = DashboardSession.objects.filter(token=token).select_related("user").first()
    if not session or session.expires_at < now:
        if session:
            session.delete()
        return None, None
    # Enforce strict tenant membership on every authenticated request.
    client = _resolve_request_client(request)
    if not client:
        return None, None
    has_membership = ClientUserMembership.objects.filter(
        client=client,
        user=session.user,
        is_active=True,
    ).exists()
    if not has_membership:
        return None, None
    return session, session.user


def _otp_session_user(request):
    session, user = _session_user(request)
    if not session or not user:
        return None, None
    return session, user


def _user_permissions(user):
    direct = list(
        UserPermission.objects.filter(user=user).select_related("permission").values_list("permission__key", flat=True)
    )
    if direct:
        return direct
    role = user.role_obj or Role.objects.filter(slug=(user.role or "user").lower()).first()
    if not role:
        return []
    return list(role.permissions.values_list("key", flat=True))


def _role_payload(user):
    role = user.role_obj or Role.objects.filter(slug=(user.role or "user").lower()).first()
    effective_role = role.slug if role and role.slug else (user.role or "user")
    return role, effective_role


def _app_url():
    app_url = (os.getenv("APP_URL") or os.getenv("FRONTEND_URL") or "").strip()
    if not app_url:
        app_url = "https://nixonstats.info" if (
            os.path.exists("/.dockerenv")
            or os.getenv("RUNNING_IN_DOCKER", "").lower() in ("1", "true", "yes")
        ) else "http://localhost:3000"
    return app_url.rstrip("/")


def _generate_otp_code() -> str:
    return "".join(random.choices(string.digits, k=6))


def _resolve_request_client(request) -> Client:
    client = resolve_client_from_host(request.get_host())
    if client:
        return client
    workspace_subdomain = (request.headers.get("X-Workspace-Subdomain") or "").strip().lower()
    if workspace_subdomain and all(c.isalnum() or c == "-" for c in workspace_subdomain):
        return Client.objects.filter(subdomain=workspace_subdomain, is_active=True).first()
    forced = resolve_forced_workspace_client()
    if forced:
        return forced
    # Local dev fallback: if running on localhost without subdomain/header and only one active
    # client exists, use it so authenticated admin routes continue to work.
    host = (request.get_host() or "").split(":")[0].lower()
    if host in ("localhost", "127.0.0.1"):
        active_clients = Client.objects.filter(is_active=True).order_by("id")
        if active_clients.count() == 1:
            return active_clients.first()
    return None


def _login_client_for_request(request, workspace_subdomain: str) -> Client:
    if workspace_subdomain:
        return Client.objects.filter(subdomain=workspace_subdomain, is_active=True).first()
    return _resolve_request_client(request)


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def login(request):
    username = request.data.get("username", "")
    password = request.data.get("password", "")
    remember_me = bool(request.data.get("remember_me", False))
    workspace_subdomain = (request.data.get("workspace_subdomain") or "").strip().lower()
    user = authenticate(request._request, username=username, password=password)
    if not user:
        return Response({"detail": "Invalid username or password"}, status=401)
    if user.invite_token:
        return Response({"detail": "Please set your password from your invite link first."}, status=403)
    client = _login_client_for_request(request, workspace_subdomain)
    if not client:
        return Response({"detail": "Unknown workspace. Provide a valid subdomain."}, status=400)
    has_membership = ClientUserMembership.objects.filter(client=client, user=user, is_active=True).exists()
    if not has_membership:
        return Response({"detail": "You do not have access to this workspace."}, status=403)

    now = timezone.now()
    session = DashboardSession.objects.create(
        user=user,
        token=secrets.token_urlsafe(32),
        expires_at=now + timedelta(days=_session_days(remember_me)),
        remember_me=remember_me,
        otp_verified_at=now,
    )
    role, effective_role = _role_payload(user)
    response_payload = {
        "user": {
            "id": user.id,
            "username": user.username,
            "role": effective_role,
            "role_id": getattr(user, "role_obj_id", None),
            "role_name": role.name if role else effective_role.capitalize(),
            "permissions": _user_permissions(user),
        }
    }
    if workspace_subdomain:
        # Frontend root-URL login can hand off session to subdomain with one-time token transfer.
        response_payload["transfer_token"] = session.token
        response_payload["workspace_subdomain"] = workspace_subdomain
    response = JsonResponse(
        response_payload
    )
    response.set_cookie(
        SESSION_COOKIE_NAME,
        session.token,
        max_age=_cookie_max_age(remember_me),
        httponly=True,
        samesite="Strict",
        secure=_secure_cookie(),
        path="/",
    )
    return response


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def consume_transfer_token(request):
    """
    Exchange transfer_token for a cookie on the target subdomain.
    Used when login starts at root host and should complete on workspace subdomain.
    """
    token = (request.data.get("transfer_token") or "").strip()
    if not token:
        return Response({"detail": "Missing transfer token"}, status=400)
    now = timezone.now()
    session = DashboardSession.objects.filter(token=token).select_related("user").first()
    if not session or session.expires_at < now:
        return Response({"detail": "Transfer token expired or invalid"}, status=401)
    workspace_subdomain = (request.data.get("workspace_subdomain") or "").strip().lower()
    client = _resolve_request_client(request)
    if not client and workspace_subdomain:
        client = Client.objects.filter(subdomain=workspace_subdomain, is_active=True).first()
    if not client:
        return Response({"detail": "Transfer must be completed on a workspace subdomain."}, status=400)
    has_membership = ClientUserMembership.objects.filter(
        client=client,
        user=session.user,
        is_active=True,
    ).exists()
    if not has_membership:
        return Response({"detail": "You do not have access to this workspace."}, status=403)
    user = session.user
    role, effective_role = _role_payload(user)
    response = JsonResponse(
        {
            "user": {
                "id": user.id,
                "username": user.username,
                "role": effective_role,
                "role_id": getattr(user, "role_obj_id", None),
                "role_name": role.name if role else effective_role.capitalize(),
                "permissions": _user_permissions(user),
            }
        }
    )
    response.set_cookie(
        SESSION_COOKIE_NAME,
        session.token,
        max_age=_cookie_max_age(bool(session.remember_me)),
        httponly=True,
        samesite="Strict",
        secure=_secure_cookie(),
        path="/",
    )
    return response


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def logout(request):
    token = request.COOKIES.get(SESSION_COOKIE_NAME)
    if token:
        DashboardSession.objects.filter(token=token).delete()
    response = JsonResponse({"ok": True})
    response.delete_cookie(SESSION_COOKIE_NAME, path="/")
    return response


@api_view(["GET"])
def me(request):
    _, user = _session_user(request)
    if not user:
        return Response({"detail": "Not authenticated"}, status=401)
    role, effective_role = _role_payload(user)
    return Response(
        {
            "user": {
                "id": user.id,
                "username": user.username,
                "role": effective_role,
                "role_id": getattr(user, "role_obj_id", None),
                "role_name": role.name if role else effective_role.capitalize(),
                "permissions": _user_permissions(user),
            }
        }
    )


@api_view(["GET"])
def invite_info(request):
    token = request.query_params.get("token", "")
    user = User.objects.filter(invite_token=token).select_related("role_obj").first()
    if not user or not user.invite_expires_at or user.invite_expires_at < timezone.now():
        return Response({"valid": False, "email": None, "role_name": None})
    role = user.role_obj or Role.objects.filter(slug=(user.role or "user").lower()).first()
    return Response(
        {
            "valid": True,
            "email": user.username,
            "role_name": role.name if role else (user.role or "User"),
        }
    )


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def set_password(request):
    token = request.data.get("token", "")
    password = request.data.get("password", "")
    user = User.objects.filter(invite_token=token).first()
    if not user or not user.invite_expires_at or user.invite_expires_at < timezone.now():
        return Response({"detail": "Invalid or expired invite link"}, status=400)
    if len(password) < 8:
        return Response({"detail": "Password must be at least 8 characters"}, status=400)
    user.password = pwd_ctx.hash(password)
    user.invite_token = None
    user.invite_expires_at = None
    user.save(update_fields=["password", "invite_token", "invite_expires_at"])
    return Response({"ok": True, "message": "Password set. You can now log in."})


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def request_password_reset(request):
    email = (request.data.get("email", "") or "").strip().lower()
    if not email or "@" not in email:
        return Response({"detail": "Valid email required"}, status=400)
    user = User.objects.filter(username=email).first()
    if user and not user.invite_token:
        reset_token = secrets.token_urlsafe(32)
        user.reset_token = reset_token
        user.reset_expires_at = timezone.now() + timedelta(hours=1)
        user.save(update_fields=["reset_token", "reset_expires_at"])
        send_reset_password_email(email, f"{_app_url()}/reset-password?token={reset_token}")
    return Response({"ok": True, "message": "If an account exists with that email, a reset link has been sent."})


@api_view(["GET"])
def reset_password_info(request):
    token = request.query_params.get("token", "")
    user = User.objects.filter(reset_token=token).first()
    if not user or not user.reset_expires_at or user.reset_expires_at < timezone.now():
        return Response({"valid": False, "email": None})
    return Response({"valid": True, "email": user.username})


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def reset_password(request):
    token = request.data.get("token", "")
    password = request.data.get("password", "")
    user = User.objects.filter(reset_token=token).first()
    if not user or not user.reset_expires_at or user.reset_expires_at < timezone.now():
        return Response({"detail": "Invalid or expired reset link"}, status=400)
    if len(password) < 8:
        return Response({"detail": "Password must be at least 8 characters"}, status=400)
    user.password = pwd_ctx.hash(password)
    user.reset_token = None
    user.reset_expires_at = None
    user.save(update_fields=["password", "reset_token", "reset_expires_at"])
    return Response({"ok": True, "message": "Password updated. You can now log in."})


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def request_otp(request):
    session, user = _otp_session_user(request)
    if not session or not user:
        return Response({"detail": "Session expired or invalid"}, status=401)
    code = _generate_otp_code()
    session.otp_code_hash = pwd_ctx.hash(code)
    session.otp_expires_at = timezone.now() + timedelta(minutes=OTP_EXPIRE_MINUTES)
    session.save(update_fields=["otp_code_hash", "otp_expires_at"])
    sent = send_otp_email(user.username, code)
    return Response(
        {
            "ok": True,
            "sent": sent,
            "message": "Verification code sent to your email." if sent else "Code generated but email not sent (check SendGrid).",
        }
    )


@csrf_exempt
@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def verify_otp(request):
    code = request.data.get("code", "")
    session, user = _otp_session_user(request)
    if not session or not user:
        return Response({"detail": "Session expired or invalid"}, status=401)
    if not session.otp_code_hash or not session.otp_expires_at or session.otp_expires_at < timezone.now():
        return Response({"detail": "Code expired. Request a new one."}, status=400)
    if not pwd_ctx.verify(code, session.otp_code_hash):
        return Response({"detail": "Invalid code"}, status=401)
    session.otp_verified_at = timezone.now()
    session.otp_code_hash = None
    session.otp_expires_at = None
    session.save(update_fields=["otp_verified_at", "otp_code_hash", "otp_expires_at"])
    role, effective_role = _role_payload(user)
    return Response(
        {
            "user": {
                "id": user.id,
                "username": user.username,
                "role": effective_role,
                "role_id": getattr(user, "role_obj_id", None),
                "role_name": role.name if role else effective_role.capitalize(),
                "permissions": _user_permissions(user),
            }
        }
    )


@require_GET
def health(request):
    return JsonResponse({"status": "healthy", "backend": "django"})


@require_GET
def site_status(request):
    upgrade_mode = os.getenv("SITE_UPGRADE_MODE", "").lower() in ("1", "true", "yes")
    return JsonResponse({"upgrade_mode": upgrade_mode})
