# backend/setup.py
import logging
import os
import warnings
from typing import Optional

import pydantic.warnings
from flask import Flask, jsonify
from pydantic import ValidationError

from backend.database.user_store import UserStore
from backend.fetch_api import fetch_api
from backend.routes import register_routes
from backend.services.admin_config_service import setup_admin_config
from backend.socket import socketio
from backend.utils import metrics, sqlalchemy_tracing
from backend.utils.logging_utils import configure_logging, get_logger
from backend.utils.ws_utils import before_request as auth_before_request
from backend.utils.ws_utils import setup_socketio

logger = get_logger(__name__)

# may be usefull to keep
os.environ["LANGCHAIN_TRACING_V2"] = "false"


def my_warning_handler(message, category, filename, lineno, file=None, line=None):
    if issubclass(category, pydantic.warnings.PydanticDeprecatedSince20):
        return  # ignore this warning
    import sys

    sys.stderr.write(warnings.formatwarning(message, category, filename, lineno, line))


warnings.showwarning = my_warning_handler


class NoParsingFilter(logging.Filter):
    def filter(self, record):
        message = record.getMessage()
        return not message.endswith("AssertionError: write() before start_response")


def setup_app(
    app: Flask, db_url: str, workload_folder_path: Optional[str] = None, dry=False, app_mode: str = "agenthub"
) -> None:
    """
    The function will be called without any arguments. If it returns
    a non-``None`` value, the value is handled as if it was the
    return value from the view, and further request handling is
    stopped.
    :param app: The Flask application to configure.
    :param db_url: Database connection URL used by the backend.
    :param workload_folder_path: Path to the workload folder used by the app.
    :param dry: If True, perform a dry-run setup without side effects
        where supported.
    :return: None.
    """
    os.environ["APP_MODE"] = app_mode
    app.register_blueprint(blueprint=fetch_api)
    app.register_blueprint(blueprint=register_routes(mode=app_mode))

    app.before_request(auth_before_request)
    logger.info("Setting up app with workload_folder_path=%s", workload_folder_path)

    # Register Pydantic v2 validation error handler
    @app.errorhandler(ValidationError)
    def handle_pydantic_validation_error(e: ValidationError):
        errors = [
            {
                "field": ".".join(str(loc) for loc in err["loc"]),
                "message": err["msg"],
                "type": err["type"],
                "input": err.get("input"),
            }
            for err in e.errors()
        ]
        logger.error(f"Pydantic validation error: {errors}")
        return jsonify({"error": "Validation Error", "details": "Error occurred when validating data"}), 500

    if not (db_url):
        raise ValueError("Database URL is required.")
    logger.info(f"DB URL path: {db_url}")

    from backend.database import db, migrate

    app.config["SQLALCHEMY_DATABASE_URI"] = db_url
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

    sqlalchemy_opts = {"pool_size": os.environ.get("AGENT_HUB_SQLALCHEMYPOOL_SIZE", 30)}

    if "snowflake://" in db_url and "authenticator=oauth" in db_url:
        # Check if using Snowflake OAuth (token may expire)
        # For Snowflake OAuth: use custom creator to get fresh token on each new connection

        from backend.app_paths import get_db_url

        def get_fresh_snowflake_connection():
            """Create a new Snowflake connection with fresh OAuth token."""
            from sqlalchemy import create_engine

            fresh_url = get_db_url()  # Gets fresh token via get_oauth2_credential()
            temp_engine = create_engine(fresh_url)
            return temp_engine.raw_connection()

        sqlalchemy_opts["creator"] = get_fresh_snowflake_connection
        sqlalchemy_opts["pool_pre_ping"] = True  # Detect stale connections
        sqlalchemy_opts["pool_recycle"] = 1800  #  Force recycle every 30 min
        logger.info("Snowflake OAuth: configured with fresh token creator")
    elif "sqlite://" in db_url:
        # No pre-ping needed for SQLite since it's in-process and doesn't have stale connection issues
        pass
    else:
        # For other databases, enable pool_pre_ping to detect and refresh stale connections
        sqlalchemy_opts["pool_pre_ping"] = True

    app.config["SQLALCHEMY_ENGINE_OPTIONS"] = sqlalchemy_opts

    app.config["STORE"] = UserStore("", [])
    db.init_app(app)

    # Install tracing of long SQL queries
    with app.app_context():
        sqlalchemy_tracing.install_sql_trace(db.engine)

    # DEBUG ONLY: Install DB pool instrumentation to log connection checkout/checkin and detect
    # what holds connections and for how long, to help identify leaks or long-running transactions.
    # with app.app_context():
    #    install_db_pool_instrumentation(db)

    # Install periodic logging of all metrics
    metrics.start_metric_reporter()

    migrate.init_app(app, db)
    configure_logging()
    logging.getLogger("werkzeug").addFilter(NoParsingFilter())
    socketio.init_app(app)
    setup_socketio(socketio)
    if not dry:
        from backend.indexing_monitor import start_monitor, wake_monitor

        with app.app_context():
            setup_admin_config(workload_folder_path, app_mode)
        start_monitor(app, interval=5)
        wake_monitor()

        if app_mode == "agenthub":
            from backend.services.publishing_service import PublishingService

            with app.app_context():
                publishing_service = PublishingService(app=app)
                app.config["PUBLISHING_SERVICE"] = publishing_service
                publishing_service.resume_publishing_jobs()
