"""
Authentication API endpoints.

Endpoints:
- POST /auth/register - Register new user
- POST /auth/login - Login and get JWT token
- GET /auth/me - Get current user info
- PUT /auth/me - Update current user
- POST /auth/forgot-password - Request password reset
- POST /auth/reset-password - Reset password with token
- POST /auth/verify-email - Verify email address
- POST /auth/resend-verification - Resend verification email
"""
import os
import secrets
import logging
import re
from datetime import datetime, timedelta
from typing import Optional

from fastapi import APIRouter, HTTPException, status, BackgroundTasks, Request, Response
from pydantic import BaseModel, EmailStr
from sqlalchemy import select
from slowapi import Limiter
from slowapi.util import get_remote_address

from app.api.deps import Database, CurrentUser
from app.config import get_settings
from app.schemas.auth import (
    LoginResponse,
    ReferralLinkResponse,
    ReferralStats,
    UserCreate,
    UserLogin,
    UserResponse,
)
from app.services.auth import AuthService
from app.services.email_service import email_service
from app.models.user import User
from app.security import set_auth_cookie, clear_auth_cookie
from app.services.telemetry import track_event
from app.services.referral_rewards import (
    QUALIFIED_REFERRAL_BATCH_SIZE,
    apply_referral_rewards_for_user,
    compute_badge,
)

logger = logging.getLogger(__name__)

router = APIRouter()

# Rate limiter for auth endpoints
limiter = Limiter(key_func=get_remote_address)
settings = get_settings()


# ============== Schemas ==============

class ForgotPasswordRequest(BaseModel):
    """Request password reset."""
    email: EmailStr


class ResetPasswordRequest(BaseModel):
    """Reset password with token."""
    token: str
    new_password: str


class VerifyEmailRequest(BaseModel):
    """Verify email with token."""
    token: str


class MessageResponse(BaseModel):
    """Simple message response."""
    message: str
    success: bool = True


class UpdateUserRequest(BaseModel):
    """Update user profile."""
    name: Optional[str] = None


# ============== Endpoints ==============

@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
@limiter.limit("5/minute")
async def register(
    request: Request,
    user_data: UserCreate,
    db: Database,
    background_tasks: BackgroundTasks,
):
    """
    Register a new user.
    
    - Creates user account
    - Sends verification email (if SMTP configured)
    - Returns user info (without password)
    """
    # Check if user exists
    existing_user = await AuthService.get_user_by_email(db, user_data.email)
    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered",
        )
    
    # Create user
    user = await AuthService.create_user(
        db=db,
        email=user_data.email,
        password=user_data.password,
        name=user_data.name,
    )
    
    # Process referral if present.
    # Supported formats:
    # - yield_{user_id}_{domain_id}
    # - invite code (12 hex chars)
    referral_applied = False
    referrer_user_id: Optional[int] = None
    referral_type: Optional[str] = None

    if user_data.ref:
        ref_raw = user_data.ref.strip()

        # Yield referral: yield_{user_id}_{domain_id}
        if ref_raw.startswith("yield_"):
            try:
                parts = ref_raw.split("_")
                if len(parts) >= 3:
                    referrer_user_id = int(parts[1])
                    user.referred_by_user_id = referrer_user_id
                    user.referral_code = ref_raw
                    referral_type = "yield"

                    # Try to map the yield_domain_id to a domain string
                    try:
                        from app.models.yield_domain import YieldDomain

                        yield_domain_id = int(parts[2])
                        yd_res = await db.execute(select(YieldDomain).where(YieldDomain.id == yield_domain_id))
                        yd = yd_res.scalar_one_or_none()
                        if yd:
                            user.referred_by_domain = yd.domain
                    except Exception:
                        pass

                    await db.commit()
                    referral_applied = True
                    logger.info("User %s referred via yield by user %s", user.email, referrer_user_id)
            except Exception as e:
                logger.warning("Failed to process yield referral code: %s, error: %s", ref_raw, e)
        else:
            # Invite code referral (viral loop)
            code = ref_raw.lower()
            if re.fullmatch(r"[0-9a-f]{12}", code):
                try:
                    ref_user_res = await db.execute(select(User).where(User.invite_code == code))
                    ref_user = ref_user_res.scalar_one_or_none()
                    if ref_user and ref_user.id != user.id:
                        referrer_user_id = ref_user.id
                        user.referred_by_user_id = ref_user.id
                        user.referral_code = code
                        referral_type = "invite"
                        await db.commit()
                        referral_applied = True
                        logger.info("User %s referred via invite_code by user %s", user.email, ref_user.id)
                except Exception as e:
                    logger.warning("Failed to process invite referral code: %s, error: %s", code, e)
    
    # Auto-admin for specific email
    ADMIN_EMAILS = ["guggeryves@hotmail.com"]
    if user.email.lower() in [e.lower() for e in ADMIN_EMAILS]:
        user.is_admin = True
        user.is_verified = True  # Auto-verify admins
        await db.commit()
        
        # Give admin Tycoon subscription (only if no subscription exists)
        from app.models.subscription import Subscription, SubscriptionTier, SubscriptionStatus, TIER_CONFIG
        from sqlalchemy import select
        
        # Check if subscription already exists
        existing_sub = await db.execute(
            select(Subscription).where(Subscription.user_id == user.id)
        )
        if not existing_sub.scalar_one_or_none():
            tycoon_config = TIER_CONFIG.get(SubscriptionTier.TYCOON, {})
            subscription = Subscription(
                user_id=user.id,
                tier=SubscriptionTier.TYCOON,
                status=SubscriptionStatus.ACTIVE,
                max_domains=tycoon_config.get("domain_limit", 500),
            )
            db.add(subscription)
            await db.commit()
    
    # Generate verification token
    verification_token = secrets.token_urlsafe(32)
    user.email_verification_token = verification_token
    user.email_verification_expires = datetime.utcnow() + timedelta(hours=24)
    await db.commit()

    # Telemetry: registration + referral attribution
    try:
        await track_event(
            db,
            event_name="user_registered",
            request=request,
            user_id=user.id,
            is_authenticated=False,
            source="public",
            metadata={"ref": bool(user_data.ref)},
        )
        if referral_applied:
            await track_event(
                db,
                event_name="referral_attributed",
                request=request,
                user_id=user.id,
                is_authenticated=False,
                source="public",
                metadata={
                    "referral_type": referral_type,
                    "referrer_user_id": referrer_user_id,
                    "ref": user_data.ref,
                },
            )
        await db.commit()
    except Exception:
        # never block registration
        pass
    
    # Send verification email in background
    if email_service.is_configured():
        site_url = (settings.site_url or "http://localhost:3000").rstrip("/")
        verify_url = f"{site_url}/verify-email?token={verification_token}"
        
        background_tasks.add_task(
            email_service.send_email_verification,
            to_email=user.email,
            user_name=user.name or "there",
            verification_url=verify_url,
        )
    
    return user


