"""Tests for service_bridge.http.flask — ServiceBridge Flask middleware."""

from __future__ import annotations

import time

import pytest
from flask import Flask, g

from service_bridge.servicebridge import ServiceBridge
from service_bridge.http.flask import init_servicebridge


def _make_client() -> ServiceBridge:
    return ServiceBridge("localhost:14445", "testkey")


# ─── Fixtures ─────────────────────────────────────────────────────────────────

@pytest.fixture()
def svc():
    return _make_client()


def make_flask_app(svc, **middleware_kwargs) -> Flask:
    app = Flask(__name__)
    app.config["TESTING"] = True
    init_servicebridge(app, svc, **middleware_kwargs)

    @app.get("/users")
    def get_users():
        return {"users": []}

    @app.get("/users/<user_id>")
    def get_user(user_id):
        return {"id": user_id}

    @app.post("/orders")
    def create_order():
        return {"created": True}

    @app.get("/health")
    def health():
        return {"ok": True}

    return app


# ─── Tests ────────────────────────────────────────────────────────────────────

class TestFlaskMiddleware:
    def test_sets_x_trace_id_header(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            resp = client.get("/users")
        assert resp.status_code == 200
        assert "x-trace-id" in resp.headers
        assert resp.headers["x-trace-id"] != ""

    def test_propagates_x_trace_id(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            resp = client.get("/users", headers={"x-trace-id": "my-trace-id"})
        assert resp.headers.get("x-trace-id") == "my-trace-id"

    def test_propagates_traceparent(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            resp = client.get(
                "/users",
                headers={"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
            )
        assert resp.headers.get("x-trace-id") == "4bf92f3577b34da6a3ce929d0e0e4736"

    def test_injects_client_into_g(self, svc):
        app = make_flask_app(svc)
        captured = {}

        @app.get("/check")
        def check():
            captured["client"] = getattr(g, "service_bridge", None)
            return {}

        with app.test_client() as client:
            client.get("/check")

        assert captured["client"] is svc

    def test_trace_id_in_g(self, svc):
        app = make_flask_app(svc)
        captured = {}

        @app.get("/trace")
        def trace_check():
            captured["trace_id"] = getattr(g, "trace_id", None)
            captured["span_id"] = getattr(g, "span_id", None)
            return {}

        with app.test_client() as client:
            client.get("/trace")

        assert captured["trace_id"] is not None
        assert captured["span_id"] is not None

    def test_no_trace_header_when_propagation_disabled(self, svc):
        app = make_flask_app(svc, propagate_trace_header=False)
        with app.test_client() as client:
            resp = client.get("/users")
        assert "x-trace-id" not in resp.headers

    def test_excludes_paths(self, svc):
        app = make_flask_app(svc, exclude_paths=("/health",))
        with app.test_client() as client:
            resp = client.get("/health")
        assert resp.status_code == 200
        # Excluded paths get the client but no span header (no x-trace-id)
        assert "x-trace-id" not in resp.headers

    def test_auto_register_calls_catalog(self, svc):
        calls: list[tuple[str, str]] = []
        svc.register_http_endpoint = lambda method, route, *args, **kwargs: calls.append((method, route))  # type: ignore[method-assign]
        app = make_flask_app(svc)
        with app.test_client() as client:
            client.get("/users")
            client.get("/users")  # second call — should not re-register

        time.sleep(0.1)
        assert len(calls) == 1
        assert calls[0] == ("GET", "/users")

    def test_auto_register_sends_route_pattern(self, svc):
        calls: list[tuple[str, str]] = []
        svc.register_http_endpoint = lambda method, route, *args, **kwargs: calls.append((method, route))  # type: ignore[method-assign]
        app = make_flask_app(svc)
        with app.test_client() as client:
            client.get("/users/42")

        time.sleep(0.1)
        assert len(calls) == 1
        # Flask URL rule should be "/users/<user_id>", not the actual path.
        assert calls[0] == ("GET", "/users/<user_id>")

    def test_auto_register_disabled(self, svc):
        calls: list[tuple[str, str]] = []
        svc.register_http_endpoint = lambda method, route, *args, **kwargs: calls.append((method, route))  # type: ignore[method-assign]
        app = make_flask_app(svc, auto_register=False)
        with app.test_client() as client:
            client.get("/users")

        time.sleep(0.1)
        assert len(calls) == 0

    def test_generates_trace_id_when_none_provided(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            resp = client.get("/users")
        trace_id = resp.headers.get("x-trace-id", "")
        assert len(trace_id) == 32  # 16 bytes hex

    def test_post_request(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            resp = client.post("/orders", json={"item": "book"})
        assert resp.status_code == 200
        assert "x-trace-id" in resp.headers

    def test_path_param_route(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            resp = client.get("/users/99")
        assert resp.status_code == 200
        assert "x-trace-id" in resp.headers

    def test_multiple_routes_registered_separately(self, svc):
        calls: list[tuple[str, str]] = []
        svc.register_http_endpoint = lambda method, route, *args, **kwargs: calls.append((method, route))  # type: ignore[method-assign]
        app = make_flask_app(svc)
        with app.test_client() as client:
            client.get("/users")
            client.post("/orders")

        time.sleep(0.1)
        methods = {method for method, _ in calls}
        assert "GET" in methods
        assert "POST" in methods

    def test_different_requests_get_different_trace_ids(self, svc):
        app = make_flask_app(svc)
        with app.test_client() as client:
            r1 = client.get("/users")
            r2 = client.get("/users")

        t1 = r1.headers.get("x-trace-id")
        t2 = r2.headers.get("x-trace-id")
        assert t1 and t2 and t1 != t2
