import logging

from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from core.responses import success_response, error_response
from core.permissions import IsAdmin
from django.utils import timezone
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
from apps.audit_logs.services import log_auth_event
from apps.audit_logs.models import AuthEventLog
from apps.users.models import PasswordHistory

from . import mcp_client
from .services import send_email_otp, verify_email_otp, check_email_otp

User = get_user_model()
logger = logging.getLogger(__name__)

def validate_password_strength(new_password, current_password_hash, password_histories):
    """
    Validates the password complexity rules, checks for reuse,
    and returns a tuple (is_valid, error_message).
    """
    if not (8 <= len(new_password) <= 64):
        return False, "Password must be between 8 and 64 characters long."

    if not any(c.isupper() for c in new_password):
        return False, "Password must contain at least one uppercase letter."

    if not any(c.islower() for c in new_password):
        return False, "Password must contain at least one lowercase letter."

    if not any(c.isdigit() for c in new_password):
        return False, "Password must contain at least one number."

    special_characters = "!@#$%^&*(),.?\":{}|<>"
    if not any(c in special_characters for c in new_password):
        return False, "Password must contain at least one special character."

    # Block common weak passwords
    common_passwords = {"password", "12345678", "qwertyuiop", "welcome123", "admin123", "password123", "changeme"}
    if new_password.lower() in common_passwords:
        return False, "Password is too common or weak."

    # Check same as current password
    if check_password(new_password, current_password_hash):
        return False, "New password cannot be the same as your current password."

    # Check reuse of last 5 passwords
    for h in password_histories[:5]:
        if check_password(new_password, h.password_hash):
            return False, "You cannot reuse any of your last 5 passwords."

    return True, ""


class PasswordPolicyView(APIView):
    """GET  /api/v1/auth/password/policy/ — read the global policy (any auth user).
    PATCH /api/v1/auth/password/policy/ — update expiry_days (ADMIN only)."""

    def get_permissions(self):
        if self.request.method == "GET":
            return [IsAuthenticated()]
        return [IsAdmin()]

    def get(self, request):
        from .models import PasswordPolicy

        policy = PasswordPolicy.load()
        return success_response({"expiry_days": policy.expiry_days})

    def patch(self, request):
        from .models import PasswordPolicy

        expiry_days = request.data.get("expiry_days")
        if expiry_days is None:
            return error_response("expiry_days is required.", status_code=400)
        try:
            expiry_days = int(expiry_days)
        except (TypeError, ValueError):
            return error_response("expiry_days must be an integer.", status_code=400)
        if not (1 <= expiry_days <= 365):
            return error_response("expiry_days must be between 1 and 365.", status_code=400)

        policy = PasswordPolicy.load()
        policy.expiry_days = expiry_days
        policy.save()
        return success_response({"expiry_days": policy.expiry_days}, "Password policy updated.")


class PasswordPolicyStatsView(APIView):
    """GET /api/v1/auth/password/policy/stats/ — expiry stats + at-risk users (ADMIN only)."""

    permission_classes = [IsAdmin]

    def get(self, request):
        now = timezone.now()
        soon = now + timedelta(days=7)

        # Administrator accounts are exempt from password expiry policy.
        qs = User.objects.filter(is_active=True).exclude(role=User.Role.ADMIN)
        with_policy = qs.filter(password_expiry_date__isnull=False)

        total_with_policy = with_policy.count()
        expired_count = with_policy.filter(password_expired=True).count()
        expiring_soon_count = with_policy.filter(
            password_expired=False,
            password_expiry_date__lte=soon,
            password_expiry_date__gt=now,
        ).count()
        active_count = total_with_policy - expired_count - expiring_soon_count

        at_risk = with_policy.filter(
            password_expiry_date__lte=soon
        ).order_by("password_expiry_date").values(
            "id", "email", "full_name", "role",
            "password_expiry_date", "password_expired", "password_last_changed_at"
        )

        users = []
        for u in at_risk:
            expiry = u["password_expiry_date"]
            days_left = max(0, (expiry.date() - now.date()).days) if expiry else None
            users.append({
                "id": u["id"],
                "email": u["email"],
                "full_name": u["full_name"] or "",
                "role": u["role"],
                "password_expiry_date": expiry.strftime("%d/%m/%Y") if expiry else None,
                "password_expired": u["password_expired"],
                "days_remaining": days_left,
            })

        return success_response({
            "total_with_policy": total_with_policy,
            "expired": expired_count,
            "expiring_soon": expiring_soon_count,
            "active": active_count,
            "at_risk_users": users,
        })


