from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Request, Form
import json
from datetime import datetime
from typing import List, Optional, Dict, Union
import os
from pydantic import ValidationError, SecretStr
from PIL import Image
import requests
from io import BytesIO
from cryptography.fernet import Fernet
from dotenv import load_dotenv

from app.db import database
from app.v1.libraries.object import str_to_objectid, object_to_str_array
from ...models.saas.appmodel import App, AppList, AppBase, AppCategory, AppIntegration,AppIntegrationUpdate, Category, AppLog, AppLogBase, Integrations
#from ..models.appsmodel import App, AppBase, AppCategory, AppIntegration, Category, AppLog, AppLogBase, Integrations
from ...libraries.crypto import encrypt_data, decrypt_data 
from app.v1.libraries.jsonloader import load_api_schema_as_json

load_dotenv()

APP_COLLECTION = "app"
APP_CATEGORY_COLLECTION = "apptypes"
APP_INTEGRATION_COLLECTION = "app_users"
APP_LOG_COLLECTION = "app_logs"
FLOWS_COLLECTION = "flows"

router = APIRouter()

@router.get("/categories", response_model=List[Category])
def list_categories(db: database.MongoDB = Depends(database.get_mongo_db)):
    categories = db[APP_CATEGORY_COLLECTION].find({}, {"_id": 1, "category": 1})
    return [{"id": str(cat["_id"]), "name": cat["category"]} for cat in categories]

@router.get("/", response_model=Dict[str, Union[List[AppList], int]])
def list_apps(
    skip: int = 0, 
    limit: int = 10, 
    q: Optional[str] = None,
    project_id: Optional[str] = None,
    account_id: Optional[str] = None,
    db: database.MongoDB = Depends(database.get_mongo_db)
):
    query = {}

    if project_id:
        query["project_id"] = project_id
    if account_id:
        query["account_id"] = account_id
    if q:
        query["$or"] = [{"name": {"$regex": q, "$options": "i"}}]
        
        category = db[APP_CATEGORY_COLLECTION].find_one({"category": q})
        if category:
            query["$or"].append({"category_id": str(category["_id"])})

    apps_count = db[APP_COLLECTION].count_documents(query)
    apps = list(db[APP_COLLECTION].find(query).sort("created_date", 1).skip(skip).limit(limit))
    # Assuming object_to_str_array modifies each dictionary in a list
    apps = object_to_str_array(apps)

    # Now, iterate over each app in the list to add 'app_id' from '_id'
    for app in apps:
        app["app_id"] = str(app["_id"])

    return {"apps": apps, "total_count": apps_count}



@router.get("/{app_id}", response_model=App)
async def retrieve_app(app_id: str, db: database.MongoDB = Depends(database.get_mongo_db)):
    app = db[APP_COLLECTION].find_one({"_id": str_to_objectid(app_id)})
    if app is None:
        raise HTTPException(status_code=404, detail="App not found")

    api_schema = await load_api_schema_as_json(app)
    if not api_schema:
        raise HTTPException(status_code=500, detail="Failed to load App")
    components = api_schema['components']
    #print("Components ", components)
    app['api_schema'] = json.dumps(components)  # Serialize to JSON string

    # Decrypt sensitive data like API keys or OAuth tokens
    if 'auth_config' in app and app['auth_config']:
        if 'api_key' in app['auth_config']:
            app['auth_config']['api_key'] = decrypt_data(app['auth_config']['api_key'])
        if 'oauth' in app['auth_config']:
            app['auth_config']['oauth'] = {key: decrypt_data(value) for key, value in app['auth_config']['oauth'].items()}

    app["_id"] = str(app["_id"])

    return app


@router.get("/a/{app_id}", response_model=App)
async def retrieve_admin_app(app_id: str, db: database.MongoDB = Depends(database.get_mongo_db)):
    app = db[APP_COLLECTION].find_one({"_id": str_to_objectid(app_id)})
    if app is None:
        raise HTTPException(status_code=404, detail="App not found")

    app["_id"] = str(app["_id"])
    return app


