"""
Auth domain serializers for ThrillWiki API v1.

This module contains all serializers related to authentication, user accounts,
profiles, top lists, and user statistics.
"""

from datetime import timedelta
from typing import Any

from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from django.utils import timezone
from django.utils.crypto import get_random_string
from drf_spectacular.utils import (
    OpenApiExample,
    extend_schema_field,
    extend_schema_serializer,
)
from rest_framework import serializers

from apps.accounts.models import PasswordReset
from apps.core.utils import capture_and_log

UserModel = get_user_model()


def _normalize_email(value: str) -> str:
    """Normalize email for consistent lookups (strip + lowercase)."""
    if value is None:
        return value
    return value.strip().lower()


# Import shared utilities


class ModelChoices:
    """Model choices utility class."""


# === AUTHENTICATION SERIALIZERS ===


@extend_schema_serializer(
    examples=[
        OpenApiExample(
            "User Example",
            summary="Example user response",
            description="A typical user object",
            value={
                "id": 1,
                "username": "john_doe",
                "email": "john@example.com",
                "display_name": "John Doe",
                "date_joined": "2024-01-01T12:00:00Z",
                "is_active": True,
                "avatar_url": "https://example.com/avatars/john.jpg",
            },
        )
    ]
)
class UserOutputSerializer(serializers.ModelSerializer):
    """User serializer for API responses."""

    avatar_url = serializers.SerializerMethodField()
    display_name = serializers.SerializerMethodField()
    role = serializers.SerializerMethodField()

    class Meta:
        model = UserModel
        fields = [
            "id",
            "username",
            "email",
            "display_name",
            "date_joined",
            "is_active",
            "is_staff",
            "is_superuser",
            "role",
            "avatar_url",
        ]
        read_only_fields = ["id", "date_joined", "is_active", "is_staff", "is_superuser", "role"]

    def get_display_name(self, obj):
        """Get the user's display name."""
        return obj.get_display_name()

    @extend_schema_field(serializers.URLField(allow_null=True))
    def get_avatar_url(self, obj) -> str | None:
        """Get user avatar URL."""
        if hasattr(obj, "profile") and obj.profile:
            return obj.profile.get_avatar_url()
        return None

    @extend_schema_field(serializers.CharField())
    def get_role(self, obj) -> str:
        """Compute effective role based on permissions."""
        if obj.is_superuser:
            return "SUPERUSER"
        if obj.is_staff:
            return "ADMIN"
        return "USER"


class LoginInputSerializer(serializers.Serializer):
    """Input serializer for user login.
    
    Accepts either 'email' or 'username' field for backward compatibility.
    The view will use whichever is provided.
    """

    # Accept both email and username - frontend sends "email", but we also support "username"
    email = serializers.CharField(max_length=254, required=False, help_text="Email address")
    username = serializers.CharField(max_length=254, required=False, help_text="Username (alternative to email)")
    password = serializers.CharField(max_length=128, style={"input_type": "password"}, trim_whitespace=False)

    def validate(self, attrs):
        email = attrs.get("email")
        username = attrs.get("username")
        password = attrs.get("password")

        # Use email if provided, fallback to username
        identifier = email or username
        
        if not identifier:
            raise serializers.ValidationError("Either email or username is required.")
        
        if not password:
            raise serializers.ValidationError("Password is required.")
        
        # Store the identifier in a standard field for the view to consume
        attrs["username"] = identifier
        return attrs




class LoginOutputSerializer(serializers.Serializer):
    """Output serializer for successful login."""

    access = serializers.CharField()
    refresh = serializers.CharField()
    user = UserOutputSerializer()
    message = serializers.CharField()


class MFARequiredOutputSerializer(serializers.Serializer):
    """Output serializer when MFA verification is required after password auth."""

    mfa_required = serializers.BooleanField(default=True)
    mfa_token = serializers.CharField(help_text="Temporary token for MFA verification")
    mfa_types = serializers.ListField(
        child=serializers.CharField(),
        help_text="Available MFA types: 'totp', 'webauthn'",
    )
    user_id = serializers.IntegerField(help_text="User ID for reference")
    message = serializers.CharField(default="MFA verification required")


class MFALoginVerifyInputSerializer(serializers.Serializer):
    """Input serializer for MFA login verification."""

    mfa_token = serializers.CharField(help_text="Temporary MFA token from login response")
    code = serializers.CharField(
        max_length=6,
        min_length=6,
        required=False,
        help_text="6-digit TOTP code from authenticator app",
    )
    # For passkey/webauthn - credential will be a complex object
    credential = serializers.JSONField(required=False, help_text="WebAuthn credential response")

    def validate(self, attrs):
        code = attrs.get("code")
        credential = attrs.get("credential")

        if not code and not credential:
            raise serializers.ValidationError(
                "Either 'code' (TOTP) or 'credential' (passkey) is required."
            )

        return attrs


class MFALoginVerifyOutputSerializer(serializers.Serializer):
    """Output serializer for successful MFA verification."""

    access = serializers.CharField()
    refresh = serializers.CharField()
    user = UserOutputSerializer()
    message = serializers.CharField(default="Login successful")