@router.get("/referral", response_model=ReferralLinkResponse)
async def get_referral_link(
    request: Request,
    current_user: CurrentUser,
    db: Database,
    days: int = 30,
):
    """Return the authenticated user's invite link."""
    if not current_user.invite_code:
        # Generate on demand for older users
        for _ in range(12):
            code = secrets.token_hex(6)
            exists = await db.execute(select(User.id).where(User.invite_code == code))
            if exists.scalar_one_or_none() is None:
                current_user.invite_code = code
                await db.commit()
                break
        if not current_user.invite_code:
            raise HTTPException(status_code=500, detail="Failed to generate invite code")

    # Apply rewards (idempotent) so UI reflects current state even without scheduler
    snapshot = await apply_referral_rewards_for_user(db, current_user.id)
    await db.commit()

    base = (settings.site_url or "http://localhost:3000").rstrip("/")
    url = f"{base}/register?ref={current_user.invite_code}"

    try:
        await track_event(
            db,
            event_name="referral_link_viewed",
            request=request,
            user_id=current_user.id,
            is_authenticated=True,
            source="terminal",
            metadata={"invite_code": current_user.invite_code},
        )
        await db.commit()
    except Exception:
        pass

    # Count link views in the chosen window
    try:
        from datetime import timedelta
        from sqlalchemy import and_, func

        from app.models.telemetry import TelemetryEvent

        window_days = max(1, min(int(days), 365))
        end = datetime.utcnow()
        start = end - timedelta(days=window_days)
        views = (
            await db.execute(
                select(func.count(TelemetryEvent.id)).where(
                    and_(
                        TelemetryEvent.event_name == "referral_link_viewed",
                        TelemetryEvent.user_id == current_user.id,
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                    )
                )
            )
        ).scalar()
        referral_link_views_window = int(views or 0)
    except Exception:
        window_days = 30
        referral_link_views_window = 0

    qualified = int(snapshot.qualified_referrals_total)
    if qualified < QUALIFIED_REFERRAL_BATCH_SIZE:
        next_reward_at = QUALIFIED_REFERRAL_BATCH_SIZE
    else:
        remainder = qualified % QUALIFIED_REFERRAL_BATCH_SIZE
        next_reward_at = qualified + (QUALIFIED_REFERRAL_BATCH_SIZE - remainder) if remainder else qualified + QUALIFIED_REFERRAL_BATCH_SIZE

    return ReferralLinkResponse(
        invite_code=current_user.invite_code,
        url=url,
        stats=ReferralStats(
            window_days=int(window_days),
            referred_users_total=int(snapshot.referred_users_total),
            qualified_referrals_total=qualified,
            referral_link_views_window=int(referral_link_views_window),
            bonus_domains=int(snapshot.bonus_domains),
            next_reward_at=int(next_reward_at),
            badge=compute_badge(qualified),
            cooldown_days=int(getattr(snapshot, "cooldown_days", 7) or 7),
            disqualified_cooldown_total=int(getattr(snapshot, "disqualified_cooldown_total", 0) or 0),
            disqualified_missing_ip_total=int(getattr(snapshot, "disqualified_missing_ip_total", 0) or 0),
            disqualified_shared_ip_total=int(getattr(snapshot, "disqualified_shared_ip_total", 0) or 0),
            disqualified_duplicate_ip_total=int(getattr(snapshot, "disqualified_duplicate_ip_total", 0) or 0),
        ),
    )


