##############################################################################################################################
# Fetch data from DB and store in variable
##############################################################################################################################

from pymongo import MongoClient # type: ignore
from dotenv import load_dotenv # type: ignore
import os
from flask import Blueprint, request, jsonify
from bson import ObjectId
from datetime import datetime



load_dotenv()


def get_mongo_client():
    """Create and return a MongoDB client."""
    uri = os.getenv('MONGO_URI')
    if not uri:
        raise ValueError("MongoDB URI not found in .env file")
    return MongoClient(uri,serverSelectionTimeoutMS=100000) # 50 seconds timeout

client = get_mongo_client()

def fetch_sections(client):
    """Fetch all documents from the 'sections' collection."""
    db = client['narrative_design_test']
    collection = db['sections']
    return list(collection.find())

def fetch_triggers(client):
    """Fetch all documents from the 'triggers' collection."""
    db = client['narrative_design_test']
    collection = db['triggers']
    return list(collection.find())

def fetch_mannual_triggers(client):
    """Fetch all documents from the 'mannual_triggers' collection."""
    db = client['narrative_design_test']
    collection = db['mannual_triggers']
    return list(collection.find())

def fetch_characters(client):
    """Fetch all documents from the 'character_descriptions' collection."""
    db = client['character_test']
    collection = db['character_descriptions']
    return list(collection.find())

def get_characters_by_client_id(client_id):
    """
    Fetch all characters belonging to a specific client, as well as public characters (no client_id).
    """
    db = client['character_test']
    collection = db['character_descriptions']
    # Fetch characters for the client or public (no client_id or empty client_id)
    characters = list(collection.find({
        "$or": [
            {"client_id": client_id},
            {"client_id": {"$exists": False}},
            {"client_id": ""}
        ]
    }))
    # Remove MongoDB _id from each character
    for character in characters:
        character.pop('_id', None)
    return characters

def get_character_by_id(character_id, client_id=None):
    db_2 = client['character_test']
    character_collection = db_2['character_descriptions']
    
    # Build query based on whether client_id is provided
    query = {"character_id": character_id}
    if client_id:
        query["client_id"] = client_id
    
    # Fetch the character by character_id and optionally client_id
    character = character_collection.find_one(query)
    
    if not character:
        if client_id:
            raise ValueError(f"No character found with character_id: {character_id} for client_id: {client_id}")
        else:
            raise ValueError(f"No character found with character_id: {character_id}")
    
    # Return all fields for the character
    character.pop('_id', None)  # Remove MongoDB _id
    return character

def update_character_by_id(character_id, update_data):
    """
    Updates a character's details in the database.
    
    :param character_id: The unique ID of the character to update.
    :param update_data: A dictionary with the fields to update.
    :return: Success message or error.
    """
    db_2 = client['character_test']
    character_collection = db_2['character_descriptions']
    
    # Update the character document
    result = character_collection.update_one(
        {"character_id": character_id},  # Match condition
        {"$set": update_data}  # Fields to update
    )

    if result.matched_count == 0:
        return f"No character found with character_id: {character_id}"
    
    return f"Character with character_id {character_id} updated successfully."

def update_token_usage(client, character_id, token_usage):
    """
    Updates the token usage for a character in the database.
    
    Args:
        client: The MongoDB client.
        character_id: The ID of the character to update.
        token_usage: The token usage to set.
    
    Returns:
        A success message or an error message if the character is not found.
    """
    db = client['character_test']
    collection = db['character_descriptions']
    
    result = collection.find_one({"character_id": character_id})
    client_id= result.get("client_id")
    collection = db["clients"]
    data= collection.update_one(
        {"client_id": client_id},
        {"$set": {"token_used": token_usage}})
    if data.modified_count == 0:
        return f"No client found with character_id: {character_id}"
    return f"Token usage for character_id {character_id} updated successfully."