@router.post("/", response_model=App)
async def add_app(app: str = Form(...), db: database.MongoDB = Depends(database.get_mongo_db)):
    
    # Parse the app data from the JSON string
    try:
        raw_app_data = json.loads(app)
    except json.JSONDecodeError:
        raise HTTPException(status_code=400, detail="Invalid app JSON format")

    # Validate the parsed app data with your Pydantic model
    try:
        app_validated = AppBase(**raw_app_data)
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=str(e))

    #print(raw_app_data)

    # Add the current datetime to the app data before insertion
    final_app_data = app_validated.dict()
    final_app_data["created_date"] = datetime.utcnow()

    if final_app_data.get('auth_config'):
        if final_app_data['auth_config'].get('api_key'):
            final_app_data['auth_config']['api_key'] = encrypt_data(final_app_data['auth_config']['api_key'])
        if final_app_data['auth_config'].get('oauth'):
            final_app_data['auth_config']['oauth'] = {key: encrypt_data(value) for key, value in final_app_data['auth_config']['oauth'].items()}

    new_app = db[APP_COLLECTION].insert_one(final_app_data)
    created_app = {**final_app_data, "_id": str(new_app.inserted_id)}
    new_app_id = str(new_app.inserted_id)

    # Create a directory to store the icons if it doesn't exist
    os.makedirs("./public/appicons", exist_ok=True)

    icon_url = app_validated.icon

    await create_app_icon(new_app_id,icon_url)

    return created_app


@router.put("/{app_id}", response_model=App)
async def update_app(app_id: str, request: Request, db: database.MongoDB = Depends(database.get_mongo_db)):
    # Read the form data
    form_data = await request.form()
    app_data_json = form_data.get("app")

    # Parse JSON string to Python dict
    try:
        app_data_dict = json.loads(app_data_json)
    except json.JSONDecodeError:
        raise HTTPException(status_code=400, detail="Invalid app JSON format")

    # Validate app data with Pydantic model
    try:
        app_data = AppBase(**app_data_dict)
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=str(e))

    # Your existing logic for updating the app
    existing_app = db[APP_COLLECTION].find_one({"_id": str_to_objectid(app_id)})
    if existing_app is None:
        raise HTTPException(status_code=404, detail="App not found")

    updated_data = app_data.dict(exclude_unset=True)
    result = db[APP_COLLECTION].update_one({"_id": str_to_objectid(app_id)}, {"$set": updated_data})

    if result.matched_count == 0:
        raise HTTPException(status_code=404, detail="App not found")

    updated_app = db[APP_COLLECTION].find_one({"_id": str_to_objectid(app_id)})
    if updated_app:
        updated_app["_id"] = str(updated_app["_id"])
    
    if existing_app["icon"] != updated_app["icon"]:
        icon_url = updated_app["icon"]
        await create_app_icon(app_id,icon_url)

    return updated_app

async def create_app_icon(app_id: str, icon_url: str):
    icon_path = f"./public/appicons/{app_id}.jpg"
    small_icon_path = f"./public/appicons/{app_id}_small.jpg"

    response = requests.get(icon_url)
    if response.status_code != 200:
        print("Image URL is not valid")
        return

    image_data = BytesIO(response.content)
    with Image.open(image_data) as im:
        # Ensure the image is in a format that supports transparency ('RGBA', 'LA', or 'P' with transparency)
        if im.mode == 'RGBA' or im.mode == 'LA' or (im.mode == 'P' and 'transparency' in im.info):
            # Attempt to extract the alpha channel if present
            try:
                alpha = im.split()[3]
            except IndexError:
                # If there's no alpha channel, convert directly to 'RGB'
                im = im.convert('RGB')
            else:
                # If there's an alpha channel, paste using it as the mask
                background = Image.new('RGB', im.size, (255, 255, 255))
                background.paste(im, mask=alpha)
                im = background
        else:
            # If the image does not support transparency, convert to 'RGB'
            im = im.convert('RGB')

        # Proceed with resizing and saving as before
        im_large = im.resize((250, 250))
        im_large.save(icon_path, "JPEG")

        im_small = im.resize((48, 48))
        im_small.save(small_icon_path, "JPEG")

# Integrations Section
# This will be used whenever app is being integrated into a Project. 