class SignupInputSerializer(serializers.ModelSerializer):
    """Input serializer for user registration."""

    password = serializers.CharField(
        write_only=True,
        validators=[validate_password],
        style={"input_type": "password"},
    )
    password_confirm = serializers.CharField(write_only=True, style={"input_type": "password"})

    class Meta:
        model = UserModel
        fields = [
            "username",
            "email",
            "display_name",
            "password",
            "password_confirm",
        ]
        extra_kwargs = {
            "password": {"write_only": True},
            "email": {"required": True},
            "display_name": {"required": True},
        }

    def validate_email(self, value):
        """Validate email is unique (case-insensitive) and return normalized email."""
        normalized = _normalize_email(value)
        if UserModel.objects.filter(email__iexact=normalized).exists():
            raise serializers.ValidationError("A user with this email already exists.")
        return normalized

    def validate_username(self, value):
        """Validate username is unique."""
        if UserModel.objects.filter(username=value).exists():
            raise serializers.ValidationError("A user with this username already exists.")
        return value

    def validate(self, attrs):
        """Validate passwords match."""
        password = attrs.get("password")
        password_confirm = attrs.get("password_confirm")

        if password != password_confirm:
            raise serializers.ValidationError({"password_confirm": "Passwords do not match."})

        return attrs

    def create(self, validated_data):
        """Create user with validated data and send verification email."""
        validated_data.pop("password_confirm", None)
        password = validated_data.pop("password")

        # Create inactive user - they need to verify email first
        user = UserModel.objects.create_user(  # type: ignore[attr-defined]
            password=password, is_active=False, **validated_data
        )

        # Create email verification record and send email
        self._send_verification_email(user)

        return user

    def _send_verification_email(self, user):
        """Send email verification to the user."""
        import logging

        from django.contrib.sites.shortcuts import get_current_site
        from django.utils.crypto import get_random_string
        from django_forwardemail.services import EmailService

        from apps.accounts.models import EmailVerification

        logger = logging.getLogger(__name__)

        # Create or update email verification record
        verification, created = EmailVerification.objects.get_or_create(
            user=user, defaults={"token": get_random_string(64)}
        )

        if not created:
            # Update existing token and timestamp
            verification.token = get_random_string(64)
            verification.save()

        # Get current site from request context
        request = self.context.get("request")
        if request:
            site = get_current_site(request._request)

            # Build verification URL
            verification_url = request.build_absolute_uri(f"/api/v1/auth/verify-email/{verification.token}/")

            # Send verification email
            try:
                response = EmailService.send_email(
                    to=user.email,
                    subject="Verify your ThrillWiki account",
                    text=f"""
Welcome to ThrillWiki!

Please verify your email address by clicking the link below:
{verification_url}

If you didn't create an account, you can safely ignore this email.

Thanks,
The ThrillWiki Team
                    """.strip(),
                    site=site,
                )

                # Log the ForwardEmail email ID from the response
                email_id = response.get("id") if response else None
                if email_id:
                    logger.info(f"Verification email sent successfully to {user.email}. ForwardEmail ID: {email_id}")
                else:
                    logger.info(f"Verification email sent successfully to {user.email}. No email ID in response.")

            except Exception as e:
                # Capture error but don't fail registration
                capture_and_log(e, f'Send verification email to {user.email}', source='api', severity='low')


class SignupOutputSerializer(serializers.Serializer):
    """Output serializer for successful signup."""

    access = serializers.CharField(allow_null=True)
    refresh = serializers.CharField(allow_null=True)
    user = UserOutputSerializer()
    message = serializers.CharField()
    email_verification_required = serializers.BooleanField(default=False)


class PasswordResetInputSerializer(serializers.Serializer):
    """Input serializer for password reset request."""

    email = serializers.EmailField()

    def validate_email(self, value):
        """Normalize email and attach user to the serializer when found (case-insensitive).

        Returns the normalized email. Does not reveal whether the email exists.
        """
        normalized = _normalize_email(value)
        try:
            user = UserModel.objects.get(email__iexact=normalized)
            self.user = user
        except UserModel.DoesNotExist:
            # Do not reveal whether the email exists; keep behavior unchanged.
            pass
        return normalized

    def save(self, **kwargs):
        """Send password reset email if user exists."""
        if hasattr(self, "user"):
            # generate a secure random token and persist it with expiry
            now = timezone.now()
            expires = now + timedelta(hours=24)  # token valid for 24 hours

            # Persist password reset with generated token (avoid creating an unused local variable).
            PasswordReset.objects.create(
                user=self.user,
                token=get_random_string(64),
                expires_at=expires,
            )

            # Optionally: enqueue/send an email with the token-based reset link here.
            # Keep token out of API responses to avoid leaking it.


class PasswordResetOutputSerializer(serializers.Serializer):
    """Output serializer for password reset request."""

    detail = serializers.CharField()


