"""
Flask integration.

Two modes:

1. **Auto** (zero-config) — ``FlaskIntegration`` monkey-patches
   ``Flask.full_dispatch_request`` so every Flask app created in the
   process is automatically instrumented.  Enabled by default when
   Flask is installed.

2. **Manual** — ``middleware_flask(app)`` installs per-app hooks.
   Useful if you want to instrument only *one* specific app, or if
   auto-discovery is disabled.

The auto-integration guards against double-instrumentation: if a
request is already tracked by the class-level patch, the manual
middleware's hooks will detect it and skip.
"""

import time
import traceback

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


# ═══════════════════════════════════════════════════════════════════════════════
# Auto-discovery integration (monkey-patches Flask class)
# ═══════════════════════════════════════════════════════════════════════════════

class FlaskIntegration(Integration):
    identifier = "flask"

    @staticmethod
    def setup_once():
        try:
            from flask import Flask
        except ImportError:
            raise DidNotEnable("flask is not installed")

        _originals["flask_full_dispatch_request"] = Flask.full_dispatch_request

        def _patched_full_dispatch_request(self):
            from flask import request as flask_request, g
            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["flask_full_dispatch_request"](self)

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

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

            try:
                rv = _originals["flask_full_dispatch_request"](self)
            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"Flask error: {exc}", {
                        "type": "flask_error",
                        "method": flask_request.method,
                        "path": flask_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 = (
                        rv.status_code if hasattr(rv, "status_code") else 200
                    )
                    scope = get_current_scope()
                    scope.add_breadcrumb({
                        "type": "http",
                        "category": "flask",
                        "message": (
                            f"{flask_request.method} {flask_request.path}"
                            f" {status_code}"
                        ),
                        "data": {
                            "method": flask_request.method,
                            "path": flask_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": flask_request.method,
                        "url": flask_request.url,
                        "path": flask_request.path,
                        "status_code": status_code,
                        "duration_s": round(duration, 3),
                        "content_length": (
                            rv.content_length
                            if hasattr(rv, "content_length") else None
                        ),
                    }
                    if client.options.get("send_default_pii", False):
                        meta["user_agent"] = flask_request.headers.get(
                            "User-Agent"
                        )
                        meta["ip"] = flask_request.remote_addr

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

        Flask.full_dispatch_request = _patched_full_dispatch_request


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

def middleware_flask(app):
    """Install Overcast request tracking on a Flask *app*.

    If the auto ``FlaskIntegration`` is already active, this is a no-op
    (the class-level patch already covers every app).

    Usage::

        from overcast_sdk import middleware_flask
        app = Flask(__name__)
        middleware_flask(app)
    """
    from overcast_sdk.integrations import get_installed_integrations

    if "flask" in get_installed_integrations():
        # Auto-integration already active — nothing to do.
        return app

    @app.before_request
    def _overcast_before():
        from flask import g
        from overcast_sdk.scope import Scope, push_isolation_scope
        g._overcast_start = time.monotonic()
        g._overcast_scope_token = push_isolation_scope(Scope())

    @app.after_request
    def _overcast_after(response):
        with capture_internal:
            from flask import request, g
            from overcast_sdk.client import get_client
            from overcast_sdk.scope import get_current_scope, pop_isolation_scope

            duration = time.monotonic() - getattr(
                g, "_overcast_start", time.monotonic()
            )
            client = get_client()

            if client:
                scope = get_current_scope()
                scope.add_breadcrumb({
                    "type": "http",
                    "category": "flask",
                    "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.url,
                    "path": request.path,
                    "status_code": response.status_code,
                    "duration_s": round(duration, 3),
                    "content_length": response.content_length,
                }
                if client.options.get("send_default_pii", False):
                    meta["user_agent"] = request.headers.get("User-Agent")
                    meta["ip"] = request.remote_addr

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

            token = getattr(g, "_overcast_scope_token", None)
            if token is not None:
                pop_isolation_scope(token)

        return response

    # Signal-based error capture (does not overwrite user error handlers)
    try:
        from flask import got_request_exception

        def _overcast_on_exception(sender, exception, **kwargs):
            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"Flask error: {exception}", {
                        "type": "flask_error",
                        "error": {
                            "type": type(exception).__name__,
                            "message": str(exception),
                            "traceback": tb,
                        },
                    })

        got_request_exception.connect(_overcast_on_exception, app)
    except ImportError:
        pass

    return app
