# import logging - replaced with loguru
from pathlib import Path
from importlib import resources as importlib_resources

from flask import (
    Flask,
    jsonify,
    make_response,
    request,
    send_from_directory,
)
from flask_wtf.csrf import CSRFProtect
from loguru import logger
from local_deep_research.settings.logger import log_settings

from ..utilities.log_utils import InterceptHandler

# Removed DB_PATH import - using per-user databases now
from .services.socket_service import SocketIOService


def create_app():
    """
    Create and configure the Flask application.

    Returns:
        tuple: (app, socketio) - The configured Flask app and SocketIO instance
    """
    # Set Werkzeug logger to WARNING level to suppress Socket.IO polling logs
    import logging

    logging.getLogger("werkzeug").setLevel(logging.WARNING)
    logging.getLogger("werkzeug").addHandler(InterceptHandler())

    try:
        # Get directories based on package installation
        PACKAGE_DIR = importlib_resources.files("local_deep_research") / "web"
        with importlib_resources.as_file(PACKAGE_DIR) as package_dir:
            STATIC_DIR = (package_dir / "static").as_posix()
            TEMPLATE_DIR = (package_dir / "templates").as_posix()

        # Initialize Flask app with package directories
        app = Flask(
            __name__, static_folder=STATIC_DIR, template_folder=TEMPLATE_DIR
        )
        logger.debug(f"Using package static path: {STATIC_DIR}")
        logger.debug(f"Using package template path: {TEMPLATE_DIR}")
    except Exception:
        # Fallback for development
        logger.exception("Package directories not found, using fallback paths")
        app = Flask(
            __name__,
            static_folder=str(Path("static").resolve()),
            template_folder=str(Path("templates").resolve()),
        )

    # App configuration
    # Generate or load a unique SECRET_KEY per installation
    import secrets
    from ..config.paths import get_data_directory

    secret_key_file = Path(get_data_directory()) / ".secret_key"
    if secret_key_file.exists():
        try:
            with open(secret_key_file, "r") as f:
                app.config["SECRET_KEY"] = f.read().strip()
        except Exception as e:
            logger.warning(f"Could not read secret key file: {e}")
            app.config["SECRET_KEY"] = secrets.token_hex(32)
    else:
        # Generate a new key on first run
        new_key = secrets.token_hex(32)
        try:
            secret_key_file.parent.mkdir(parents=True, exist_ok=True)
            with open(secret_key_file, "w") as f:
                f.write(new_key)
            secret_key_file.chmod(0o600)  # Secure file permissions
            app.config["SECRET_KEY"] = new_key
            logger.info("Generated new SECRET_KEY for this installation")
        except Exception as e:
            logger.warning(f"Could not save secret key file: {e}")
            app.config["SECRET_KEY"] = new_key
    app.config["SESSION_COOKIE_SECURE"] = False  # Allow HTTP for local testing
    app.config["SESSION_COOKIE_HTTPONLY"] = True
    app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
    app.config["PERMANENT_SESSION_LIFETIME"] = 7200  # 2 hours in seconds
    app.config["PREFERRED_URL_SCHEME"] = "https"

    # Initialize CSRF protection
    CSRFProtect(app)
    # Exempt Socket.IO from CSRF protection
    # Note: Flask-SocketIO handles CSRF internally, so we don't need to exempt specific views

    # Disable CSRF for API routes
    @app.before_request
    def disable_csrf_for_api():
        if (
            request.path.startswith("/api/v1/")
            or request.path.startswith("/research/api/")
            or request.path.startswith("/benchmark/api/")
        ):
            # Mark this request as exempt from CSRF
            request.environ["csrf_exempt"] = True

    # Database configuration - Using per-user databases now
    # No shared database configuration needed
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    app.config["SQLALCHEMY_ECHO"] = False

    # Per-user databases are created automatically via encrypted_db.py

    # Log data location and security information
    from ..config.paths import get_data_directory

    data_dir = get_data_directory()
    logger.info("=" * 60)
    logger.info("DATA STORAGE INFORMATION")
    logger.info("=" * 60)
    logger.info(f"Data directory: {data_dir}")
    logger.info(
        "Databases: Per-user encrypted databases in encrypted_databases/"
    )

    # Check if using custom location
    from local_deep_research.settings.manager import SettingsManager

    settings_manager = SettingsManager()
    custom_data_dir = settings_manager.get_setting("bootstrap.data_dir")
    if custom_data_dir:
        logger.info(
            f"Using custom data location via LDR_DATA_DIR: {custom_data_dir}"
        )
    else:
        logger.info("Using default platform-specific data location")

    logger.warning(
        "SECURITY NOTICE: All data including API keys stored in the database are currently unencrypted."
    )
    logger.warning(
        "Please ensure appropriate file system permissions are set on the data directory."
    )
    logger.info(
        "TIP: You can change the data location by setting the LDR_DATA_DIR environment variable."
    )
    logger.info("=" * 60)

    # Register socket service
    socket_service = SocketIOService(app=app)

    # Initialize news subscription scheduler
    try:
        # Always initialize news for now - per-user enabling will be handled in routes
        if True:
            # News tables are now created per-user in their encrypted databases
            logger.info(
                "News tables will be created in per-user encrypted databases"
            )

            # Initialize the new activity-based scheduler
            from ..news.subscription_manager.scheduler import get_news_scheduler
            from ..settings.manager import SettingsManager

            # Get system settings for scheduler configuration
            settings_manager = SettingsManager()

            # Get scheduler instance and initialize with settings
            scheduler = get_news_scheduler()
            scheduler.initialize_with_settings(settings_manager)

            # Check if scheduler is enabled in settings
            if scheduler.config.get("enabled", True):
                scheduler.start()
                app.news_scheduler = scheduler
                logger.info(
                    "News scheduler started with activity-based tracking"
                )
            else:
                app.news_scheduler = scheduler  # Keep instance but don't start
                logger.info(
                    "News scheduler initialized but not started (disabled in settings)"
                )
        else:
            logger.info(
                "News module disabled - subscription scheduler not started"
            )
            app.news_scheduler = None
    except Exception:
        logger.exception("Failed to initialize news scheduler")
        app.news_scheduler = None

    # Apply middleware
    apply_middleware(app)

    # Initialize dogpile cache
    from ..memory_cache.app_integration import setup_dogpile_cache

    setup_dogpile_cache(app)

    # Register blueprints
    register_blueprints(app)

    # Register error handlers
    register_error_handlers(app)

    # Start the queue processor v2 (uses encrypted databases)
    from ..config.queue_config import USE_QUEUE_PROCESSOR

    if USE_QUEUE_PROCESSOR:
        from .queue.processor_v2 import queue_processor

        queue_processor.start()
        logger.info("Started research queue processor v2")
    else:
        logger.info("Queue processor disabled - using direct mode")

    return app, socket_service