class CheckPasswordExpiryView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user

        if user.is_admin:
            user.check_password_expiry()
            return success_response({
                "expired": False,
                "last_changed_at": user.password_last_changed_at,
                "expiry_date": None,
                "days_remaining": None,
            })

        # If the expiry date hasn't been set, or is in the past, update the status
        if user.password_expiry_date and timezone.now() >= user.password_expiry_date:
            if not user.password_expired:
                user.password_expired = True
                user.save(update_fields=["password_expired"])

        days_remaining = None
        if user.password_expiry_date:
            days_remaining = max(0, (user.password_expiry_date.date() - timezone.now().date()).days)

        return success_response({
            "expired": user.password_expired,
            "last_changed_at": user.password_last_changed_at,
            "expiry_date": user.password_expiry_date,
            "days_remaining": days_remaining
        })


class PasswordReminderStatusView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        if user.is_admin:
            user.check_password_expiry()
            return success_response({
                "show_reminder": False,
                "days_remaining": 0,
                "message": ""
            })

        user.check_password_expiry()
        if not user.password_expiry_date or user.password_expired:
            return success_response({
                "show_reminder": False,
                "days_remaining": 0,
                "message": ""
            })

        days_remaining = max(0, (user.password_expiry_date.date() - timezone.now().date()).days)
        reminder_thresholds = [15, 7, 3, 1]

        show_reminder = days_remaining in reminder_thresholds

        message = ""
        if show_reminder:
            message = (
                f"Your password will expire in {days_remaining} day{'s' if days_remaining != 1 else ''}. "
                "Please update your password to continue using the ATS securely."
            )

        return success_response({
            "show_reminder": show_reminder,
            "days_remaining": days_remaining,
            "message": message
        })


class PasswordHistoryValidationView(APIView):
     
    
    permission_classes = [IsAuthenticated]

    def post(self, request):
        password = request.data.get("password")
        if not password:
            return error_response("Password is required.", status_code=400)

        user = request.user
        histories = user.password_histories.all()
        "# Validate password complexity and prevent password reuse"
        is_valid, msg = validate_password_strength(password, user.password, histories)

        return success_response({
            "valid": is_valid,
            "message": msg if not is_valid else "Password is valid and complies with policy."
        })


class ForgotPasswordView(APIView):
    """Step 1 of password reset: email a 6-digit OTP to the account (if it exists)."""
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        email = (request.data.get("email") or "").strip().lower()
        if not email:
            return error_response("Email is required.", status_code=400)

        user = User.objects.filter(email__iexact=email).first()
        if not user:
            return error_response("This email is not registered.", status_code=404)
        if not user.is_active:
            return error_response(
                "Your account is inactive. Please contact the administrator to reactivate your account.",
                status_code=403,
            )

        try:
            res = send_email_otp(user)
            msg = res.get("message") if isinstance(res, dict) else "A reset code has been sent to your email."
            return success_response(message=msg)
        except Exception as e:
            return error_response(
                f"Could not send reset code: {e}",
                status_code=503,
            )


class VerifyResetOTPView(APIView):
    """Step 2 of password reset: confirm the emailed OTP is valid (does not consume it)."""
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        email = (request.data.get("email") or "").strip().lower()
        otp = (request.data.get("otp") or "").strip()
        telegram_chat_id = (request.data.get("telegram_chat_id") or "").strip() or None
        whatsapp_number = (request.data.get("whatsapp_number") or "").strip() or None
        if not email or not otp:
            return error_response("Email and code are required.", status_code=400)

        user = User.objects.filter(email__iexact=email).first()
        if not user or not user.is_active or not check_email_otp(user, otp):
            return error_response("Invalid or expired code.", status_code=400)

        candidate_id = None
        if telegram_chat_id or whatsapp_number:
            # Never let a linking failure break the OTP-verify response itself —
            # the code IS valid at this point regardless of what happens below.
            try:
                from apps.candidates.models import Candidate
                candidate = Candidate.get_for_user(user) or Candidate.objects.filter(email__iexact=email).first()
                if candidate and telegram_chat_id and not Candidate.objects.filter(telegram_chat_id=telegram_chat_id).exclude(id=candidate.id).exists():
                    candidate.telegram_chat_id = telegram_chat_id
                    candidate.save(update_fields=["telegram_chat_id"])
                if candidate and whatsapp_number and not Candidate.objects.filter(whatsapp_number=whatsapp_number).exclude(id=candidate.id).exists():
                    candidate.whatsapp_number = whatsapp_number
                    candidate.save(update_fields=["whatsapp_number"])
                if candidate:
                    candidate_id = candidate.id
            except Exception:
                logger.exception("Could not link chat/number for %s during OTP verify", email)

        return success_response({"candidate_id": candidate_id}, "Code verified.")


