import json
import os
import re
import random
import string
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"
COLLECTION_EMAILS = "emails"
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)

# Generate Referral Code for Users
def generate_referral_code(length=8):
    """Generate a random alphanumeric referral code."""
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))

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)

    # ✅ Generate a unique referral code
    max_attempts = 5
    for attempt in range(max_attempts):
        referral_code = generate_referral_code()
        if not users_collection.find_one({"referral_code": referral_code}):
            new_user_data["referral_code"] = referral_code
            break
    else:
        raise HTTPException(status_code=500, detail="Could not generate unique referral code")

    try:
        # ✅ Get role value from emails collection and update new_user_data
        emails_collection = db[COLLECTION_EMAILS]
        emails_info = emails_collection.find_one({"email": user.email})
        if emails_info:
            new_user_data["roles"] = emails_info.get("role", 100)  # Fixed key: 'role' not 'roles'
            new_user_data["referred_by"] = emails_info.get("referred_by", '')

            referral_info = users_collection.find_one({"referral_code": new_user_data["referred_by"]})
            if referral_info:
                new_user_data["referred_account_id"] = referral_info.get("account_id", '')            
        
        # 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
    referral_code = current_user.get("referral_code") 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,
                "referred_by": referral_code
            }
            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,referral_code)
            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], status: Optional[str], referred_account_id: Optional[str], db, current_user, created_date_from:Optional[str], created_date_to:Optional[str],role:Optional[int]) -> 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 created_date_from or created_date_to:
        date_filter = {}
        if created_date_from:
            date_filter["$gte"] = datetime.strptime(created_date_from, "%Y-%m-%d")
        if created_date_to:
            date_filter["$lte"] = datetime.strptime(created_date_to, "%Y-%m-%d")
        query["created_date"] = date_filter

    # 🔹 Referred ID filter
    if referred_account_id:
        query["referred_account_id"] = referred_account_id

    if role:
        query["role"] = role

    if q:
        regex_query = {"$regex": q, "$options": "i"}
        query["$or"] = [{"name": regex_query}, {"email": regex_query}, {"mobile": regex_query}]
    
    if status:
        if status.lower() == "active":
            query["is_active"] = True
        elif status.lower() == "inactive":
            query["is_active"] = False

    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 read_user_serviceby_email(email_id: str, db) -> dict:
    user = db['emails'].find_one({"email": email_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"}
