from datetime import datetime
from typing import Any, Dict

from bson import ObjectId
from fastapi import APIRouter, Depends, HTTPException, Path
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from app.db.db import get_col
from app.models.User import (
    EmailOtpVerify,
    ForgotPasswordRequest,
    ForgotPasswordVerify,
    LoginRequest,
    LoginResponse,
    ResetPasswordRequest,
    UserAddAddress,
    UserCreate,
    UserOut,
    UserUpdateAddress,
    addToWishlistRequest,
    removeFromWishlistRequest,
    userUpdateProfile,
    VoucherRequest
)
from app.utility.otp import create_email_otp, send_otp_via_email, verify_email_otp
from app.utility.security import create_jwt, hash_password, verify_jwt, verify_password

router = APIRouter(prefix="/users", tags=["users"])
auth_scheme = HTTPBearer(auto_error=False)


def _ensure_object_id(value: str, label: str = "user id") -> ObjectId:
    try:
        return ObjectId(value)
    except Exception as exc:
        raise HTTPException(status_code=400, detail=f"Invalid {label}") from exc


def _get_user_or_404(user_id: str) -> Dict[str, Any]:
    users = get_col("users")
    user = users.find_one({"_id": _ensure_object_id(user_id)})
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user


def _assert_self_or_admin(target_user: Dict[str, Any], requester: Dict[str, Any]) -> None:
    if str(target_user["_id"]) == str(requester["_id"]):
        return
    if requester.get("role") == "admin":
        return
    raise HTTPException(status_code=403, detail="Not authorized to modify this user")


def _user_to_out(doc: Dict[str, Any]) -> UserOut:
    return UserOut(
        id=str(doc["_id"]),
        name=doc["name"],
        email=doc["email"],
        role=doc.get("role", "user"),
        mobile=doc["mobile"],
        isVerified=bool(doc.get("isVerified", False)),
        createdAt=doc.get("createdAt", datetime.utcnow()),
        updatedAt=doc.get("updatedAt", datetime.utcnow()),
    )


@router.post("/register")
def register(body: UserCreate):
    users = get_col("users")
    existing = users.find_one({"email": body.email.lower()})
    if existing:
        raise HTTPException(status_code=400, detail="Email already registered")
    now = datetime.utcnow()
    doc = {
        "name": body.name,
        "email": body.email.lower(),
        "password": hash_password(body.password),
        "role": "user",
        "mobile": body.mobile,
        "isVerified": False,
        "wishlist": [],
        "addresses": [],
        "createdAt": now,
        "updatedAt": now,
    }
    res = users.insert_one(doc)
    otp = create_email_otp(body.email, "verify_email")
    send_otp_via_email(body.email, otp, "verify_email")
    return {"message": "Registered successfully. OTP sent to email for verification.", "userId": str(res.inserted_id)}


@router.post("/verify-email")
def verify_email(body: EmailOtpVerify):
    users = get_col("users")
    ok = verify_email_otp(body.email, "verify_email", body.otp)
    if not ok:
        raise HTTPException(status_code=400, detail="Invalid or expired OTP")
    res = users.update_one({"email": body.email.lower()}, {"$set": {"isVerified": True, "updatedAt": datetime.utcnow()}})
    if res.matched_count == 0:
        raise HTTPException(status_code=404, detail="User not found")
    return {"message": "Email verified successfully"}


@router.post("/login", response_model=LoginResponse)
def login(body: LoginRequest):
    users = get_col("users")
    doc = users.find_one({"email": body.email.lower()})
    if not doc:
        raise HTTPException(status_code=400, detail="Invalid credentials")
    if not verify_password(body.password, doc["password"]):
        raise HTTPException(status_code=400, detail="Invalid credentials")
    token = create_jwt({"sub": str(doc["_id"]), "email": doc["email"], "role": doc.get("role", "user")})
    return LoginResponse(access_token=token, user=_user_to_out(doc))


@router.post("/forgot-password")
def forgot_password(body: ForgotPasswordRequest):
    users = get_col("users")
    doc = users.find_one({"email": body.email.lower()})
    if not doc:
        # Avoid leaking existence of account
        return {"message": "If the email exists, an OTP has been sent"}
    otp = create_email_otp(body.email, "reset_password")
    send_otp_via_email(body.email, otp, "reset_password")
    return {"message": "If the email exists, an OTP has been sent"}


@router.post("/forgot-password/verify")
def forgot_password_verify(body: ForgotPasswordVerify):
    users = get_col("users")
    ok = verify_email_otp(body.email, "reset_password", body.otp)
    if not ok:
        raise HTTPException(status_code=400, detail="Invalid or expired OTP")
    res = users.update_one(
        {"email": body.email.lower()},
        {"$set": {"password": hash_password(body.newPassword), "updatedAt": datetime.utcnow()}},
    )
    if res.matched_count == 0:
        raise HTTPException(status_code=404, detail="User not found")
    return {"message": "Password updated successfully"}


def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(auth_scheme)):
    if not credentials:
        raise HTTPException(status_code=401, detail="Not authenticated")
    token = credentials.credentials
    ok, payload = verify_jwt(token)
    if not ok or not payload:
        raise HTTPException(status_code=401, detail="Invalid token")
    users = get_col("users")
    try:
        oid = ObjectId(payload["sub"])
    except Exception as exc:
        raise HTTPException(status_code=401, detail="Invalid token subject") from exc
    user = users.find_one({"_id": oid})
    if not user:
        raise HTTPException(status_code=401, detail="User not found")
    return user