@router.post("/subscribe", response_model=AppIntegration)
async def subscribe_to_app(app_integration: AppIntegration, db: database.MongoDB = Depends(database.get_mongo_db)):
    # Convert Pydantic model to a dictionary
    new_integration_data = app_integration.dict()

    # Convert SecretStr fields to regular strings
    for key, value in new_integration_data.get('auth_config', {}).get('credentials', {}).items():
        if isinstance(value, SecretStr):
            encrypted_value = encrypt_data(value.get_secret_value())
            new_integration_data['auth_config']['credentials'][key] = encrypted_value #value.get_secret_value()

    # Insert into MongoDB
    try:
        new_integration = db[APP_INTEGRATION_COLLECTION].insert_one(new_integration_data)
        created_integration = {**new_integration_data, "integration_id": str(new_integration.inserted_id)}
        
        # Update the APP_COLLECTION to increment integrations count
        db[APP_COLLECTION].update_one(
            {"_id": str_to_objectid(created_integration["app_id"])},
            {"$inc": {"integrations": 1}}
        )

        return created_integration
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database insertion error: {str(e)}")

@router.get("/endpoints/{integration_id}")
async def retrieve_endpoints(integration_id: str, db: database.MongoDB = Depends(database.get_mongo_db)):
    integration = db[APP_INTEGRATION_COLLECTION].find_one({"_id": str_to_objectid(integration_id)})
    app = db[APP_COLLECTION].find_one({"_id":str_to_objectid(integration['app_id'])})

    api_schema = await load_api_schema_as_json(app)
    if not api_schema:
        raise HTTPException(status_code=500, detail="Failed to load App")
    #print("Components ", components)
    app['api_schema'] = json.dumps(api_schema)  # Serialize to JSON string

    return app['api_schema']


@router.get("/integrations/{integration_id}", response_model=AppIntegration)
def retrieve_integration(integration_id: str, db: database.MongoDB = Depends(database.get_mongo_db)):
    integration = db[APP_INTEGRATION_COLLECTION].find_one({"_id": str_to_objectid(integration_id)})
    if integration is None:
        raise HTTPException(status_code=404, detail="Integration not found")
    integration["_id"] = str(integration["_id"])
    return integration


@router.get("/integrations/list/{account_id}/", response_model=Dict[str, Union[List[Integrations], int]])
def list_app_integrations(
    skip: int = 0,
    limit: int = 10,
    q: Optional[str] = None,
    app_id: Optional[str] = None,
    account_id: Optional[str] = None,
    db: database.MongoDB = Depends(database.get_mongo_db)
):

    query = {}

    if app_id:
        query["app_id"] = app_id
    if account_id:
        query["account_id"] = account_id
    if q:
        # The below line assumes you are searching by integration name. You can modify it to search by other fields if needed.
        query["name"] = {"$regex": q, "$options": "i"}

    # Ensure the keys in the query are correct and handle any other query logic.

    integrations_count = db[APP_INTEGRATION_COLLECTION].count_documents(query)
    integrations_cursor = db[APP_INTEGRATION_COLLECTION].find(query).skip(skip).limit(limit)

    # Convert cursor to list and iterate over it to modify each document
    integrations_list = list(integrations_cursor)
    for integration in integrations_list:
        integration["integration_id"] = str(integration["_id"])

    integrations = object_to_str_array(list(integrations_list))

    return {"integrations": integrations, "total_count": integrations_count}

