import json
import os
import re
import random
from datetime import datetime, timedelta
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", 3)
        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: Optional[dict] = 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"}
