"""Refactored monitoring middleware with dependency injection."""

import asyncio
import logging
import time
from typing import Optional

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

from .config import MonitorConfig
from .repository import SQLiteRepository
from .services import MonitorService, RequestProcessor

logger = logging.getLogger(__name__)


class MonitorMiddleware(BaseHTTPMiddleware):
    """Enhanced monitoring middleware with proper separation of concerns."""

    def __init__(
        self,
        app,
        config: Optional[MonitorConfig] = None,
        service: Optional[MonitorService] = None,
        # Legacy parameters for backward compatibility
        db_path: Optional[str] = None,
        exclude_paths: Optional[list] = None,
        **kwargs
    ):
        super().__init__(app)

        # Handle legacy parameters
        if config is None and (db_path or exclude_paths or kwargs):
            config = MonitorConfig(
                db_path=db_path or "monitor.db",
                exclude_paths=exclude_paths or [],
                **kwargs
            )

        # Use provided config or create default
        self.config = config or MonitorConfig()

        # Setup service with dependency injection
        if service:
            self.service = service
        else:
            repository = SQLiteRepository(self.config)
            self.service = MonitorService(repository, self.config)

        self.processor = RequestProcessor(self.config)
        self._initialized = False

    async def _initialize(self) -> None:
        """Initialize the middleware."""
        try:
            await self.service.start_background_tasks()
            logger.info("Monitor middleware initialized successfully")
        except Exception as e:
            logger.error(f"Failed to initialize monitor middleware: {e}")

    async def dispatch(self, request: Request, call_next):
        """Process request and response for monitoring."""
        # Lazy initialization on first request
        if not self._initialized:
            await self._initialize()
            self._initialized = True

        # Check if path should be monitored
        if not self.service.should_monitor_path(request.url.path):
            return await call_next(request)

        start_time = time.time()
        request_body = None
        response = None
        error_info = None

        try:
            # Capture request body
            request_body = await self.processor.capture_request_body(request)

            # Extract request data
            request_data = self.processor.extract_request_data(
                request, start_time, request_body
            )

            # Process the request
            response = await self._process_request(request, call_next)

            # Calculate response time
            response_time = (time.time() - start_time) * 1000

            # Capture response body
            response_body = None
            if self._should_capture_response_body(response):
                response_body = await self._capture_response_body(response)

            # Extract response data
            response_data = self.processor.extract_response_data(
                response, response_time, response_body
            )

            # Record the request asynchronously
            asyncio.create_task(
                self.service.record_request(request_data, response_data, error_info)
            )

        except Exception as e:
            # Handle errors
            response_time = (time.time() - start_time) * 1000
            error_info = self.processor.extract_error_info(e)

            # Create error response data
            response_data = self.processor.extract_response_data(
                type('MockResponse', (), {
                    'status_code': 500,
                    'headers': {}
                })(),
                response_time
            )

            # Record the error asynchronously
            if 'request_data' in locals():
                asyncio.create_task(
                    self.service.record_request(request_data, response_data, error_info)
                )

            # Re-raise the exception
            raise

        return response

    async def _process_request(self, request: Request, call_next):
        """Process the actual request."""
        try:
            return await asyncio.wait_for(
                call_next(request),
                timeout=self.config.request_timeout
            )
        except asyncio.TimeoutError:
            logger.warning(f"Request timeout for {request.url.path}")
            raise
        except Exception as e:
            logger.error(f"Error processing request {request.url.path}: {e}")
            raise

    def _should_capture_response_body(self, response: Response) -> bool:
        """Determine if response body should be captured."""
        content_type = response.headers.get("content-type", "")
        return content_type.startswith(("application/json", "text/", "application/xml"))

    async def _capture_response_body(self, response: Response) -> Optional[str]:
        """Capture response body and recreate response."""
        try:
            body = b""
            async for chunk in response.body_iterator:
                body += chunk

            # Recreate response with captured body
            new_response = Response(
                content=body,
                status_code=response.status_code,
                headers=dict(response.headers),
                media_type=response.media_type,
            )

            # Replace the original response
            response.__dict__.update(new_response.__dict__)

            return self.processor.capture_response_body(body)

        except Exception as e:
            logger.warning(f"Failed to capture response body: {e}")
            return None

    async def __aenter__(self):
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit."""
        await self.service.stop_background_tasks()


# Factory function for easy setup
def create_monitor_middleware(
    app,
    db_path: str = "monitor.db",
    exclude_paths: Optional[list] = None,
    **kwargs
) -> MonitorMiddleware:
    """Factory function to create monitor middleware with configuration."""

    config = MonitorConfig(
        db_path=db_path,
        exclude_paths=exclude_paths or [],
        **kwargs
    )

    return MonitorMiddleware(app, config=config)
