from __future__ import annotations

import time
import uuid
from typing import Any

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

from src.core.logging import get_logger
from src.domain.entities import AuditLogEntity
from src.domain.interfaces import IAuditLogRepository
from src.infrastructure.database.session import async_session_factory

logger = get_logger("audit_middleware")

WRITE_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
IGNORED_PATHS = {"/docs", "/redoc", "/openapi.json", "/metrics", "/api/v1/system/health"}


class AuditMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        response = await call_next(request)

        if request.method in WRITE_METHODS and not self._should_ignore(request.url.path):
            try:
                await self._record_audit(request, response)
            except Exception as exc:
                logger.warning("audit_log_failed", extra={"error": str(exc)})

        return response

    def _should_ignore(self, path: str) -> bool:
        return any(path.startswith(p) for p in IGNORED_PATHS)

    async def _record_audit(self, request: Request, response: Response) -> None:
        actor_id = None
        actor_type = "anonymous"

        if hasattr(request.state, "user") and request.state.user:
            user = request.state.user
            try:
                actor_id = uuid.UUID(user.subject)
            except (ValueError, TypeError):
                pass
            actor_type = user.role.value if hasattr(user.role, "value") else str(user.role)

        resource_type = self._extract_resource_type(request.url.path)
        action = f"{request.method.lower()}_{resource_type}"
        resource_id = self._extract_resource_id(request.url.path)
        ip_address = request.client.host if request.client else None
        user_agent = request.headers.get("user-agent", "")
        request_id = getattr(request.state, "request_id", str(uuid.uuid4()))

        async with async_session_factory() as session:
            try:
                from src.infrastructure.database.repositories import AuditLogRepository
                repo = AuditLogRepository(session)
                entity = AuditLogEntity(
                    actor_id=actor_id,
                    actor_type=actor_type,
                    action=action,
                    resource_type=resource_type,
                    resource_id=resource_id,
                    ip_address=ip_address,
                    user_agent=user_agent,
                    request_id=request_id,
                )
                await repo.add(entity)
                await session.commit()
            except Exception as exc:
                logger.warning("audit_db_write_failed", extra={"error": str(exc)})

    def _extract_resource_type(self, path: str) -> str:
        parts = path.strip("/").split("/")
        if len(parts) >= 3:
            return parts[2]
        if len(parts) >= 1:
            return parts[-1]
        return "unknown"

    def _extract_resource_id(self, path: str) -> str | None:
        parts = path.strip("/").split("/")
        for part in reversed(parts[3:] if len(parts) > 3 else parts):
            try:
                uuid.UUID(part)
                return part
            except ValueError:
                continue
        return None