def update_selected_character_id(client_id, character_id):
    """
    Updates the selected_character_id for a client in the database.
    :param client_id: The unique ID of the client to update.
    :param character_id: The character ID to set as selected.
    :return: Success message or error.
    """
    db = client['character_test']
    clients_collection = db['clients']
    result = clients_collection.update_one(
        {"client_id": client_id},
        {"$set": {"selected_character_id": character_id}}
    )
    if result.matched_count == 0:
        return f"No client found with client_id: {client_id}"
    return f"Client {client_id} updated with selected_character_id {character_id}."

def filter_data_by_character_id(character_id, mannual_triggers, triggers, sections):
    """
    Filter the arrays to keep only objects with the given character_id.
    
    Args:
        character_id: The character_id to filter by.
        mannual_triggers: List of mannual triggers.
        triggers: List of triggers.
        sections: List of sections.
    
    Returns:
        A tuple containing the filtered mannual_triggers, triggers, and sections.
    """
    # Filter mannual_triggers
    filtered_mannual_triggers = [trigger for trigger in mannual_triggers if trigger.get("character_id") == character_id]
    
    # Filter triggers
    filtered_triggers = [trigger for trigger in triggers if trigger.get("character_id") == character_id]
    
    # Filter sections
    filtered_sections = [section for section in sections if section.get("character_id") == character_id]
    
    return filtered_mannual_triggers, filtered_triggers, filtered_sections

api_bp = Blueprint('api', __name__, url_prefix='/api')

@api_bp.route('/get-objective', methods=['GET'])
def get_objective():
    section_id = request.args.get('section_id')
    character_id = request.args.get('character_id')
    if not section_id or not character_id:
        return jsonify({'error': 'Missing section ID or character ID'}), 400
    try:
        db = get_mongo_client()['narrative_design_test']
        section = db['sections'].find_one({'id': section_id})
        if not section:
            return jsonify({'error': 'Section not found'}), 404
        return jsonify({'objective': section.get('objective', '')})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@api_bp.route('/get-section', methods=['GET'])