def apply_middleware(app):
    """Apply middleware to the Flask app."""

    # Import auth decorators and middleware
    from .auth.cleanup_middleware import cleanup_completed_research
    from .auth.database_middleware import ensure_user_database
    from .auth.decorators import inject_current_user
    from .auth.queue_middleware import process_pending_queue_operations
    from .auth.queue_middleware_v2 import notify_queue_processor
    from .auth.session_cleanup import cleanup_stale_sessions

    # Register authentication middleware
    # First clean up stale sessions
    app.before_request(cleanup_stale_sessions)
    # Then ensure database is open for authenticated users
    app.before_request(ensure_user_database)
    # Then inject current user into g
    app.before_request(inject_current_user)
    # Clean up completed research records
    app.before_request(cleanup_completed_research)
    # Process any pending queue operations for this user (direct mode)
    app.before_request(process_pending_queue_operations)
    # Notify queue processor of user activity (queue mode)
    app.before_request(notify_queue_processor)

    # Flush any queued logs from background threads
    from ..utilities.log_utils import flush_log_queue

    app.before_request(flush_log_queue)

    # Clean up database sessions after each request
    @app.teardown_appcontext
    def cleanup_db_session(exception=None):
        """Clean up database session after each request to avoid cross-thread issues."""
        from flask import g

        if hasattr(g, "db_session"):
            try:
                if g.db_session:
                    g.db_session.close()
            except Exception:
                pass  # Ignore errors during cleanup
            finally:
                g.db_session = None

    # Add Content Security Policy headers to allow Socket.IO to function
    @app.after_request
    def add_security_headers(response):
        # Define a permissive CSP for development that allows Socket.IO to function
        csp = (
            "default-src 'self'; "
            "connect-src 'self' ws: wss: http: https:; "
            "script-src 'self' 'unsafe-inline' 'unsafe-eval' cdnjs.cloudflare.com cdn.jsdelivr.net unpkg.com; "
            "style-src 'self' 'unsafe-inline' cdnjs.cloudflare.com cdn.jsdelivr.net; "
            "font-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net; "
            "img-src 'self' data:; "
            "worker-src blob:; "
            "frame-src 'self';"
        )

        response.headers["Content-Security-Policy"] = csp
        response.headers["X-Content-Security-Policy"] = csp

        # Add CORS headers for API requests
        if request.path.startswith("/api/") or request.path.startswith(
            "/research/api/"
        ):
            response.headers["Access-Control-Allow-Origin"] = "*"
            response.headers["Access-Control-Allow-Methods"] = (
                "GET, POST, PUT, DELETE, OPTIONS"
            )
            response.headers["Access-Control-Allow-Headers"] = (
                "Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override"
            )
            response.headers["Access-Control-Allow-Credentials"] = "true"
            response.headers["Access-Control-Max-Age"] = "3600"

        return response

    # Add a middleware layer to handle abrupt disconnections
    @app.before_request
    def handle_websocket_requests():
        if request.path.startswith("/socket.io"):
            try:
                if not request.environ.get("werkzeug.socket"):
                    return
            except Exception:
                logger.exception("WebSocket preprocessing error")
                # Return empty response to prevent further processing
                return "", 200

    # Handle CORS preflight requests
    @app.before_request
    def handle_preflight():
        if request.method == "OPTIONS":
            if request.path.startswith("/api/") or request.path.startswith(
                "/research/api/"
            ):
                response = app.make_default_options_response()
                response.headers["Access-Control-Allow-Origin"] = "*"
                response.headers["Access-Control-Allow-Methods"] = (
                    "GET, POST, PUT, DELETE, OPTIONS"
                )
                response.headers["Access-Control-Allow-Headers"] = (
                    "Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override"
                )
                response.headers["Access-Control-Allow-Credentials"] = "true"
                response.headers["Access-Control-Max-Age"] = "3600"
                return response