@router.put("/subscribe/{integration_id}", response_model=AppIntegration)
def update_integration(integration_id: str, integration_data: AppIntegrationUpdate, db: database.MongoDB = Depends(database.get_mongo_db)):
    existing_integration = db[APP_INTEGRATION_COLLECTION].find_one({"_id": str_to_objectid(integration_id)})
    if existing_integration is None:
        raise HTTPException(status_code=404, detail="Integration not found")

    # Copy existing data to updated_data
    updated_data = existing_integration.copy()

    for key, value in integration_data.dict(exclude_unset=True).items():
        if key == "auth_config":
            # Check if 'credentials' is provided and not empty
            if "credentials" in value and value["credentials"]:
                updated_auth_config = {}
                for k, v in value["credentials"].items():
                    if isinstance(v, SecretStr):
                        # Encrypt the value if it's a SecretStr instance
                        encrypted_value = encrypt_data(v.get_secret_value())
                        updated_auth_config[k] = encrypted_value
                    else:
                        # Copy the value as is for non-SecretStr instances
                        updated_auth_config[k] = v
                # Update the 'credentials' in 'auth_config' with encrypted values
                if not updated_data.get("auth_config"):
                    updated_data["auth_config"] = {"credentials": {}}
                updated_data["auth_config"]["credentials"].update(updated_auth_config)
            # If 'credentials' is provided but empty, skip updating it
            elif "credentials" in value and not value["credentials"]:
                continue
            else:
                # Update other parts of auth_config if present
                updated_data["auth_config"] = value  
        elif key == 'configurations':
            updated_data['configurations'] = value
        else:
            updated_data[key] = value

    result = db[APP_INTEGRATION_COLLECTION].update_one({"_id": str_to_objectid(integration_id)}, {"$set": updated_data})

    if result.matched_count == 0:
        raise HTTPException(status_code=404, detail="Integration not found")

    updated_integration = db[APP_INTEGRATION_COLLECTION].find_one({"_id": str_to_objectid(integration_id)})
    if updated_integration:
        updated_integration["_id"] = str(updated_integration["_id"])
        return updated_integration

    raise HTTPException(status_code=404, detail="Integration not found after update")
    
    return
    for key, value in integration_data.dict(exclude_unset=True).items():
        if key == "auth_config":
            # Check if 'credentials' is provided and not empty
            if "credentials" in value and value["credentials"]:
                updated_auth_config = {k: v.get_secret_value() if isinstance(v, SecretStr) else v for k, v in value["credentials"].items()}
                updated_data["auth_config"]["credentials"].update(updated_auth_config)
            # If 'credentials' is provided but empty, skip updating it
            elif "credentials" in value and not value["credentials"]:
                continue
            else:
                updated_data["auth_config"] = value  # Update other parts of auth_config if present
        elif key == 'configurations':
            updated_data['configurations'] = value
        else:
            updated_data[key] = value


@router.delete("/integrations/remove/{account_id}/{integration_id}")
async def delete_integration(account_id: str, integration_id: str, db: database.MongoDB = Depends(database.get_mongo_db)):
    # Check if the integration belongs to the given account_id
    existing_integration = db[APP_INTEGRATION_COLLECTION].find_one({
        "_id": str_to_objectid(integration_id),
        "account_id": account_id
    })
    if existing_integration is None:
        raise HTTPException(status_code=404, detail="Integration not found for the given account")

    # Perform the deletion of the integration
    result = db[APP_INTEGRATION_COLLECTION].delete_one({"_id": str_to_objectid(integration_id)})
    if result.deleted_count == 0:
        raise HTTPException(status_code=404, detail="Integration not found")

    # Fetch the IDs of flows that match the criteria
    flow_ids = db[FLOWS_COLLECTION].find(
        {"app_integration_id": integration_id},
        {"_id": 1}
    )
    flow_ids_list = [str(flow["_id"]) for flow in flow_ids]

    print("flow_ids ", flow_ids_list)

    # Update flows in the FLOWS_COLLECTION
    flows_update_result = db[FLOWS_COLLECTION].update_many(
        {"_id": {"$in": [str_to_objectid(id) for id in flow_ids_list]}},
        {"$set": {"status": False, "app_integration_id": ''}}
    )

    # Update project_flows in the project_flows collection
    project_flows_update_result = db["project_flows"].update_many(
        {"flow_id": {"$in": flow_ids_list}},
        {"$set": {"status": False}}
    )

    print( "message", "Integration deleted successfully", "flows_updated", flows_update_result.modified_count,"project_flows_updated", project_flows_update_result.modified_count )

    return {
        "message": "Integration deleted successfully",
        "flows_updated": flows_update_result.modified_count,
        "project_flows_updated": project_flows_update_result.modified_count
    }
    
# LOGS Section
# This will be used whenever there is an integration is being called to log the data. 
@router.post("/logs", response_model=AppLog)
def add_app_log(log_data: AppLogBase, db: database.MongoDB = Depends(database.get_mongo_db)):
    new_log_data = log_data.dict()
    new_log = db[APP_LOG_COLLECTION].insert_one(new_log_data)
    created_log = {**new_log_data, "_id": str(new_log.inserted_id)}
    return created_log

@router.get("/logs", response_model=List[AppLog])
def list_app_logs(app_id: Optional[str] = None, project_id: Optional[str] = None, limit: int = 10, db: database.MongoDB = Depends(database.get_mongo_db)):
    query = {}
    if app_id:
        query["app_id"] = app_id
    if project_id:
        query["project_id"] = project_id
    logs = list(db[APP_LOG_COLLECTION].find(query).limit(limit))
    for log in logs:
        log["_id"] = str(log["_id"])
    return logs