class ResetPasswordView(APIView):
    """Step 3 of password reset: verify the emailed OTP and set a new password."""
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        email = (request.data.get("email") or "").strip().lower()
        otp = (request.data.get("otp") or "").strip()
        new_password = request.data.get("new_password")
        confirm_password = request.data.get("confirm_password")

        if not all([email, otp, new_password, confirm_password]):
            return error_response("All fields are required.", status_code=400)

        if new_password != confirm_password:
            return error_response(
                "New password and confirm password do not match.", status_code=400
            )

        user = User.objects.filter(email__iexact=email).first()
        if not user or not user.is_active:
            return error_response("Invalid or expired reset code.", status_code=400)

        # 1. Verify the emailed OTP (consumes it on success).
        if not verify_email_otp(user, otp):
            return error_response("Invalid or expired reset code.", status_code=400)

        # 2. Enforce password policy + history.
        histories = user.password_histories.all()
        is_valid, msg = validate_password_strength(new_password, user.password, histories)
        if not is_valid:
            return error_response(msg, status_code=400)

        # 3. Save new password (User.save() updates expiry metadata + history).
        user.set_password(new_password)
        user.save()

        # 4. Push the new hash to auth_mcp so credential login keeps working.
        try:
            mcp_client.sync_user(email, user.password, getattr(user, "role", "ADMIN"))
        except Exception:
            pass  # local password is the source of truth; login self-heals the MCP hash

        # 5. Invalidate all existing refresh tokens.
        from rest_framework_simplejwt.token_blacklist.models import OutstandingToken, BlacklistedToken
        for token in OutstandingToken.objects.filter(user=user):
            BlacklistedToken.objects.get_or_create(token=token)

        # 6. Audit.
        log_auth_event(user, AuthEventLog.EventType.PASSWORD_CHANGE, request)

        return success_response(message="Password has been reset. You can now sign in.")


class ChangePasswordView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        current_password = request.data.get("current_password")
        new_password = request.data.get("new_password")
        confirm_password = request.data.get("confirm_password")

        if not current_password or not new_password or not confirm_password:
            return error_response("All password fields are required.", status_code=400)

        user = request.user

        # 1. Validate current password
        if not user.check_password(current_password):
            return error_response("Incorrect current password.", status_code=400)

        # 2. Validate password match
        if new_password != confirm_password:
            return error_response("New password and confirm password do not match.", status_code=400)

        # 3. Validate password strength and history
        histories = user.password_histories.all()
        is_valid, msg = validate_password_strength(new_password, user.password, histories)
        if not is_valid:
            return error_response(msg, status_code=400)

        # 4. Save new password and update expiry metadata
        user.set_password(new_password)
        # Note: self.password changes will be detected in save(), which will update:
        # password_last_changed_at, password_expiry_date, password_expired and create PasswordHistory record
        user.save()

        # 5. Invalidate all existing refresh tokens
        from rest_framework_simplejwt.token_blacklist.models import OutstandingToken, BlacklistedToken
        outstanding = OutstandingToken.objects.filter(user=user)
        for token in outstanding:
            BlacklistedToken.objects.get_or_create(token=token)

        # 6. Log audit event
        log_auth_event(user, AuthEventLog.EventType.PASSWORD_CHANGE, request)

        return success_response({
            "next_expiry_date": user.password_expiry_date.strftime("%d/%m/%Y")
        }, "Password updated successfully.")