def register_blueprints(app):
    """Register blueprints with the Flask app."""

    # Import blueprints
    from ..benchmarks.web_api.benchmark_routes import benchmark_bp
    from .api import api_blueprint  # Import the API blueprint
    from .auth import auth_bp  # Import the auth blueprint
    from .routes.api_routes import api_bp  # Import the API blueprint
    from .routes.context_overflow_api import (
        context_overflow_bp,
    )  # Import context overflow API
    from .routes.history_routes import history_bp
    from .routes.metrics_routes import metrics_bp
    from .routes.research_routes import research_bp
    from .routes.settings_routes import settings_bp

    # Add root route
    @app.route("/")
    def index():
        """Root route - redirect to login if not authenticated"""
        from flask import redirect, session, url_for

        from ..database.session_context import get_user_db_session
        from ..utilities.db_utils import get_settings_manager
        from .utils.templates import render_template_with_defaults

        # Check if user is authenticated
        if "username" not in session:
            return redirect(url_for("auth.login"))

        # Load current settings from database using proper session context
        username = session.get("username")
        settings = {}
        with get_user_db_session(username) as db_session:
            if db_session:
                settings_manager = get_settings_manager(db_session, username)
                settings = {
                    "llm_provider": settings_manager.get_setting(
                        "llm.provider", "ollama"
                    ),
                    "llm_model": settings_manager.get_setting("llm.model", ""),
                    "llm_openai_endpoint_url": settings_manager.get_setting(
                        "llm.openai_endpoint.url", ""
                    ),
                    "search_tool": settings_manager.get_setting(
                        "search.tool", ""
                    ),
                    "search_iterations": settings_manager.get_setting(
                        "search.iterations", 2
                    ),
                    "search_questions_per_iteration": settings_manager.get_setting(
                        "search.questions_per_iteration", 3
                    ),
                    "search_strategy": settings_manager.get_setting(
                        "search.search_strategy", "source-based"
                    ),
                }

        # Debug logging
        log_settings(settings, "Research page settings loaded")

        return render_template_with_defaults(
            "pages/research.html", settings=settings
        )

    # Register auth blueprint FIRST (so login page is accessible)
    app.register_blueprint(auth_bp)  # Already has url_prefix="/auth"

    # Register other blueprints
    app.register_blueprint(research_bp)
    app.register_blueprint(history_bp)  # Already has url_prefix="/history"
    app.register_blueprint(metrics_bp)
    app.register_blueprint(settings_bp)  # Already has url_prefix="/settings"
    app.register_blueprint(
        api_bp, url_prefix="/research/api"
    )  # Register API blueprint with prefix
    app.register_blueprint(benchmark_bp)  # Register benchmark blueprint
    app.register_blueprint(
        context_overflow_bp, url_prefix="/metrics"
    )  # Register context overflow API

    # Register news API routes
    from .routes import news_routes

    app.register_blueprint(news_routes.bp)
    logger.info("News API routes registered successfully")

    # Register follow-up research routes
    from ..followup_research.routes import followup_bp

    app.register_blueprint(followup_bp)
    logger.info("Follow-up research routes registered successfully")

    # Register news page blueprint
    from ..news.web import create_news_blueprint

    news_bp = create_news_blueprint()
    app.register_blueprint(news_bp, url_prefix="/news")
    logger.info("News page routes registered successfully")

    # Register API v1 blueprint
    app.register_blueprint(api_blueprint)  # Already has url_prefix='/api/v1'

    # After registration, update CSRF exemptions
    if hasattr(app, "extensions") and "csrf" in app.extensions:
        csrf = app.extensions["csrf"]
        # Exempt the API blueprint routes by actual endpoints
        csrf.exempt("api_v1")
        csrf.exempt("api")
        for rule in app.url_map.iter_rules():
            if rule.endpoint and (
                rule.endpoint.startswith("api_v1.")
                or rule.endpoint.startswith("api.")
            ):
                csrf.exempt(rule.endpoint)

    # Add favicon route
    @app.route("/favicon.ico")
    def favicon():
        return send_from_directory(
            app.static_folder, "favicon.ico", mimetype="image/x-icon"
        )

    # Add static route at the app level for compatibility
    @app.route("/static/<path:path>")
    def app_serve_static(path):
        return send_from_directory(app.static_folder, path)


def register_error_handlers(app):
    """Register error handlers with the Flask app."""

    @app.errorhandler(404)
    def not_found(error):
        return make_response(jsonify({"error": "Not found"}), 404)

    @app.errorhandler(500)
    def server_error(error):
        return make_response(jsonify({"error": "Server error"}), 500)

    # Handle News API exceptions globally
    try:
        from ..news.exceptions import NewsAPIException

        @app.errorhandler(NewsAPIException)
        def handle_news_api_exception(error):
            """Handle NewsAPIException and convert to JSON response."""
            from loguru import logger

            logger.error(
                f"News API error: {error.message} (code: {error.error_code})"
            )
            return jsonify(error.to_dict()), error.status_code
    except ImportError:
        # News module not available
        pass


def create_database(app):
    """
    DEPRECATED: Database creation is now handled per-user via encrypted_db.py
    This function is kept for compatibility but does nothing.
    """
    pass
