"""Tests for HTTP endpoint routes — CRUD, no-leak, /test, LAN policy."""

from unittest.mock import MagicMock

import httpx
import pytest
import respx
from fastapi import FastAPI
from fastapi.testclient import TestClient

from ledgrab.api import dependencies as deps
from ledgrab.api.routes.http_endpoints import router
from ledgrab.storage.http_endpoint_store import HTTPEndpointStore


@pytest.fixture
def _route_db(tmp_path):
    from ledgrab.storage.database import Database

    db = Database(tmp_path / "test.db")
    yield db
    db.close()


@pytest.fixture
def http_store(_route_db):
    return HTTPEndpointStore(_route_db)


@pytest.fixture
def client(http_store):
    app = FastAPI()
    app.include_router(router)

    from ledgrab.api.auth import verify_api_key

    app.dependency_overrides[verify_api_key] = lambda: "test-user"
    app.dependency_overrides[deps.get_http_endpoint_store] = lambda: http_store

    # Stub fire_entity_event's processor_manager
    deps._deps["processor_manager"] = MagicMock()

    return TestClient(app, raise_server_exceptions=False)


# ---------------------------------------------------------------------------
# CRUD shape + no-leak guarantee
# ---------------------------------------------------------------------------


class TestCRUDRoutes:
    def test_create_and_list(self, client):
        r = client.post(
            "/api/v1/http/endpoints",
            json={
                "name": "Plex",
                "url": "https://example.com/status/sessions",
                "auth_token": "very-secret",
                "headers": {"Accept": "application/json"},
            },
        )
        assert r.status_code == 201
        body = r.json()
        assert body["id"].startswith("htep_")
        assert body["auth_token_set"] is True
        assert "very-secret" not in r.text

        lst = client.get("/api/v1/http/endpoints").json()
        assert lst["count"] == 1

    def test_response_never_exposes_auth_token(self, client):
        """Defence-in-depth: scan every CRUD response for the token string."""
        token = "uniq-token-12345-no-collisions"
        post = client.post(
            "/api/v1/http/endpoints",
            json={
                "name": "x",
                "url": "https://example.com",
                "auth_token": token,
            },
        )
        assert post.status_code == 201
        endpoint_id = post.json()["id"]

        get = client.get(f"/api/v1/http/endpoints/{endpoint_id}")
        put = client.put(f"/api/v1/http/endpoints/{endpoint_id}", json={"name": "renamed"})
        lst = client.get("/api/v1/http/endpoints")

        for resp in (post, get, put, lst):
            assert token not in resp.text, f"token leaked in {resp.url}"

    def test_method_allowlist_at_schema_layer(self, client):
        r = client.post(
            "/api/v1/http/endpoints",
            json={"name": "x", "url": "https://example.com", "method": "POST"},
        )
        assert r.status_code == 422

    def test_crlf_in_header_rejected(self, client):
        r = client.post(
            "/api/v1/http/endpoints",
            json={
                "name": "x",
                "url": "https://example.com",
                "headers": {"X-Inject": "value\r\nX-Smuggle: yes"},
            },
        )
        assert r.status_code == 422

    def test_invalid_header_name_rejected(self, client):
        r = client.post(
            "/api/v1/http/endpoints",
            json={
                "name": "x",
                "url": "https://example.com",
                "headers": {"Bad Name With Space": "v"},
            },
        )
        assert r.status_code == 422


# ---------------------------------------------------------------------------
# /test one-shot endpoint + LAN policy
# ---------------------------------------------------------------------------


class TestOneShotEndpoint:
    @respx.mock
    def test_test_success(self, client):
        respx.get("https://example.com/status").mock(
            return_value=httpx.Response(200, json={"ok": True})
        )
        r = client.post(
            "/api/v1/http/endpoints/test",
            json={"url": "https://example.com/status"},
        )
        assert r.status_code == 200
        body = r.json()
        assert body["success"] is True
        assert body["status_code"] == 200
        assert body["body_json"] == {"ok": True}

    @respx.mock
    def test_test_sends_auth_header(self, client):
        route = respx.get("https://example.com/secure").mock(
            return_value=httpx.Response(200, json={})
        )
        client.post(
            "/api/v1/http/endpoints/test",
            json={
                "url": "https://example.com/secure",
                "auth_token": "tok-abc",
            },
        )
        sent = route.calls.last.request
        assert sent.headers.get("authorization") == "Bearer tok-abc"

    def test_test_rejects_loopback_url(self, client):
        # Loopback is always blocked, even with the relaxed polling policy.
        r = client.post(
            "/api/v1/http/endpoints/test",
            json={"url": "http://127.0.0.1:9000/"},
        )
        assert r.status_code == 400

    def test_test_rejects_metadata_link_local(self, client):
        # AWS/GCP metadata at 169.254.169.254 stays blocked.
        r = client.post(
            "/api/v1/http/endpoints/test",
            json={"url": "http://169.254.169.254/latest/meta-data/"},
        )
        assert r.status_code == 400

    @respx.mock
    def test_test_allows_private_lan_ip(self, client):
        """Relaxed polling policy MUST permit private IPs — primary use case."""
        respx.get("http://192.168.1.100/status").mock(
            return_value=httpx.Response(200, json={"ok": True})
        )
        r = client.post(
            "/api/v1/http/endpoints/test",
            json={"url": "http://192.168.1.100/status"},
        )
        assert r.status_code == 200
        assert r.json()["success"] is True
