import json
import os
import re
import random
from datetime import datetime, timedelta

# passlib 1.7.x expects bcrypt.__about__.__version__, but bcrypt>=4 removed __about__.
# Avoid noisy warnings by providing a compat shim.
try:
    import bcrypt as _bcrypt  # type: ignore

    if not hasattr(_bcrypt, "__about__"):
        class _About:  # pragma: no cover
            __version__ = getattr(_bcrypt, "__version__", "0")

        _bcrypt.__about__ = _About()  # type: ignore[attr-defined]
except Exception:
    pass

from passlib.context import CryptContext
from bson import ObjectId
from pymongo.errors import DuplicateKeyError
from fastapi import HTTPException, status, Request
from dotenv import load_dotenv
from typing import Optional

from app.v1.dependencies.auth import create_access_token
from app.v1.libraries.object import str_to_objectid
from ...libraries.email_templates import (
    send_verification_email, send_invite_email, 
    send_forgot_password_email, send_welcome_email
)
from ...models.saas.usersmodel import (
    User, UserBase, UserLogin, UserUpdate, 
    UserResponseList, EmailAction, ResetPasswordRequest
)

load_dotenv()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
ACCESS_TOKEN_EXPIRES = timedelta(minutes=14320)

# Collection names
COLLECTION_NAME = "users"
COLLECTION_ROLE = "roles"
ACCOUNT_COLLECTION_NAME = "accounts"
SUBSCRIPTION_COLLECTION_NAME = "subscriptions"
PROJECT_COLLECTION_NAME = "projects"

ALLOWED_FREE_AGENTS = int(os.getenv('ALLOWED_FREE_AGENTS', '2'))

# Custom JSON encoder to handle datetime and ObjectId
class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, ObjectId):
            return str(obj)
        return super().default(obj)

def create_user_service(user: UserBase, background_tasks, db) -> dict:
    users_collection = db[COLLECTION_NAME]
    emails_collection = db['emails']

    # Check if user exists by email
    if users_collection.find_one({"email": user.email}):
        raise HTTPException(status_code=400, detail="User with this email already exists.")

    # Verify email using the verification code from emails collection
    email_entry = emails_collection.find_one({"email": user.email})
    if not email_entry or "verificationCode" not in email_entry or email_entry["verificationCode"] != user.verificationCode:
        raise HTTPException(status_code=400, detail="Invalid verification code.")

    # Hash the password and prepare user data
    hashed_password = pwd_context.hash(user.password)
    new_user_data = {**user.dict(), "hashed_password": hashed_password, "is_active": True, "is_verified": True}
    new_user_data['subscription_agents'] = ALLOWED_FREE_AGENTS
    new_user_data['active_agents'] = 0

    # If an account_id exists in the email entry, populate account info
    if email_entry.get("account_id"):
        new_user_data["account_id"] = email_entry["account_id"]
        new_user_data["roles"] = email_entry.get("role", 100)
        accounts_collection = db[ACCOUNT_COLLECTION_NAME]
        account_info = accounts_collection.find_one({"_id": str_to_objectid(email_entry.get("account_id"))})
        if account_info:
            new_user_data['account_type'] = account_info.get('account_type', 0)
            new_user_data['subscription_id'] = account_info.get('active_subscription_id', '')
            new_user_data['subscription_status'] = account_info.get('subscription_status', '')
            if account_info.get('active_subscription_id'):
                subscription_collection = db[SUBSCRIPTION_COLLECTION_NAME]
                subscription = subscription_collection.find_one({"_id": str_to_objectid(account_info.get('active_subscription_id'))})
                if subscription:
                    new_user_data['active_agents'] = subscription.get('active_agents_count', 0)
                    new_user_data['subscription_agents'] = subscription.get('subscription_agents_count', ALLOWED_FREE_AGENTS)
            else:
                project_collection = db[PROJECT_COLLECTION_NAME]
                active_projects_count = project_collection.count_documents({"account_id": new_user_data["account_id"], "status": "ACTIVE"})
                new_user_data['active_agents'] = active_projects_count
    else:
        # For new users with no account_id, you might set defaults here
        pass

    new_user_data["created_date"] = datetime.utcnow()
    new_user_data["last_login"] = datetime.utcnow()
    new_user_data.pop("password")  # Remove plain password
    new_user_data["name"] = re.search(r'^([^@]+)', user.email).group(1)

    try:
        # Exclude subscription-related keys from the insertion if needed
        insert_user_data = {k: v for k, v in new_user_data.items() if k not in ['account_type', 'subscription_id', 'subscription_status', 'active_agents', 'subscription_agents']}
        token = create_access_token(data={"sub": user.email}, expires_delta=ACCESS_TOKEN_EXPIRES)
        new_user = users_collection.insert_one(insert_user_data)
        new_user_data["_id"] = str(new_user.inserted_id)
        background_tasks.add_task(send_welcome_email, user.email, new_user_data["name"])
    except DuplicateKeyError:
        raise HTTPException(status_code=400, detail="User with this email already exists.")

    # Prepare JSON-compatible user data
    json_compatible_user_data = json.loads(json.dumps(new_user_data, cls=CustomJSONEncoder))
    return {"user_data": json_compatible_user_data, "token": token}

#This is the main email invitation function for various purposes