@router.post("/login", response_model=LoginResponse)
@limiter.limit("10/minute")
async def login(request: Request, user_data: UserLogin, db: Database, response: Response):
    """
    Authenticate user and return JWT token.
    
    Note: Email verification is currently not enforced.
    Set REQUIRE_EMAIL_VERIFICATION=true to enforce.
    """
    from app.models.subscription import Subscription, SubscriptionTier, SubscriptionStatus, TIER_CONFIG
    from sqlalchemy import select
    
    user = await AuthService.authenticate_user(db, user_data.email, user_data.password)
    
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect email or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    # Auto-admin for specific email
    ADMIN_EMAILS = ["guggeryves@hotmail.com"]
    if user.email.lower() in [e.lower() for e in ADMIN_EMAILS]:
        if not user.is_admin:
            user.is_admin = True
            user.is_verified = True  # Auto-verify admins
            await db.commit()
        
        # Ensure admin has Tycoon subscription
        sub_result = await db.execute(
            select(Subscription).where(Subscription.user_id == user.id)
        )
        subscription = sub_result.scalar_one_or_none()
        
        tycoon_config = TIER_CONFIG.get(SubscriptionTier.TYCOON, {})
        
        if not subscription:
            subscription = Subscription(
                user_id=user.id,
                tier=SubscriptionTier.TYCOON,
                status=SubscriptionStatus.ACTIVE,
                max_domains=tycoon_config.get("domain_limit", 500),
            )
            db.add(subscription)
            await db.commit()
        elif subscription.tier != SubscriptionTier.TYCOON:
            subscription.tier = SubscriptionTier.TYCOON
            subscription.max_domains = tycoon_config.get("domain_limit", 500)
            subscription.status = SubscriptionStatus.ACTIVE
            await db.commit()
    
    # Optional: Check email verification
    require_verification = os.getenv("REQUIRE_EMAIL_VERIFICATION", "false").lower() == "true"
    if require_verification and not user.is_verified:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Please verify your email address before logging in",
        )
    
    access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
    access_token = AuthService.create_access_token(
        data={"sub": str(user.id), "email": user.email},
        expires_delta=access_token_expires,
    )
    
    # Set HttpOnly cookie (preferred for browser clients)
    set_auth_cookie(
        response=response,
        token=access_token,
        max_age_seconds=settings.access_token_expire_minutes * 60,
    )

    # Do NOT return the token in the response body (prevents leaks via logs/JS storage)
    return LoginResponse(expires_in=settings.access_token_expire_minutes * 60)


@router.post("/logout", response_model=MessageResponse)
async def logout(response: Response):
    """Clear auth cookie."""
    clear_auth_cookie(response)
    return MessageResponse(message="Logged out")


@router.get("/me", response_model=UserResponse)
async def get_current_user_info(current_user: CurrentUser):
    """Get current user information."""
    return current_user


@router.put("/me", response_model=UserResponse)
async def update_current_user(
    update_data: UpdateUserRequest,
    current_user: CurrentUser,
    db: Database,
):
    """Update current user information."""
    if update_data.name is not None:
        current_user.name = update_data.name
    
    await db.commit()
    await db.refresh(current_user)
    
    return current_user


