"""
Django integration.

Two modes:

1. **Auto** (zero-config) — ``DjangoIntegration`` monkey-patches
   ``BaseHandler.get_response`` so every Django project is automatically
   instrumented.  Enabled by default when Django is installed.

2. **Manual** — ``middleware_django()`` returns a standard Django
   middleware class you can add to ``MIDDLEWARE``.  Useful if you
   disabled auto-discovery and want explicit control.

The auto-integration marks each request so the manual middleware
detects it and avoids double-instrumentation.
"""

import time
import traceback

from overcast_sdk.integrations import Integration, DidNotEnable
from overcast_sdk.utils import capture_internal, _originals


# ═══════════════════════════════════════════════════════════════════════════════
# Auto-discovery integration (monkey-patches Django's BaseHandler)
# ═══════════════════════════════════════════════════════════════════════════════

class DjangoIntegration(Integration):
    identifier = "django"

    @staticmethod
    def setup_once():
        try:
            import django
            from django.core.handlers.base import BaseHandler
        except ImportError:
            raise DidNotEnable("django is not installed")

        _originals["django_get_response"] = BaseHandler.get_response

        def _patched_get_response(self, request):
            from overcast_sdk.client import get_client
            from overcast_sdk.scope import (
                Scope, push_isolation_scope, pop_isolation_scope,
                get_current_scope,
            )

            client = get_client()
            if not client:
                return _originals["django_get_response"](self, request)

            # Mark so manual middleware can detect us
            request._overcast_auto = True

            token = push_isolation_scope(Scope())
            start = time.monotonic()

            try:
                response = _originals["django_get_response"](self, request)
            except Exception as exc:
                duration = time.monotonic() - start
                with capture_internal:
                    tb = "".join(traceback.format_exception(
                        type(exc), exc, exc.__traceback__,
                    ))
                    client.enqueue("ERROR", f"Django error: {exc}", {
                        "type": "django_error",
                        "method": request.method,
                        "path": request.path,
                        "duration_s": round(duration, 3),
                        "error": {
                            "type": type(exc).__name__,
                            "message": str(exc),
                            "traceback": tb,
                        },
                    })
                raise
            else:
                duration = time.monotonic() - start
                with capture_internal:
                    status_code = response.status_code
                    scope = get_current_scope()
                    scope.add_breadcrumb({
                        "type": "http",
                        "category": "django",
                        "message": (
                            f"{request.method} {request.path} {status_code}"
                        ),
                        "data": {
                            "method": request.method,
                            "path": request.path,
                            "status_code": status_code,
                            "duration_ms": round(duration * 1000),
                        },
                        "level": "error" if status_code >= 500 else "info",
                    })

                    level = (
                        "ERROR" if status_code >= 500
                        else "WARNING" if status_code >= 400
                        else "INFO"
                    )
                    meta = {
                        "type": "http_incoming",
                        "method": request.method,
                        "url": request.build_absolute_uri(),
                        "path": request.path,
                        "status_code": status_code,
                        "duration_s": round(duration, 3),
                    }
                    if client.options.get("send_default_pii", False):
                        meta["user_agent"] = request.META.get(
                            "HTTP_USER_AGENT"
                        )
                        meta["ip"] = request.META.get("REMOTE_ADDR")

                    client.enqueue(
                        level,
                        (
                            f"{request.method} {request.path}"
                            f" {status_code} ({duration:.3f}s)"
                        ),
                        meta,
                    )
                return response
            finally:
                pop_isolation_scope(token)

        BaseHandler.get_response = _patched_get_response


# ═══════════════════════════════════════════════════════════════════════════════
# Manual middleware (kept for backwards-compat / explicit opt-in)
# ═══════════════════════════════════════════════════════════════════════════════

def middleware_django(get_response=None):
    """Return a Django middleware class (or instance if *get_response* given).

    If the auto ``DjangoIntegration`` is already active, the middleware
    detects it and becomes a transparent pass-through.

    Works both as a direct call and as a Django MIDDLEWARE dotted-path::

        MIDDLEWARE = ['overcast_sdk.middleware_django', ...]
    """

    class OvercastDjangoMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response

        def __call__(self, request):
            # If auto-integration already handled this request, pass through.
            if getattr(request, "_overcast_auto", False):
                return self.get_response(request)

            from overcast_sdk.scope import (
                Scope, push_isolation_scope, pop_isolation_scope,
            )

            token = push_isolation_scope(Scope())
            start = time.monotonic()

            try:
                response = self.get_response(request)
            except Exception:
                pop_isolation_scope(token)
                raise
            duration = time.monotonic() - start

            with capture_internal:
                from overcast_sdk.client import get_client
                from overcast_sdk.scope import get_current_scope
                client = get_client()

                if client:
                    scope = get_current_scope()
                    scope.add_breadcrumb({
                        "type": "http",
                        "category": "django",
                        "message": (
                            f"{request.method} {request.path}"
                            f" {response.status_code}"
                        ),
                        "data": {
                            "method": request.method,
                            "path": request.path,
                            "status_code": response.status_code,
                            "duration_ms": round(duration * 1000),
                        },
                        "level": (
                            "error" if response.status_code >= 500
                            else "info"
                        ),
                    })

                    level = (
                        "ERROR" if response.status_code >= 500
                        else "WARNING" if response.status_code >= 400
                        else "INFO"
                    )
                    meta = {
                        "type": "http_incoming",
                        "method": request.method,
                        "url": request.build_absolute_uri(),
                        "path": request.path,
                        "status_code": response.status_code,
                        "duration_s": round(duration, 3),
                    }
                    if client.options.get("send_default_pii", False):
                        meta["user_agent"] = request.META.get(
                            "HTTP_USER_AGENT"
                        )
                        meta["ip"] = request.META.get("REMOTE_ADDR")

                    client.enqueue(
                        level,
                        (
                            f"{request.method} {request.path}"
                            f" {response.status_code} ({duration:.3f}s)"
                        ),
                        meta,
                    )

            pop_isolation_scope(token)
            return response

        def process_exception(self, request, exception):
            # Skip if auto-integration is handling this.
            if getattr(request, "_overcast_auto", False):
                return None

            with capture_internal:
                from overcast_sdk.client import get_client
                client = get_client()
                if client:
                    tb = "".join(traceback.format_exception(
                        type(exception), exception,
                        exception.__traceback__,
                    ))
                    client.enqueue("ERROR", f"Django error: {exception}", {
                        "type": "django_error",
                        "path": request.path,
                        "method": request.method,
                        "error": {
                            "type": type(exception).__name__,
                            "message": str(exception),
                            "traceback": tb,
                        },
                    })
            return None

    if get_response is not None:
        return OvercastDjangoMiddleware(get_response)

    return OvercastDjangoMiddleware
