from django.contrib.auth import authenticate
from django.utils import timezone
from rest_framework import generics, permissions, status, views
from rest_framework.response import Response
from rest_framework.throttling import ScopedRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
import pyotp

from apps.accounts.models import LoginEvent, LoginSession, OneTimePassword, Role, User
from apps.accounts.serializers import (
    LoginSerializer,
    OTPRequestSerializer,
    OTPVerifySerializer,
    PasswordChangeSerializer,
    PasswordResetConfirmSerializer,
    PasswordResetRequestSerializer,
    SessionSerializer,
    UserSerializer,
)
from apps.accounts.services import issue_otp, verify_otp
from apps.audit.services import client_ip, log_audit, user_agent
from apps.core.permissions import STAFF_ROLES, IsAuthenticatedStaff
from apps.notifications.models import NotificationPreference


def tokens_for(user):
    refresh = RefreshToken.for_user(user)
    refresh["role"] = user.role
    refresh["email"] = user.email
    return {"refresh": str(refresh), "access": str(refresh.access_token)}


class LoginThrottle(ScopedRateThrottle):
    scope = "login"


class OTPThrottle(ScopedRateThrottle):
    scope = "otp"


class MeView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        return Response({"success": True, "data": UserSerializer(request.user).data})


class LoginView(views.APIView):
    permission_classes = [permissions.AllowAny]
    throttle_classes = [LoginThrottle]
    throttle_scope = "login"
    serializer_class = LoginSerializer

    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        email = serializer.validated_data["email"].lower()
        password = serializer.validated_data["password"]
        user = authenticate(request, username=email, password=password)
        ip = client_ip(request)
        ua = user_agent(request)

        if user is None:
            LoginEvent.objects.create(
                email=email,
                status=LoginEvent.Status.FAILED,
                ip_address=ip,
                user_agent=ua,
                note="Invalid credentials",
            )
            return Response(
                {"success": False, "message": "Invalid email or password."},
                status=status.HTTP_401_UNAUTHORIZED,
            )

        if not user.is_active:
            return Response(
                {"success": False, "message": "Account is disabled."},
                status=status.HTTP_403_FORBIDDEN,
            )

        portal = serializer.validated_data.get("portal")
        if portal == "admin" and user.role not in STAFF_ROLES:
            return Response(
                {"success": False, "message": "Staff accounts only. Use the investor portal to sign in."},
                status=status.HTTP_403_FORBIDDEN,
            )

        if user.role == Role.INVESTOR and not user.is_email_verified:
            return Response(
                {
                    "success": False,
                    "message": "Email verification required.",
                    "code": "email_unverified",
                },
                status=status.HTTP_403_FORBIDDEN,
            )

        if user.two_factor_enabled:
            totp_code = serializer.validated_data.get("totp_code") or ""
            otp_code = serializer.validated_data.get("otp_code") or ""
            ok = False
            if totp_code and user.two_factor_secret:
                ok = pyotp.TOTP(user.two_factor_secret).verify(totp_code, valid_window=1)
            if not ok and otp_code:
                ok = verify_otp(user, OneTimePassword.Purpose.TWO_FACTOR, otp_code) is not None
            if not ok:
                LoginEvent.objects.create(
                    user=user,
                    email=email,
                    status=LoginEvent.Status.TWO_FACTOR_REQUIRED,
                    ip_address=ip,
                    user_agent=ua,
                )
                return Response(
                    {
                        "success": False,
                        "message": "Two-factor authentication required.",
                        "code": "2fa_required",
                    },
                    status=status.HTTP_401_UNAUTHORIZED,
                )

        user.last_login = timezone.now()
        user.last_login_ip = ip
        user.last_login_user_agent = ua
        user.save(update_fields=["last_login", "last_login_ip", "last_login_user_agent"])

        LoginSession.objects.create(
            user=user,
            ip_address=ip,
            user_agent=ua,
            device_label=(ua[:80] if ua else "Unknown device"),
            last_seen_at=timezone.now(),
        )
        LoginEvent.objects.create(
            user=user,
            email=email,
            status=LoginEvent.Status.SUCCESS,
            ip_address=ip,
            user_agent=ua,
        )
        log_audit(action="Investor Login" if user.role == Role.INVESTOR else "Staff Login", module="accounts", request=request, user=user)
        data = tokens_for(user)
        data["user"] = UserSerializer(user).data
        return Response({"success": True, "data": data})


class LogoutView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        refresh = request.data.get("refresh")
        if refresh:
            try:
                token = RefreshToken(refresh)
                token.blacklist()
            except Exception:
                pass
        LoginSession.objects.filter(user=request.user, is_active=True).update(
            is_active=False, revoked_at=timezone.now()
        )
        log_audit(action="Logout", module="accounts", request=request)
        return Response({"success": True, "message": "Signed out."})


