"""
Centralized error handling for StarPunk

Per ADR-055 and developer Q&A Q4:
- Uses Flask's @app.errorhandler decorator
- Registered in app factory (centralized)
- Micropub endpoints return spec-compliant JSON errors
- Other endpoints return HTML error pages
- All errors logged with correlation IDs
"""

from flask import request, render_template, jsonify, g


def register_error_handlers(app):
    """
    Register centralized error handlers

    Checks request path to determine response format:
    - /micropub/* returns JSON (Micropub spec compliance)
    - All others return HTML templates

    All errors are logged with correlation IDs for tracing

    Args:
        app: Flask application instance
    """

    @app.errorhandler(400)
    def bad_request(error):
        """Handle 400 Bad Request errors"""
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.warning(f"Bad request: {error}")

        if request.path.startswith('/micropub'):
            # Micropub spec-compliant error response
            return jsonify({
                'error': 'invalid_request',
                'error_description': str(error) or 'Bad request'
            }), 400

        return render_template('400.html', error=error), 400

    @app.errorhandler(401)
    def unauthorized(error):
        """Handle 401 Unauthorized errors"""
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.warning(f"Unauthorized access attempt")

        if request.path.startswith('/micropub'):
            # Micropub spec-compliant error response
            return jsonify({
                'error': 'unauthorized',
                'error_description': 'Authentication required'
            }), 401

        return render_template('401.html'), 401

    @app.errorhandler(403)
    def forbidden(error):
        """Handle 403 Forbidden errors"""
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.warning(f"Forbidden access attempt")

        if request.path.startswith('/micropub'):
            # Micropub spec-compliant error response
            return jsonify({
                'error': 'forbidden',
                'error_description': 'Insufficient scope or permissions'
            }), 403

        return render_template('403.html'), 403

    @app.errorhandler(404)
    def not_found(error):
        """Handle 404 Not Found errors"""
        # Don't log 404s at warning level - they're common and not errors
        app.logger.debug(f"Resource not found: {request.path}")

        if request.path.startswith('/api/') or request.path.startswith('/micropub'):
            return jsonify({'error': 'Not found'}), 404

        return render_template('404.html'), 404

    @app.errorhandler(405)
    def method_not_allowed(error):
        """Handle 405 Method Not Allowed errors"""
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.warning(f"Method not allowed: {request.method} {request.path}")

        if request.path.startswith('/micropub'):
            return jsonify({
                'error': 'invalid_request',
                'error_description': f'Method {request.method} not allowed'
            }), 405

        return render_template('405.html'), 405

    @app.errorhandler(500)
    def internal_server_error(error):
        """Handle 500 Internal Server Error"""
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.error(f"Internal server error: {error}", exc_info=True)

        if request.path.startswith('/api/') or request.path.startswith('/micropub'):
            # Don't expose internal error details in API responses
            if request.path.startswith('/micropub'):
                return jsonify({
                    'error': 'server_error',
                    'error_description': 'An internal server error occurred'
                }), 500
            else:
                return jsonify({'error': 'Internal server error'}), 500

        return render_template('500.html'), 500

    @app.errorhandler(503)
    def service_unavailable(error):
        """Handle 503 Service Unavailable errors"""
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.error(f"Service unavailable: {error}")

        if request.path.startswith('/api/') or request.path.startswith('/micropub'):
            return jsonify({
                'error': 'temporarily_unavailable',
                'error_description': 'Service temporarily unavailable'
            }), 503

        return render_template('503.html'), 503

    # Register generic exception handler
    @app.errorhandler(Exception)
    def handle_exception(error):
        """
        Handle uncaught exceptions

        Logs the full exception with correlation ID and returns appropriate error response
        """
        correlation_id = getattr(g, 'correlation_id', 'no-request')
        app.logger.error(f"Uncaught exception: {error}", exc_info=True)

        # If it's an HTTP exception, let Flask handle it
        if hasattr(error, 'code'):
            return error

        # Otherwise, return 500
        if request.path.startswith('/micropub'):
            return jsonify({
                'error': 'server_error',
                'error_description': 'An unexpected error occurred'
            }), 500
        elif request.path.startswith('/api/'):
            return jsonify({'error': 'Internal server error'}), 500
        else:
            return render_template('500.html'), 500


class MicropubError(Exception):
    """
    Micropub-specific error class

    Automatically formats errors according to Micropub spec
    """

    def __init__(self, error_code, description, status_code=400):
        """
        Initialize Micropub error

        Args:
            error_code: Micropub error code (e.g., 'invalid_request', 'insufficient_scope')
            description: Human-readable error description
            status_code: HTTP status code (default 400)
        """
        self.error_code = error_code
        self.description = description
        self.status_code = status_code
        super().__init__(description)

    def to_response(self):
        """
        Convert to Micropub-compliant JSON response

        Returns:
            tuple: (dict, int) Flask response tuple
        """
        return jsonify({
            'error': self.error_code,
            'error_description': self.description
        }), self.status_code
