"""
App factory para Flask.
"""
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_caching import Cache
from app.config import Config
from app.db import test_connection
from app.utils.logger import get_logger
from datetime import datetime, timezone

jwt = JWTManager()
limiter = None  # Se inicializará en create_app()
cache = Cache()

# Logger principal
logger = get_logger(__name__)

def create_app():
    """Crea y configura la aplicación Flask."""
    import os
    from pathlib import Path
    
    # Configurar Flask para servir archivos estáticos desde app/static
    static_folder = Path(__file__).parent / 'static'
    # Usar static_url_path='' para servir desde la raíz, pero también agregar rutas explícitas
    app = Flask(__name__, static_folder=str(static_folder), static_url_path='')
    
    # Configuración
    app.config['JWT_SECRET_KEY'] = Config.JWT_SECRET_KEY
    app.config['JWT_ACCESS_TOKEN_EXPIRES'] = Config.JWT_ACCESS_TOKEN_EXPIRES
    app.config['DEBUG'] = Config.DEBUG
    
    # Initialize extensions
    jwt.init_app(app)
    
    # JWT Error handlers
    @jwt.expired_token_loader
    def expired_token_callback(jwt_header, jwt_payload):
        return jsonify({'error': 'Token has expired', 'message': 'Please login again'}), 401
    
    @jwt.invalid_token_loader
    def invalid_token_callback(error):
        logger.warning(f"Invalid JWT token: {error}", extra={
            'endpoint': request.path,
            'method': request.method,
            'ip_address': get_remote_address()
        })
        return jsonify({'error': 'Invalid token', 'message': 'Please login again'}), 422
    
    @jwt.unauthorized_loader
    def missing_token_callback(error):
        return jsonify({'error': 'Authorization required', 'message': 'Please provide a valid token'}), 401
    
    @jwt.needs_fresh_token_loader
    def token_not_fresh_callback(jwt_header, jwt_payload):
        return jsonify({'error': 'Token is not fresh', 'message': 'Please login again'}), 401
    
    # Rate Limiting
    global limiter
    if Config.RATELIMIT_ENABLED:
        limiter = Limiter(
            app=app,
            key_func=get_remote_address,
            storage_uri=Config.RATELIMIT_STORAGE_URL,
            default_limits=["200 per day", "50 per hour"],
            headers_enabled=True
        )
        logger.info("Rate limiting enabled")
    else:
        logger.warning("Rate limiting is disabled")
    
    # Caching
    cache_config = {
        'CACHE_TYPE': Config.CACHE_TYPE,
        'CACHE_DEFAULT_TIMEOUT': Config.CACHE_DEFAULT_TIMEOUT
    }
    
    if Config.CACHE_TYPE == 'RedisCache' and Config.CACHE_REDIS_URL:
        cache_config['CACHE_REDIS_URL'] = Config.CACHE_REDIS_URL
    elif Config.CACHE_TYPE == 'RedisCache':
        logger.warning("RedisCache seleccionado pero CACHE_REDIS_URL no configurado. Usando SimpleCache.")
        cache_config['CACHE_TYPE'] = 'SimpleCache'
    
    cache.init_app(app, config=cache_config)
    logger.info(f"Caching enabled with type: {cache_config['CACHE_TYPE']}")
    
    # CORS configuration
    CORS(app, 
         resources={r"/api/*": {
             "origins": Config.CORS_ORIGINS,
             "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
             "allow_headers": ["Content-Type", "Authorization", "X-Requested-With"],
             "expose_headers": ["Content-Type", "Authorization"],
             "supports_credentials": True
         }},
         supports_credentials=True)
    
    # Register blueprints
    from app.routers.auth import auth_bp
    from app.routers.tires import tires_bp
    from app.routers.businesses import businesses_bp
    from app.routers.inventory import inventory_bp
    from app.routers.users import users_bp
    from app.routers.reviews import reviews_bp
    from app.routers.stats import stats_bp
    from app.routers.governance import governance_bp
    from app.routers.favorites import favorites_bp
    from app.routers.history import history_bp
    from app.routers.price_alerts import price_alerts_bp
    from app.routers.comparisons import comparisons_bp
    
    app.register_blueprint(auth_bp, url_prefix='/api/auth')
    app.register_blueprint(tires_bp, url_prefix='/api/tires')
    app.register_blueprint(businesses_bp, url_prefix='/api/businesses')
    app.register_blueprint(inventory_bp, url_prefix='/api/inventory')
    app.register_blueprint(users_bp, url_prefix='/api/users')
    app.register_blueprint(reviews_bp, url_prefix='/api/reviews')
    app.register_blueprint(stats_bp, url_prefix='/api/stats')
    app.register_blueprint(governance_bp, url_prefix='/api/governance')
    app.register_blueprint(favorites_bp, url_prefix='/api/favorites')
    app.register_blueprint(history_bp, url_prefix='/api/history')
    app.register_blueprint(price_alerts_bp, url_prefix='/api/price-alerts')
    app.register_blueprint(comparisons_bp, url_prefix='/api/comparisons')
    
    # Aplicar rate limiting específico a endpoints críticos después de registrar blueprints
    # Nota: Los decoradores @limiter.limit() en los routers ya aplican rate limiting.
    # Los límites por defecto se aplican automáticamente cuando limiter está habilitado.
    if Config.RATELIMIT_ENABLED and limiter:
        try:
            # Intentar aplicar límites específicos si las funciones existen
            if 'login' in auth_bp.view_functions:
                limiter.limit("5 per minute")(auth_bp.view_functions['login'])
            if 'register' in auth_bp.view_functions:
                limiter.limit("10 per hour")(auth_bp.view_functions['register'])
            if 'forgot_password' in auth_bp.view_functions:
                limiter.limit("5 per hour")(auth_bp.view_functions['forgot_password'])
            if 'reset_password' in auth_bp.view_functions:
                limiter.limit("5 per hour")(auth_bp.view_functions['reset_password'])
            logger.info("Rate limiting específico aplicado a endpoints de autenticación")
        except Exception as e:
            logger.warning(f"No se pudo aplicar rate limiting específico: {e}")
    
    # Aplicar cacheo a endpoints públicos frecuentes
    # Nota: El cacheo se aplica condicionalmente ya que algunos endpoints pueden necesitar datos en tiempo real
    # Para endpoints específicos, se puede aplicar directamente en el decorator @cache.cached()
    logger.info("Cache configurado. Aplicar @cache.cached() directamente en endpoints que requieran cacheo.")
    
    # Handler para OPTIONS requests (preflight)
    @app.before_request
    def handle_preflight():
        if request.method == "OPTIONS":
            response = app.make_default_options_response()
            origin = request.headers.get('Origin')
            if origin and origin in Config.CORS_ORIGINS:
                response.headers['Access-Control-Allow-Origin'] = origin
                response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS, PATCH'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With'
            response.headers['Access-Control-Max-Age'] = '3600'
            return response
    
    @app.route('/health')
    def health_check():
        """Health check mejorado con estado de DB y servicios."""
        health_status = {
            'status': 'healthy',
            'timestamp': datetime.now(timezone.utc).isoformat()
        }
        
        # Verificar conexión a base de datos
        try:
            from app.db import get_db_connection
            conn = get_db_connection()
            if conn:
                cursor = conn.cursor()
                cursor.execute("SELECT 1")
                cursor.close()
                
                # Devolver conexión al pool si está usando pooling
                try:
                    from app.db import USE_POOL, _pool_initialized
                    if USE_POOL and _pool_initialized:
                        from app.db_pool import return_db_connection
                        return_db_connection(conn)
                    else:
                        conn.close()
                except:
                    # Si hay error, cerrar la conexión normalmente
                    try:
                        conn.close()
                    except:
                        pass
                
                health_status['database'] = 'connected'
            else:
                health_status['status'] = 'degraded'
                health_status['database'] = 'disconnected'
        except Exception as e:
            health_status['status'] = 'degraded'
            health_status['database'] = f'error: {str(e)}'
        
        # Estadísticas del pool si está usando pooling
        try:
            from app.db_pool import get_pool
            pool = get_pool()
            if pool:
                health_status['connection_pool'] = pool.get_stats()
        except:
            pass
        
        # Cache stats si está disponible
        try:
            if cache:
                # Redis tiene método info(), SimpleCache no
                if hasattr(cache.cache, 'info'):
                    try:
                        cache_info = cache.cache.info()
                        health_status['cache'] = {'type': Config.CACHE_TYPE, 'info': cache_info}
                    except:
                        health_status['cache'] = {'type': Config.CACHE_TYPE, 'status': 'connected'}
                else:
                    health_status['cache'] = {'type': Config.CACHE_TYPE, 'status': 'active'}
        except:
            pass
        
        status_code = 200 if health_status['status'] == 'healthy' else 503
        return jsonify(health_status), status_code
    
    @app.route('/api')
    @limiter.exempt if limiter else lambda f: f
    def api_info():
        """API information endpoint"""
        return jsonify({
            'message': 'RoadFY API',
            'version': '1.0.0',
            'endpoints': {
                'auth': '/api/auth',
                'tires': '/api/tires',
                'businesses': '/api/businesses',
                'inventory': '/api/inventory',
                'users': '/api/users',
                'reviews': '/api/reviews',
                'stats': '/api/stats',
                'governance': '/api/governance',
                'favorites': '/api/favorites',
                'history': '/api/history',
                'price_alerts': '/api/price-alerts',
                'comparisons': '/api/comparisons'
            }
        })
    
    # Ruta para servir archivos estáticos explícitamente (js, css, imágenes, etc.)
    # Estas rutas deben estar ANTES de la ruta catch-all para que Flask las capture primero
    @app.route('/js/<path:filename>')
    def serve_js(filename):
        """Serve JavaScript files"""
        try:
            # Normalizar el path (manejar tanto / como \)
            normalized_filename = filename.replace('\\', '/')
            static_path = f'js/{normalized_filename}'
            
            # Usar send_static_file que es más confiable
            response = app.send_static_file(static_path)
            # Agregar headers para evitar caché en desarrollo
            response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
            response.headers['Pragma'] = 'no-cache'
            response.headers['Expires'] = '0'
            response.headers['Content-Type'] = 'text/javascript; charset=utf-8'
            return response
        except Exception as e:
            logger.error(f"Error serving JS file {filename}", exc_info=True, extra={
                'filename': filename,
                'endpoint': '/js/<path:filename>'
            })
            return jsonify({'error': 'Error serving file'}), 500
    
    @app.route('/css/<path:filename>')
    def serve_css(filename):
        """Serve CSS files"""
        try:
            static_path = f'css/{filename}'
            return app.send_static_file(static_path)
        except Exception as e:
            logger.warning(f"Error serving CSS file {filename}", extra={
                'filename': filename,
                'endpoint': '/css/<path:filename>'
            })
            return jsonify({'error': 'File not found'}), 404
    
    @app.route('/assets/<path:filename>')
    def serve_assets(filename):
        """Serve asset files (images, etc.)"""
        try:
            static_path = f'assets/{filename}'
            return app.send_static_file(static_path)
        except Exception as e:
            logger.warning(f"Error serving asset file {filename}", extra={
                'filename': filename,
                'endpoint': '/assets/<path:filename>'
            })
            return jsonify({'error': 'File not found'}), 404
    
    @app.route('/')
    def index():
        """Serve the frontend index.html"""
        return send_from_directory(app.static_folder, 'index.html')
    
    # Catch-all route for SPA - must be last
    # IMPORTANTE: Esta ruta NO debe capturar rutas de archivos estáticos
    # Flask debería haber capturado las rutas específicas antes, pero por seguridad:
    @app.route('/<path:path>')
    def serve_spa(path):
        """Serve index.html for all non-API routes (SPA routing)"""
        # Excluir explícitamente rutas de archivos estáticos y API
        if path.startswith('api/') or path.startswith('js/') or path.startswith('css/') or path.startswith('assets/'):
            # Si llegamos aquí, significa que la ruta específica no funcionó
            # Intentar servir el archivo como fallback
            try:
                if path.startswith('js/'):
                    static_path = path  # Ya incluye 'js/'
                    response = app.send_static_file(static_path)
                    response.headers['Content-Type'] = 'text/javascript; charset=utf-8'
                    response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
                    response.headers['Pragma'] = 'no-cache'
                    response.headers['Expires'] = '0'
                    return response
                elif path.startswith('css/'):
                    return app.send_static_file(path)
                elif path.startswith('assets/'):
                    return app.send_static_file(path)
            except:
                pass
            return jsonify({'error': 'Not found'}), 404
        
        # Para todas las demás rutas (rutas SPA), servir index.html
        return send_from_directory(app.static_folder, 'index.html')
    
    # Excluir archivos estáticos del rate limiting después de definir las rutas
    if Config.RATELIMIT_ENABLED and limiter:
        try:
            # Aplicar exención a las rutas de archivos estáticos
            limiter.exempt(serve_js)
            limiter.exempt(serve_css)
            limiter.exempt(serve_assets)
            limiter.exempt(index)
            limiter.exempt(serve_spa)
            limiter.exempt(health_check)
            limiter.exempt(api_info)
            logger.info("Archivos estáticos excluidos del rate limiting")
        except Exception as e:
            logger.warning(f"No se pudo excluir archivos estáticos del rate limiting: {e}")
    
    # Probar conexión a la base de datos al iniciar
    logger.info("Inicializando aplicación RoadFY...")
    if test_connection():
        logger.info("Conexión a base de datos establecida correctamente")
    else:
        logger.error("No se pudo conectar a la base de datos")
    
    # Compression middleware para mejorar rendimiento (comprimir respuestas grandes)
    @app.after_request
    def after_request_compression(response):
        """Comprimir respuestas grandes para mejorar rendimiento."""
        # Comprimir respuestas JSON y texto mayores a 500 bytes
        if (response.status_code == 200 and 
            response.content_length and 
            response.content_length > 500 and
            response.content_type and 
            ('application/json' in response.content_type or 'text/' in response.content_type)):
            try:
                import gzip
                from io import BytesIO
                
                # Comprimir contenido
                compressed = BytesIO()
                with gzip.GzipFile(fileobj=compressed, mode='wb') as f:
                    f.write(response.data)
                compressed_data = compressed.getvalue()
                
                # Solo usar si la compresión es significativa (>20% reducción)
                if len(compressed_data) < len(response.data) * 0.8:
                    response.data = compressed_data
                    response.headers['Content-Encoding'] = 'gzip'
                    response.headers['Content-Length'] = len(compressed_data)
            except Exception as e:
                logger.debug(f"Could not compress response: {e}")
        
        return response
    
    # After request handler para asegurar CORS en todas las respuestas
    @app.after_request
    def after_request(response):
        origin = request.headers.get('Origin')
        if origin and origin in Config.CORS_ORIGINS:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS, PATCH'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With'
        response.headers['Access-Control-Expose-Headers'] = 'Content-Type, Authorization'
        return response
    
    # Error handlers
    @app.errorhandler(400)
    @app.errorhandler(401)
    @app.errorhandler(403)
    @app.errorhandler(422)
    @app.errorhandler(500)
    def handle_error(e):
        code = getattr(e, 'code', 500)
        message = getattr(e, 'description', str(e))
        
        # Log del error
        logger.warning(f"Error {code}: {message}", extra={
            'status_code': code,
            'endpoint': request.path,
            'method': request.method,
            'ip_address': get_remote_address()
        })
        
        # En producción, no exponer detalles sensibles
        if not Config.DEBUG and code == 500:
            user_message = 'Internal server error'
        else:
            user_message = message
        
        response = jsonify({'error': user_message})
        response.status_code = code
        origin = request.headers.get('Origin')
        if origin and origin in Config.CORS_ORIGINS:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
        return response
    
    # Error handler 404 - solo para rutas de API
    @app.errorhandler(404)
    def handle_404(e):
        # Si es una ruta de API, devolver error JSON
        if request.path.startswith('/api/'):
            response = jsonify({'error': 'Endpoint not found'})
            response.status_code = 404
            origin = request.headers.get('Origin')
            if origin and origin in Config.CORS_ORIGINS:
                response.headers['Access-Control-Allow-Origin'] = origin
                response.headers['Access-Control-Allow-Credentials'] = 'true'
            return response
        # Para rutas no-API, servir index.html (SPA routing)
        # Esto no debería ejecutarse normalmente porque serve_spa debería capturar todo
        from flask import send_from_directory
        return send_from_directory(app.static_folder, 'index.html')
    
    # Handler para excepciones no capturadas
    @app.errorhandler(Exception)
    def handle_unhandled_exception(e):
        error_msg = str(e)
        error_type = type(e).__name__
        
        # Log del error con toda la información
        logger.error(
            f"Unhandled exception: {error_type}: {error_msg}",
            exc_info=True,
            extra={
                'error_type': error_type,
                'endpoint': request.path,
                'method': request.method,
                'ip_address': get_remote_address(),
                'user_agent': request.headers.get('User-Agent')
            }
        )
        
        # En producción, no exponer detalles
        if Config.DEBUG:
            response_data = {
                'error': 'Internal server error',
                'message': error_msg,
                'type': error_type
            }
        else:
            response_data = {
                'error': 'Internal server error'
            }
        
        response = jsonify(response_data)
        response.status_code = 500
        origin = request.headers.get('Origin')
        if origin and origin in Config.CORS_ORIGINS:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
        return response
    
    # Handler para rate limit exceeded
    @app.errorhandler(429)
    def handle_rate_limit_exceeded(e):
        logger.warning("Rate limit exceeded", extra={
            'endpoint': request.path,
            'method': request.method,
            'ip_address': get_remote_address()
        })
        response = jsonify({
            'error': 'Too many requests',
            'message': 'Has excedido el límite de solicitudes. Por favor intenta más tarde.'
        })
        response.status_code = 429
        origin = request.headers.get('Origin')
        if origin and origin in Config.CORS_ORIGINS:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
        return response
    
    return app
