import sys
from urllib.parse import quote_plus

from flask import Flask, jsonify, render_template, request
from flask_babel import Babel, _
from flask_wtf.csrf import CSRFProtect
from sqlalchemy import create_engine, inspect, text

from config import Config
from DocumentManager.DocumentsManager import DocumentManager
from extensions import db, mail, migrate, socketio
from routes.admin import admin_bp
from routes.chatbot import chatbot_bp
from routes.copilot_app_api import copilot_app_api_bp
from routes.debug_route import debug_route_bp
from routes.factory_dashboard_api import factory_dashboard_api_bp
from routes.factory_panel_app.factory_panel_app import factory_panel_app_bp
from routes.factory_struct_api import factory_struct_api_bp
from routes.graphing_api import graphing_api_bp
from routes.incident_api import incident_api_bp
from routes.language import language_bp
from routes.login_register import login_register_bp
from routes.slots import slots_bp
from routes.voice_chat import voice_chat_bp
from routes.web import web_bp
from services.auth_service import is_admin, is_logged_in
from services.chat_service import client
from services.locale_service import get_locale, get_text_direction


HOST = True


def create_database_if_not_exists():
    """Create the PostgreSQL database itself when running outside the hosted environment.

    Flask-Migrate manages the schema inside the database. This function only creates
    the empty database when it does not exist yet.
    """
    user = Config.user
    password = quote_plus(Config.password)
    host = Config.host
    port = Config.port
    db_name = Config.db

    url_without_db = f"postgresql+psycopg2://{user}:{password}@{host}:{port}/postgres"
    engine = create_engine(url_without_db, isolation_level="AUTOCOMMIT")

    try:
        with engine.connect() as conn:
            exists = conn.execute(
                text("SELECT 1 FROM pg_database WHERE datname=:dbname"),
                {"dbname": db_name},
            ).scalar()

            if not exists:
                conn.execute(text(f'CREATE DATABASE "{db_name}" ENCODING "UTF8" TEMPLATE template1'))
                print(f"Database '{db_name}' created.")
            else:
                print(f"Database '{db_name}' already exists.")
    finally:
        engine.dispose()


def import_models():
    """Import every SQLAlchemy model so Alembic can discover its metadata.

    Add new model imports here whenever a new model module is created.
    """
    from models.ai_model import (  # noqa: F401
        AITaskModelConfig,
        ModelTokenUsage,
        OpenAIModel,
        PromptFile,
        PromptType,
        SystemPrompt,
    )
    from models.chats import ChatMessage  # noqa: F401
    from models.dashboard_model import Dashboard, DashboardWidget  # noqa: F401
    from models.doc_manager import DocConfig  # noqa: F401
    from models.user import User  # noqa: F401
    from models.factory_structure_model import (  # noqa: F401
        MachineMetaSchema,
        Machine,
        Parameter,
        ParameterValue
    )
    from models.incident_model import IncidentModel # noqa: F401
    
def _is_flask_db_command():
    """Return True while a ``flask db ...`` command is loading the app factory."""
    return "db" in sys.argv[1:]


def progress_callback_doc_manager(info):
    print(info)

    total = info.get("total", 0)
    completed = info.get("completed", 0)
    percentage = round((completed / total) * 100, 1) if total else 0

    print("percentage : ", percentage)
    socketio.emit(
        "upload_progress",
        {
            "progress": percentage,
            "message": (
                info.get("title", "در حال آپلود...")
                if percentage < 100
                else "آپلود تکمیل شد!"
            ),
            "filename": info.get("filename", ""),
        },
    )


def initialize_document_manager(app):
    """Initialize application runtime services that should not run during migrations."""
    from models.doc_manager import DocConfig

    manager_args = {"client": client}

    with app.app_context():
        # This lets the app factory load before the initial migration has run.
        if inspect(db.engine).has_table(DocConfig.__tablename__):
            doc_config = db.session.get(DocConfig, 1)
        else:
            doc_config = None

        if doc_config:
            manager_args.update(
                {
                    "cache_lifetime": int(doc_config.cache_lifetime),
                    "chunk_size": int(doc_config.chunk_size),
                    "chunk_overlap": int(doc_config.chunk_overlap),
                    "batch_size": int(doc_config.batch_size),
                }
            )
        else:
            print("WARNING: DocConfig not found. Using default settings")

        app.doc_manager = DocumentManager(
            "upload_files",
            **manager_args,
            progress_callback=progress_callback_doc_manager,
        )
        app.doc_manager.scan_directory()


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    app.config["TEMPLATES_AUTO_RELOAD"] = True
    app.config["WTF_CSRF_TIME_LIMIT"] = None

    if not HOST:
        create_database_if_not_exists()

    CSRFProtect(app)
    mail.init_app(app)

    babel = Babel()
    babel.init_app(app, locale_selector=get_locale)

    app.jinja_env.globals["get_locale"] = get_locale
    app.jinja_env.globals["get_text_direction"] = get_text_direction

    db.init_app(app)

    # Alembic reads db.metadata after the app factory is loaded. Importing every
    # model here ensures ``flask db migrate`` can detect all schema changes.
    import_models()
    migrate.init_app(app, db)

    socketio.init_app(app)

    app.register_blueprint(login_register_bp)
    app.register_blueprint(language_bp)
    app.register_blueprint(web_bp)
    app.register_blueprint(slots_bp)
    app.register_blueprint(chatbot_bp)
    app.register_blueprint(voice_chat_bp)
    app.register_blueprint(debug_route_bp)
    app.register_blueprint(admin_bp)
    app.register_blueprint(factory_panel_app_bp)
    app.register_blueprint(factory_struct_api_bp)
    app.register_blueprint(incident_api_bp)
    app.register_blueprint(copilot_app_api_bp)
    app.register_blueprint(factory_dashboard_api_bp)
    app.register_blueprint(graphing_api_bp)

    @app.errorhandler(404)
    def not_found(error):
        if request.accept_mimetypes.best == "application/json":
            return (
                jsonify(
                    {
                        "error": _("صفحه موردنظر یافت نشد"),
                        "status": 404,
                        "path": request.path,
                    }
                ),
                404,
            )
        return render_template("404.html"), 404

    @app.context_processor
    def inject_user_status():
        return {"logged_in": is_logged_in(), "is_admin": is_admin()}

    @app.errorhandler(403)
    def forbidden(error):
        return render_template("403.html"), 403

    # Do not query application tables or scan files while Alembic is loading
    # the app for ``flask db init/migrate/upgrade/downgrade``.
    if not _is_flask_db_command():
        # Check if DB exists before trying to initialize DocumentManager
        with app.app_context():
            try:
                # This just tries to connect to the DB
                db.engine.connect().close()
                # If success, then initialize
                initialize_document_manager(app)
            except Exception:
                print("Database not initialized yet. Skipping DocumentManager init.")

    return app

if __name__ == "__main__":
    app = create_app()
    socketio.run(app, host="0.0.0.0", port=5000, debug=True)
