import os
from datetime import datetime

def create_token_file(roles: list[str]):
    """
    Generates a 'tokens_repo.py' file with token management functions
    for a specified list of user roles.

    Args:
        roles: A list of strings, where each string is a user role
               (e.g., ["admin", "staff", "user"]).
    """
    # Create the 'repositories' directory if it doesn't already exist
    directory = "repositories"
    if not os.path.exists(directory):
        os.makedirs(directory)
        print(f"Created directory: '{directory}/'")

    # --- Start of the template for the generated file ---
    
    # Header comments and essential imports
    file_content = f"""
# ============================================================================
# TOKEN REPOSITORY
# ============================================================================
# This file was auto-generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S WAT')}
# It contains asynchronous functions for managing access and refresh tokens
# in a MongoDB database using FastAPI.
#
# DO NOT EDIT THIS FILE MANUALLY - RE-RUN THE GENERATOR INSTEAD.
# ============================================================================

from core.database import db  # Your Motor MongoDB client instance
from schemas.tokens_schema import accessTokenCreate, refreshTokenCreate, accessTokenOut, refreshTokenOut
from datetime import datetime, timedelta
from dateutil import parser
from bson import ObjectId, errors
from fastapi import HTTPException

# --- Generic Helper Functions ---

def is_token_expired(date_string: str, days: int = 10) -> bool:
    \"\"\"
    Checks if a given ISO date string is older than a specified number of days.
    Returns True if the token is expired, False otherwise.
    \"\"\"
    try:
        created_date = parser.isoparse(date_string)
        # Ensure the current time is timezone-aware to match the created_date
        now = datetime.utcnow().replace(tzinfo=created_date.tzinfo)
        return (now - created_date) > timedelta(days=days)
    except (ValueError, TypeError):
        # Treat invalid or missing date formats as expired for security
        return True

# --- Generic Token Operations ---

async def add_refresh_token(token_data: refreshTokenCreate) -> refreshTokenOut:
    \"\"\"Saves a new refresh token to the database.\"\"\"
    token_dict = token_data.model_dump()
    result = await db.refreshToken.insert_one(token_dict)
    
    # Verify insertion and retrieve the document
    created_token = await db.refreshToken.find_one({{"_id": result.inserted_id}})
    if not created_token:
        raise HTTPException(status_code=500, detail="Failed to create and retrieve refresh token.")
    
    return refreshTokenOut(**created_token)

async def get_refresh_token(token_id: str) -> refreshTokenOut | None:
    \"\"\"Retrieves a refresh token by its ID if it exists.\"\"\"
    try:
        obj_id = ObjectId(token_id)
        token = await db.refreshToken.find_one({{"_id": obj_id}})
        return refreshTokenOut(**token) if token else None
    except errors.InvalidId:
        return None # Return None for invalid ID format

async def get_access_token(token_id: str) -> accessTokenOut | None:
    \"\"\"
    Retrieves an access token by its ID and validates it.
    
    Validation checks:
    1. Token existence.
    2. Expiration (deletes if expired).
    3. Status must be 'active'.
    
    Returns the token if valid, otherwise None.
    \"\"\"
    try:
        obj_id = ObjectId(token_id)
    except errors.InvalidId:
        return None # Invalid ID format is not a valid token

    token = await db.accessToken.find_one({{"_id": obj_id}})
    if not token:
        return None
        
    # Check for expiration
    if is_token_expired(date_string=token.get('dateCreated', '')):
        await db.accessToken.delete_one({{"_id": obj_id}})
        return None
        
    # Security check: Only return tokens that are explicitly activated
    if token.get('status') != 'active':
        return None
        
    return accessTokenOut(**token)

async def delete_refresh_token(token_id: str) -> bool:
    \"\"\"Deletes a refresh token by its ID. Returns True if successful.\"\"\"
    try:
        obj_id = ObjectId(token_id)
        result = await db.refreshToken.delete_one({{"_id": obj_id}})
        return result.deleted_count > 0
    except errors.InvalidId:
        return False

async def delete_all_tokens_for_user(user_id: str):
    \"\"\"Deletes all access and refresh tokens associated with a user ID.\"\"\"
    await db.accessToken.delete_many({{"userId": user_id}})
    await db.refreshToken.delete_many({{"userId": user_id}})

# --- Role-Specific Access Token Management ---
"""

    # --- Dynamically generate functions for each role ---
    
    role_specific_functions = []
    for role in roles:
        # Sanitize role name to be valid in a function name (e.g., "super admin" -> "super_admin")
        safe_role_name = role.strip().lower().replace(" ", "_").replace("-", "_")
        
        # Function to add a new token for the role
        add_func = f"""
async def add_{safe_role_name}_access_token(token_data: accessTokenCreate) -> accessTokenOut:
    \"\"\"
    Creates a new, 'inactive' access token for a '{safe_role_name}'.
    The token must be activated before it can be used.
    \"\"\"
    token_dict = token_data.model_dump()
    token_dict['role'] = "{safe_role_name}"
    token_dict['status'] = "inactive"  # All tokens start as inactive for security
    
    result = await db.accessToken.insert_one(token_dict)
    created_token = await db.accessToken.find_one({{"_id": result.inserted_id}})
    if not created_token:
        raise HTTPException(status_code=500, detail="Failed to create and retrieve {safe_role_name} access token.")
        
    return accessTokenOut(**created_token)
"""
        # Function to activate the token for the role
        activate_func = f"""
async def activate_{safe_role_name}_access_token(token_id: str) -> accessTokenOut:
    \"\"\"
    Activates a '{safe_role_name}' access token by setting its status to 'active'.
    \"\"\"
    try:
        obj_id = ObjectId(token_id)
    except errors.InvalidId:
        raise HTTPException(status_code=400, detail="Invalid token ID format.")

    updated_token = await db.accessToken.find_one_and_update(
        filter={{"_id": obj_id, "role": "{safe_role_name}"}},
        update={{"$set": {{'status': 'active'}}}},
        return_document=True # Returns the document after the update
    )
    
    if not updated_token:
        raise HTTPException(status_code=404, detail="{safe_role_name.capitalize()} token not found or role mismatch.")
        
    return accessTokenOut(**updated_token)
    
async def delete_access_token(token_id: str) -> bool:
    \"\"\"
    Deletes an access token by its ID. Returns True if deletion was successful.
    This is role-agnostic and only depends on a valid token ID.
    \"\"\"
    try:
        obj_id = ObjectId(token_id)
        result = await db.accessToken.delete_one({"_id": obj_id})
        return result.deleted_count > 0
    except errors.InvalidId:
        return False
"""
        role_specific_functions.append(add_func)
        role_specific_functions.append(activate_func)

    # --- Final Assembly and File Writing ---
    
    # Combine the main template with the dynamically generated functions
    final_content = file_content + "\n".join(role_specific_functions)
    
    # Define the final file path
    file_path = os.path.join(directory, "tokens_repo.py")
    
    # Write the content to the file
    try:
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(final_content)
        print(f"✅ Successfully generated token repository at: '{file_path}'")
        print(f"   Roles included: {', '.join(roles)}")
    except IOError as e:
        print(f"❌ Error writing to file: {e}")