class OTPRequestView(views.APIView):
    permission_classes = [permissions.AllowAny]
    throttle_classes = [OTPThrottle]
    throttle_scope = "otp"

    def post(self, request):
        serializer = OTPRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        purpose = serializer.validated_data["purpose"]
        email = serializer.validated_data.get("email")
        user = request.user if request.user.is_authenticated else None
        if user is None:
            if not email:
                return Response({"success": False, "message": "Email is required."}, status=400)
            user = User.objects.filter(email__iexact=email).first()
            if user is None:
                return Response({"success": True, "message": "If the account exists, a code was sent."})
        issue_otp(user, purpose)
        return Response({"success": True, "message": "Verification code sent."})


class OTPVerifyView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        serializer = OTPVerifySerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        otp = verify_otp(request.user, serializer.validated_data["purpose"], serializer.validated_data["code"])
        if otp is None:
            return Response({"success": False, "message": "Invalid or expired code."}, status=400)
        if serializer.validated_data["purpose"] == OneTimePassword.Purpose.EMAIL_VERIFY:
            request.user.is_email_verified = True
            request.user.save(update_fields=["is_email_verified"])
        if serializer.validated_data["purpose"] == OneTimePassword.Purpose.PHONE_VERIFY:
            request.user.is_phone_verified = True
            request.user.save(update_fields=["is_phone_verified"])
        return Response({"success": True, "message": "Verified."})


class TwoFactorSetupView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        secret = request.user.two_factor_secret or pyotp.random_base32()
        if not request.user.two_factor_secret:
            request.user.two_factor_secret = secret
            request.user.save(update_fields=["two_factor_secret"])
        uri = pyotp.TOTP(secret).provisioning_uri(name=request.user.email, issuer_name="Shopno Holdings")
        return Response({"success": True, "data": {"secret": secret, "otpauth_uri": uri}})

    def post(self, request):
        code = request.data.get("totp_code", "")
        if not request.user.two_factor_secret or not pyotp.TOTP(request.user.two_factor_secret).verify(code, valid_window=1):
            return Response({"success": False, "message": "Invalid authenticator code."}, status=400)
        request.user.two_factor_enabled = True
        request.user.save(update_fields=["two_factor_enabled"])
        log_audit(action="Security Setting Changed", module="accounts", request=request, new_value={"two_factor_enabled": True})
        return Response({"success": True, "message": "Two-factor authentication enabled."})

    def delete(self, request):
        request.user.two_factor_enabled = False
        request.user.save(update_fields=["two_factor_enabled"])
        log_audit(action="Security Setting Changed", module="accounts", request=request, new_value={"two_factor_enabled": False})
        return Response({"success": True, "message": "Two-factor authentication disabled."})


class PasswordChangeView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        serializer = PasswordChangeSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        if not request.user.check_password(serializer.validated_data["current_password"]):
            return Response({"success": False, "message": "Current password is incorrect."}, status=400)
        request.user.set_password(serializer.validated_data["new_password"])
        request.user.password_changed_at = timezone.now()
        request.user.save(update_fields=["password", "password_changed_at"])
        log_audit(action="Security Setting Changed", module="accounts", request=request, new_value={"password": "updated"})
        return Response({"success": True, "message": "Password updated."})


class PasswordResetRequestView(views.APIView):
    permission_classes = [permissions.AllowAny]
    throttle_classes = [OTPThrottle]
    throttle_scope = "otp"

    def post(self, request):
        serializer = PasswordResetRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = User.objects.filter(email__iexact=serializer.validated_data["email"]).first()
        if user:
            issue_otp(user, OneTimePassword.Purpose.PASSWORD_RESET, ttl_minutes=20)
        return Response({"success": True, "message": "If the account exists, a reset code was sent."})


class PasswordResetConfirmView(views.APIView):
    permission_classes = [permissions.AllowAny]

    def post(self, request):
        serializer = PasswordResetConfirmSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = User.objects.filter(email__iexact=serializer.validated_data["email"]).first()
        if user is None:
            return Response({"success": False, "message": "Invalid reset request."}, status=400)
        otp = verify_otp(user, OneTimePassword.Purpose.PASSWORD_RESET, serializer.validated_data["code"])
        if otp is None:
            return Response({"success": False, "message": "Invalid or expired code."}, status=400)
        user.set_password(serializer.validated_data["new_password"])
        user.password_changed_at = timezone.now()
        user.save(update_fields=["password", "password_changed_at"])
        log_audit(action="Password Reset", module="accounts", user=user)
        return Response({"success": True, "message": "Password has been reset."})


class SessionListView(generics.ListAPIView):
    serializer_class = SessionSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return LoginSession.objects.filter(user=self.request.user)


class SessionRevokeView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def delete(self, request, pk):
        session = LoginSession.objects.filter(user=request.user, pk=pk).first()
        if session is None:
            return Response({"success": False, "message": "Session not found."}, status=404)
        session.is_active = False
        session.revoked_at = timezone.now()
        session.save(update_fields=["is_active", "revoked_at"])
        return Response({"success": True, "message": "Session revoked."})


class StaffUserListView(generics.ListAPIView):
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticatedStaff]
    queryset = User.objects.all()
    filterset_fields = ["role", "is_active"]
    search_fields = ["email", "first_name", "last_name"]