def send_email_verification_service(email_action: EmailAction, background_tasks, db, current_user=None) -> dict:
    users_collection = db[COLLECTION_NAME]
    emails_collection = db['emails']
    accounts_collection = db[ACCOUNT_COLLECTION_NAME]
    roles_collection = db[COLLECTION_ROLE]
    email_list = [email.strip() for email in email_action.emails.split(',') if email.strip()]

    if not email_list or not email_action.action:
        return {"message": "Email(s) and action are required.", "status": 0}

    account_id = current_user.get("account_id") if current_user else None


    if email_action.action in ['signup', 'forgot']:
        email = email_list[0]
        user_exists = users_collection.find_one({"email": email}) is not None

        if email_action.action == 'signup' and user_exists:
            return {"message": "User with this email already exists.", "status": 1}
        if email_action.action == 'forgot' and not user_exists:
            return {"message": "Email not found.", "status": 2}

        verification_code = random.randint(100000, 999999)
        emails_collection.update_one(
            {"email": email},
            {"$set": {"verificationCode": verification_code}},
            upsert=True
        )

        if email_action.action == 'signup':
            background_tasks.add_task(send_verification_email, email, verification_code)
        else:
            background_tasks.add_task(send_forgot_password_email, email, verification_code)

        return {"message": f"Verification code sent to {email}.", "status": 3}

    elif email_action.action == 'invite':
        account_name = "MOVEX"


        if account_id:
            account_details = accounts_collection.find_one({"_id": str_to_objectid(account_id)})
            if account_details:
                account_name = account_details.get("account_name", "MOVEX")

        invited = []
        skipped = []

        for email in email_list:
            user_exists = users_collection.find_one({"email": email}) is not None
            if user_exists:
                skipped.append(email)
                continue

    
            verification_code = random.randint(100000, 999999)

            roledetails = roles_collection.find_one({"role_id": email_action.role})
            role_name = roledetails.get("name", "User") if roledetails else "User"


            email_data = {
                "email": email,
                "verificationCode": verification_code,
                "role": email_action.role
            }
            if email_action.account_id:
                email_data["account_id"] = email_action.account_id

            emails_collection.update_one({"email": email}, {"$set": email_data}, upsert=True)
            background_tasks.add_task(send_invite_email, email, verification_code, email_action.role, role_name, account_name)
            invited.append(email)

        return {
            "message": f"Invitations sent to {len(invited)} user(s).",
            "invited": invited,
            "skipped": skipped,
            "status": 3
        }

    else:
        raise HTTPException(status_code=400, detail="Invalid action.")

def get_users_service(account_id: str, skip: int, limit: int, q: Optional[str], is_active: Optional[bool], db, current_user) -> dict:
    users_collection = db[COLLECTION_NAME]
    query = {}
    print("hello BoOSS")
    if account_id == "all" and current_user.get("roles" != "1"):
        raise HTTPException(status_code=403, detail="Not permitted to view all users.")

    if account_id != "all":
        query["account_id"] = account_id
    if q:
        regex_query = {"$regex": q, "$options": "i"}
        query["$or"] = [{"name": regex_query}, {"email": regex_query}, {"mobile": regex_query}]
    if is_active is not None:
        query["is_active"] = is_active

    users = list(users_collection.find(query).skip(skip).limit(limit))
    for user in users:
        user["user_id"] = str(user["_id"])
        for field in ['created_date', 'last_login', 'date_of_birth']:
            if field in user and isinstance(user[field], datetime):
                user[field] = user[field].isoformat()
    total_count = users_collection.count_documents(query)
    print("hey boss")
    return {"total_count": total_count, "users": users}

def read_user_service(user_id: str, db) -> dict:
    user = db[COLLECTION_NAME].find_one({"_id": str_to_objectid(user_id)})
    if user:
        user["id"] = str(user["_id"])  # Map _id to id
        user.pop("hashed_password", None)
    return user

def update_user_service(user_id: str, user_data: UserUpdate, db) -> dict:
    users_collection = db[COLLECTION_NAME]
    existing_user = users_collection.find_one({"_id": str_to_objectid(user_id)})
    if not existing_user:
        raise HTTPException(status_code=404, detail="User not found")
    update_data = {key: value for key, value in user_data.dict().items() if value is not None}
    result = users_collection.update_one({"_id": str_to_objectid(user_id)}, {"$set": update_data})
    if result.matched_count == 0:
        raise HTTPException(status_code=404, detail="User not found")
    updated_user = users_collection.find_one({"_id": str_to_objectid(user_id)})
    if updated_user:
        updated_user.pop("hashed_password", None)
        return updated_user
    raise HTTPException(status_code=404, detail="User not found after update")

def delete_user_service(user_id: str, db) -> dict:
    users_collection = db[COLLECTION_NAME]
    user = users_collection.find_one({"_id": str_to_objectid(user_id)})
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    users_collection.delete_one({"_id": str_to_objectid(user_id)})
    return user

def reset_password_service(request_body: ResetPasswordRequest, db) -> dict:
    email = request_body.email
    code = request_body.verificationCode
    password = request_body.password
    users_collection = db[COLLECTION_NAME]
    emails_collection = db['emails']
    user = users_collection.find_one({"email": email})
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    email_entry = emails_collection.find_one({"email": email})
    if not email_entry or "verificationCode" not in email_entry or str(email_entry["verificationCode"]) != code:
        raise HTTPException(status_code=400, detail="Invalid verification code.")
    hashed_password = pwd_context.hash(password)
    users_collection.update_one({"_id": user["_id"]}, {"$set": {"hashed_password": hashed_password}})
    emails_collection.update_one({"email": email}, {"$unset": {"verificationCode": ""}})
    return {"message": "Password reset successfully"}