class PasswordChangeInputSerializer(serializers.Serializer):
    """Input serializer for password change."""

    old_password = serializers.CharField(max_length=128, style={"input_type": "password"})
    new_password = serializers.CharField(
        max_length=128,
        validators=[validate_password],
        style={"input_type": "password"},
    )
    new_password_confirm = serializers.CharField(max_length=128, style={"input_type": "password"})

    def validate_old_password(self, value):
        """Validate old password is correct."""
        user = self.context["request"].user
        if not user.check_password(value):
            raise serializers.ValidationError("Old password is incorrect.")
        return value

    def validate(self, attrs):
        """Validate new passwords match."""
        new_password = attrs.get("new_password")
        new_password_confirm = attrs.get("new_password_confirm")

        if new_password != new_password_confirm:
            raise serializers.ValidationError({"new_password_confirm": "New passwords do not match."})

        return attrs

    def save(self, **kwargs):
        """Change user password."""
        user = self.context["request"].user
        # validated_data is guaranteed to exist after is_valid() is called
        new_password = self.validated_data["new_password"]  # type: ignore[index]

        user.set_password(new_password)
        user.save()

        return user


class PasswordChangeOutputSerializer(serializers.Serializer):
    """Output serializer for password change."""

    detail = serializers.CharField()


class LogoutOutputSerializer(serializers.Serializer):
    """Output serializer for logout."""

    message = serializers.CharField()


class SocialProviderOutputSerializer(serializers.Serializer):
    """Output serializer for social authentication providers."""

    id = serializers.CharField()
    name = serializers.CharField()
    authUrl = serializers.URLField()


class AuthStatusOutputSerializer(serializers.Serializer):
    """Output serializer for authentication status check."""

    authenticated = serializers.BooleanField()
    user = UserOutputSerializer(allow_null=True)


# === USER PROFILE SERIALIZERS ===


@extend_schema_serializer(
    examples=[
        OpenApiExample(
            "User Profile Example",
            summary="Example user profile response",
            description="A user's profile information",
            value={
                "id": 1,
                "profile_id": "1234",
                "display_name": "Coaster Enthusiast",
                "bio": "Love visiting theme parks around the world!",
                "pronouns": "they/them",
                "avatar_url": "/media/avatars/user1.jpg",
                "coaster_credits": 150,
                "dark_ride_credits": 45,
                "flat_ride_credits": 80,
                "water_ride_credits": 25,
                "user": {
                    "username": "coaster_fan",
                    "date_joined": "2024-01-01T00:00:00Z",
                },
            },
        )
    ]
)
class UserProfileOutputSerializer(serializers.Serializer):
    """Output serializer for user profiles."""

    id = serializers.IntegerField()
    profile_id = serializers.CharField()
    display_name = serializers.CharField()
    bio = serializers.CharField()
    pronouns = serializers.CharField()
    avatar_url = serializers.SerializerMethodField()
    twitter = serializers.URLField()
    instagram = serializers.URLField()
    youtube = serializers.URLField()
    discord = serializers.CharField()

    # Ride statistics
    coaster_credits = serializers.IntegerField()
    dark_ride_credits = serializers.IntegerField()
    flat_ride_credits = serializers.IntegerField()
    water_ride_credits = serializers.IntegerField()

    # User info (limited)
    user = serializers.SerializerMethodField()

    @extend_schema_field(serializers.URLField(allow_null=True))
    def get_avatar_url(self, obj) -> str | None:
        return obj.get_avatar_url()

    @extend_schema_field(serializers.DictField())
    def get_user(self, obj) -> dict[str, Any]:
        return {
            "username": obj.user.username,
            "date_joined": obj.user.date_joined,
        }


class UserProfileCreateInputSerializer(serializers.Serializer):
    """Input serializer for creating user profiles."""

    display_name = serializers.CharField(max_length=50)
    bio = serializers.CharField(max_length=500, allow_blank=True, default="")
    pronouns = serializers.CharField(max_length=50, allow_blank=True, default="")
    twitter = serializers.URLField(required=False, allow_blank=True)
    instagram = serializers.URLField(required=False, allow_blank=True)
    youtube = serializers.URLField(required=False, allow_blank=True)
    discord = serializers.CharField(max_length=100, allow_blank=True, default="")


class UserProfileUpdateInputSerializer(serializers.Serializer):
    """Input serializer for updating user profiles."""

    display_name = serializers.CharField(max_length=50, required=False)
    bio = serializers.CharField(max_length=500, allow_blank=True, required=False)
    pronouns = serializers.CharField(max_length=50, allow_blank=True, required=False)
    twitter = serializers.URLField(required=False, allow_blank=True)
    instagram = serializers.URLField(required=False, allow_blank=True)
    youtube = serializers.URLField(required=False, allow_blank=True)
    discord = serializers.CharField(max_length=100, allow_blank=True, required=False)
    coaster_credits = serializers.IntegerField(required=False)
    dark_ride_credits = serializers.IntegerField(required=False)
    flat_ride_credits = serializers.IntegerField(required=False)
    water_ride_credits = serializers.IntegerField(required=False)