@router.get("/me", response_model=UserOut)
def me(user=Depends(get_current_user)):
    return _user_to_out(user)

@router.put("/me/update-profile", response_model=UserOut)
def update_profile(data: userUpdateProfile, user=Depends(get_current_user)):
    users = get_col("users")
    update_data = {k: v for k, v in data.dict().items() if v is not None}

    if not update_data:
        raise HTTPException(status_code=400, detail="No fields to update")

    set_fields = {k: v for k, v in update_data.items()}
    set_fields["updatedAt"] = datetime.utcnow()
    users.update_one(
        {"_id": user["_id"]},
        {"$set": set_fields},
    )
    updated_user = users.find_one({"_id": user["_id"]})
    return _user_to_out(updated_user)

@router.post("/reset-password")
def reset_password(body: ResetPasswordRequest, user=Depends(get_current_user)):
    users = get_col("users")
    if not verify_password(body.currentPassword, user["password"]):
        raise HTTPException(status_code=400, detail="Current password is incorrect")
    users.update_one(
        {"_id": user["_id"]},
        {"$set": {"password": hash_password(body.newPassword), "updatedAt": datetime.utcnow()}},
    )
    return {"message": "Password updated successfully"}

@router.get("/{user_id}/addresses")
def get_addresses(
    user_id: str = Path(..., description="Target user identifier"),
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    return {"addresses": target_user.get("addresses", [])}


@router.post("/add/{user_id}/addresses")
def add_address(
    user_id: str,
    data: UserAddAddress,
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    users = get_col("users")
    address = data.dict()
    address["_id"] = str(ObjectId())

    if address.get("isDefault"):
        users.update_one(
            {"_id": target_user["_id"]},
            {"$set": {"addresses.$[].isDefault": False}},
        )

    users.update_one(
        {"_id": target_user["_id"]},
        {
            "$push": {"addresses": address},
            "$set": {"updatedAt": datetime.utcnow()},
        },
    )
    return {"message": "Address added successfully", "address": address}


@router.put("/{user_id}/addresses/{address_id}")
def update_address(
    user_id: str,
    address_id: str,
    data: UserUpdateAddress,
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    users = get_col("users")
    update_data = {k: v for k, v in data.dict().items() if v is not None}

    if not update_data:
        raise HTTPException(status_code=400, detail="No fields to update")

    if update_data.get("isDefault"):
        users.update_one(
            {"_id": target_user["_id"]},
            {"$set": {"addresses.$[].isDefault": False}},
        )

    set_fields = {f"addresses.$.{k}": v for k, v in update_data.items()}
    set_fields["updatedAt"] = datetime.utcnow()
    result = users.update_one(
        {"_id": target_user["_id"], "addresses._id": address_id},
        {"$set": set_fields},
    )

    if result.modified_count == 0:
        raise HTTPException(status_code=404, detail="Address not found")

    return {"message": "Address updated successfully"}


@router.delete("/{user_id}/delete/addresses/{address_id}")
def delete_address(
    user_id: str,
    address_id: str,
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    users = get_col("users")
    result = users.update_one(
        {"_id": target_user["_id"]},
        {
            "$pull": {"addresses": {"_id": address_id}},
            "$set": {"updatedAt": datetime.utcnow()},
        },
    )

    if result.modified_count == 0:
        raise HTTPException(status_code=404, detail="Address not found")

    return {"message": "Address removed successfully"}


@router.post("/{user_id}/wishlist")
def add_to_wishlist(
    user_id: str,
    data: addToWishlistRequest,
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    users = get_col("users")

    exists = next(
        (
            item
            for item in target_user.get("wishlist", [])
            if item["productId"] == data.productId
            and item.get("variantId") == data.variantId
        ),
        None,
    )

    if exists:
        raise HTTPException(status_code=400, detail="Item already in wishlist")

    entry = {
        "productId": data.productId,
        "variantId": data.variantId,
        "addedAt": datetime.utcnow(),
    }

    users.update_one(
        {"_id": target_user["_id"]},
        {
            "$push": {"wishlist": entry},
            "$set": {"updatedAt": datetime.utcnow()},
        },
    )

    return {"message": "Added to wishlist", "wishlist": entry}


@router.delete("/delete/{user_id}/wishlist")
def remove_from_wishlist(
    user_id: str,
    data: removeFromWishlistRequest,
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    users = get_col("users")

    pull_query = {"productId": data.productId}
    if data.variantId:
        pull_query["variantId"] = data.variantId

    result = users.update_one(
        {"_id": target_user["_id"]},
        {
            "$pull": {"wishlist": pull_query},
            "$set": {"updatedAt": datetime.utcnow()},
        },
    )

    if result.modified_count == 0:
        raise HTTPException(status_code=404, detail="Item not found in wishlist")

    return {"message": "Removed from wishlist"}


@router.get("/{user_id}/wishlist", response_model=Dict[str, Any])
def get_wishlist(
    user_id: str,
    current_user=Depends(get_current_user),
):
    target_user = _get_user_or_404(user_id)
    _assert_self_or_admin(target_user, current_user)
    return {"wishlist": target_user.get("wishlist", [])}


