"""
HTTP 추적 미들웨어
모든 HTTP 요청을 자동으로 추적하고 root span을 생성함

역할: FastAPI의 모든 HTTP 요청을 가로채서 추적 시작점(root span)을 만듦
사용: MonitoringSDK.init()에서 자동으로 등록되어 모든 라우터 요청을 추적함
"""

import time
from datetime import datetime
from typing import Callable, Optional
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
from ..types.interfaces import SpanEntry
from ..transport.batch_sender import BatchSender
from .trace_context import create_root_context, set_trace_context


class HttpTracerMiddleware(BaseHTTPMiddleware):
    """
    HTTP 요청을 추적하는 미들웨어
    """

    def __init__(
        self, app, batch_sender: BatchSender, service_name: str, environment: str
    ):
        """
        HttpTracerMiddleware 초기화

        Args:
            app: FastAPI 애플리케이션
            batch_sender: 배치 전송자
            service_name: 서비스 이름
            environment: 환경 설정
        """
        super().__init__(app)
        self.batch_sender = batch_sender
        self.service_name = service_name
        self.environment = environment

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        """
        HTTP 요청을 가로채서 추적함

        역할:
        1. 요청 시작 시 root trace context 생성 (trace_id, span_id)
        2. set_trace_context로 context 저장 (ContextVar에 저장됨)
        3. 요청 처리
        4. 응답 시 duration 계산하고 Root SpanEntry 생성
        5. BatchSender에 전송

        Args:
            request: Starlette Request
            call_next: 다음 핸들러

        Returns:
            Response
        """
        # 헬스체크 경로는 trace를 생성하지 않음
        if request.url.path == "/health" or request.url.path == "/":
            return await call_next(request)

        # Root trace context 생성 - 전체 추적의 시작점
        trace_context = create_root_context()
        start_time = time.time()
        start_timestamp = datetime.utcnow().isoformat() + "Z"

        # Trace context를 ContextVar에 저장
        # 이제 이 요청 처리 중 발생하는 모든 API 호출, Bedrock 호출이 같은 trace_id를 공유함
        set_trace_context(trace_context)

        status_code = 500
        status = "ERROR"

        try:
            response = await call_next(request)
            status_code = response.status_code
            status = "OK" if status_code < 400 else "ERROR"
            return response
        except Exception as e:
            status = "ERROR"
            raise
        finally:
            duration = (time.time() - start_time) * 1000  # Convert to milliseconds

            # HTTP Span 기록
            self._record_span(
                trace_context.trace_id,
                trace_context.span_id,
                None,
                request,
                status_code,
                start_timestamp,
                duration,
                status,
            )

    def _record_span(
        self,
        trace_id: str,
        span_id: str,
        parent_span_id: Optional[str],
        request: Request,
        status_code: int,
        timestamp: str,
        duration: float,
        status: str,
    ) -> None:
        """
        HTTP Span을 기록함

        Args:
            trace_id: Trace ID
            span_id: Span ID
            parent_span_id: Parent Span ID (root는 None)
            request: Starlette Request 객체
            status_code: HTTP 상태 코드
            timestamp: 시작 시각
            duration: 소요 시간 (밀리초)
            status: 상태 (OK 또는 ERROR)
        """
        method = request.method
        path = request.url.path
        url = str(request.url)

        span: SpanEntry = {
            "type": "span",
            "timestamp": timestamp,
            "service_name": self.service_name,
            "environment": self.environment,
            "trace_id": trace_id,
            "span_id": span_id,
            "parent_span_id": parent_span_id,
            "name": f"{method} {path}",
            "kind": "SERVER",
            "duration_ms": duration,
            "status": status,  # type: ignore
            "http_method": method,
            "http_path": path,
            "http_url": url,
            "http_status_code": status_code,
        }

        self.batch_sender.add(span)