@router.post("/forgot-password", response_model=MessageResponse)
@limiter.limit("3/minute")
async def forgot_password(
    request: Request,
    payload: ForgotPasswordRequest,
    db: Database,
    background_tasks: BackgroundTasks,
):
    """
    Request password reset email.
    
    - Always returns success (to prevent email enumeration)
    - If email exists, sends reset link
    - Reset token expires in 1 hour
    """
    # Always return success (security: don't reveal if email exists)
    success_message = "If an account with this email exists, a password reset link has been sent."
    
    # Look up user
    result = await db.execute(select(User).where(User.email == payload.email.lower()))
    user = result.scalar_one_or_none()
    
    if not user:
        # Return success anyway (security)
        return MessageResponse(message=success_message)
    
    # Generate reset token
    reset_token = secrets.token_urlsafe(32)
    user.password_reset_token = reset_token
    user.password_reset_expires = datetime.utcnow() + timedelta(hours=1)
    await db.commit()
    
    # Send reset email in background
    if email_service.is_configured():
        site_url = (settings.site_url or "http://localhost:3000").rstrip("/")
        reset_url = f"{site_url}/reset-password?token={reset_token}"
        
        background_tasks.add_task(
            email_service.send_password_reset,
            to_email=user.email,
            user_name=user.name or "there",
            reset_url=reset_url,
        )
        logger.info(f"Password reset email queued for {user.email}")
    else:
        logger.warning(f"SMTP not configured, cannot send reset email for {user.email}")
    
    return MessageResponse(message=success_message)


@router.post("/reset-password", response_model=MessageResponse)
async def reset_password(
    request: ResetPasswordRequest,
    db: Database,
):
    """
    Reset password using token from email.
    
    - Token must be valid and not expired
    - Password must be at least 8 characters
    - Invalidates token after use
    """
    if len(request.new_password) < 8:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Password must be at least 8 characters",
        )
    
    # Find user with valid token
    result = await db.execute(
        select(User).where(
            User.password_reset_token == request.token,
            User.password_reset_expires > datetime.utcnow(),
        )
    )
    user = result.scalar_one_or_none()
    
    if not user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired reset token",
        )
    
    # Update password
    user.password_hash = AuthService.get_password_hash(request.new_password)
    user.password_reset_token = None
    user.password_reset_expires = None
    await db.commit()
    
    logger.info(f"Password reset successful for user {user.id}")
    
    return MessageResponse(message="Password has been reset successfully. You can now log in.")


@router.post("/verify-email", response_model=MessageResponse)
async def verify_email(
    request: VerifyEmailRequest,
    db: Database,
):
    """
    Verify email address using token from email.
    
    - Token must be valid and not expired
    - Marks user as verified
    """
    # Find user with valid token
    result = await db.execute(
        select(User).where(
            User.email_verification_token == request.token,
            User.email_verification_expires > datetime.utcnow(),
        )
    )
    user = result.scalar_one_or_none()
    
    if not user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired verification token",
        )
    
    # Mark as verified
    user.is_verified = True
    user.email_verification_token = None
    user.email_verification_expires = None
    await db.commit()
    
    logger.info(f"Email verified for user {user.id}")
    
    return MessageResponse(message="Email verified successfully. You can now log in.")


@router.post("/resend-verification", response_model=MessageResponse)
@limiter.limit("3/minute")
async def resend_verification(
    request: Request,
    payload: ForgotPasswordRequest,  # Reuse schema - just needs email
    db: Database,
    background_tasks: BackgroundTasks,
):
    """
    Resend verification email.
    
    - Rate limited to prevent abuse
    - Always returns success (security)
    """
    success_message = "If an unverified account with this email exists, a verification link has been sent."
    
    # Look up user
    result = await db.execute(
        select(User).where(User.email == payload.email.lower())
    )
    user = result.scalar_one_or_none()
    
    if not user or user.is_verified:
        return MessageResponse(message=success_message)
    
    # Generate new verification token
    verification_token = secrets.token_urlsafe(32)
    user.email_verification_token = verification_token
    user.email_verification_expires = datetime.utcnow() + timedelta(hours=24)
    await db.commit()
    
    # Send verification email
    if email_service.is_configured():
        site_url = (settings.site_url or "http://localhost:3000").rstrip("/")
        verify_url = f"{site_url}/verify-email?token={verification_token}"
        
        background_tasks.add_task(
            email_service.send_email_verification,
            to_email=user.email,
            user_name=user.name or "there",
            verification_url=verify_url,
        )
    
    return MessageResponse(message=success_message)