def get_section():
    section_id = request.args.get('section_id')
    character_id = request.args.get('character_id')
    try:
        db = get_mongo_client()['narrative_design_test']
        section = db['sections'].find_one({'id': section_id, 'character_id': character_id})
        if not section:
            return jsonify({'error': 'Section not found'}), 404
        filtered_decisions = [d for d in section.get('decisions', []) if d.get('character_id') == character_id]
        return jsonify({
            'id': section['id'],
            'label': section.get('label', ''),
            'objective': section.get('objective', ''),
            'decisions': filtered_decisions,
            'triggers': section.get('triggers', [])
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@api_bp.route('/get-trigger-message', methods=['GET'])
def get_trigger_message():
    trigger_id = request.args.get('trigger_id')
    character_id = request.args.get('character_id')
    if not trigger_id or not character_id:
        return jsonify({'error': 'Missing required fields'}), 400
    try:
        db = get_mongo_client()['narrative_design_test']
        trigger = db['triggers'].find_one({'trigger_id': trigger_id, 'character_id': character_id})
        if not trigger:
            return jsonify({'message': ''})
        return jsonify({'message': trigger.get('message', '')})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@api_bp.route('/load-nodes', methods=['GET'])
def load_nodes():
    character_id = request.args.get('character_id')
    try:
        db = get_mongo_client()['save-nodes']
        result = db['details'].find_one({'character_id': character_id})
        return jsonify({
            'nodes': result['nodes'] if result and 'nodes' in result else [],
            'edges': result['edges'] if result and 'edges' in result else []
        })
    except Exception as e:
        return jsonify({'error': 'Failed to load nodes', 'details': str(e)}), 500

@api_bp.route('/save-nodes', methods=['POST'])
def save_nodes():
    data = request.get_json()
    character_id = data.get('character_id')
    nodes = data.get('nodes', [])
    edges = data.get('edges', [])
    try:
        db = get_mongo_client()['save-nodes']
        db['details'].update_one(
            {'character_id': character_id},
            {'$set': {'nodes': nodes, 'edges': edges}},
            upsert=True
        )
        return jsonify({'message': 'Nodes and edges saved successfully'})
    except Exception as e:
        return jsonify({'error': 'Failed to save nodes', 'details': str(e)}), 500

@api_bp.route('/update-manual-trigger', methods=['POST'])
def update_manual_trigger():
    data = request.get_json()
    trigger_id = data.get('trigger_id')
    destination_id = data.get('destination_id')
    character_id = data.get('character_id')
    label = data.get('label')
    description = data.get('description', '')
    isActive = data.get('isActive', True)
    if not trigger_id or not destination_id or not character_id or not label:
        return jsonify({'error': 'Missing required fields', 'required': ['trigger_id', 'destination_id', 'character_id', 'label']}), 400
    try:
        db = get_mongo_client()['narrative_design_test']
        manual_trigger_doc = {
            'trigger_id': trigger_id,
            'character_id': character_id,
            'destination_id': destination_id,
            'label': label,
            'description': description,
            'isActive': isActive,
            'updated_at': datetime.utcnow().isoformat()
        }
        db['mannual_triggers'].update_one(
            {'trigger_id': trigger_id, 'character_id': character_id},
            {'$set': manual_trigger_doc, '$setOnInsert': {'created_at': datetime.utcnow().isoformat()}},
            upsert=True
        )
        updated_trigger = db['mannual_triggers'].find_one({'trigger_id': trigger_id, 'character_id': character_id})
        return jsonify({'success': True, 'trigger': updated_trigger})
    except Exception as e:
        return jsonify({'error': 'Internal Server Error', 'details': str(e)}), 500

@api_bp.route('/update-section', methods=['POST'])
def update_section():
    data = request.get_json()
    id = data.get('id')
    character_id = data.get('character_id')
    label = data.get('label')
    objective = data.get('objective')
    decisions = data.get('decisions', [])
    trigger = data.get('trigger', [])
    if not id:
        return jsonify({'error': 'Missing section ID'}), 400
    try:
        db = get_mongo_client()['narrative_design_test']
        section_doc = {
            'id': id,
            'character_id': character_id,
            'label': label,
            'objective': objective,
            'decisions': [{**d, 'character_id': character_id} for d in decisions],
            'triggers': trigger,
            'updatedAt': datetime.utcnow()
        }
        db['sections'].update_one(
            {'id': id},
            {'$set': section_doc},
            upsert=True
        )
        return jsonify({'success': True, 'message': 'Section updated successfully'})
    except Exception as e:
        return jsonify({'error': 'Internal Server Error', 'details': str(e)}), 500

@api_bp.route('/update-trigger', methods=['POST'])
def update_trigger():
    data = request.get_json()
    trigger_id = data.get('trigger_id')
    message = data.get('message')
    destination_id = data.get('destination_id')
    type_ = data.get('type')
    character_id = data.get('character_id')
    if not trigger_id or not destination_id:
        return jsonify({'error': 'Missing required fields'}), 400
    try:
        db = client['narrative_design_test']
        db['triggers'].update_one(
            {'trigger_id': trigger_id},
            {'$set': {
                'message': message,
                'destination_id': destination_id,
                'type': type_,
                'character_id': character_id,
                'updated_at': datetime.utcnow()
            }},
            upsert=True
        )
        return jsonify({'success': True})
    except Exception as e:
        return jsonify({'error': 'Internal Server Error', 'details': str(e)}), 500

@api_bp.route('/upload', methods=['POST'])
def upload():
    file = request.files.get('file')
    character_id = request.form.get('character_id')
    if not file:
        return jsonify({'error': 'No file uploaded'}), 400
    if not character_id:
        return jsonify({'error': 'Character ID is required'}), 400
    try:
        db = client['Knowledge_bank']
        collection = db['upload']
        file_data = {
            'filename': file.filename,
            'contentType': file.content_type,
            'size': len(file.read()),
            'uploadDate': datetime.utcnow(),
            'character_id': character_id
        }
        file.stream.seek(0)
        # Optionally, save file content to GridFS or elsewhere if needed
        result = collection.insert_one(file_data)
        return jsonify({'message': 'File uploaded successfully', 'result': str(result.inserted_id)})
    except Exception as e:
        return jsonify({'error': 'Error uploading file', 'details': str(e)}), 500
