"""HTTP webhook authentication tests with HMAC-SHA256 signatures."""

import hashlib
import hmac
import json

import httpx
import pytest

from helpers import AUTH_TOKEN


def compute_signature(secret: str, body: bytes) -> str:
    """Compute X-Hub-Signature-256 HMAC-SHA256 signature."""
    mac = hmac.new(secret.encode(), body, hashlib.sha256)
    return f"sha256={mac.hexdigest()}"


@pytest.mark.asyncio
async def test_webhook_requires_http_webhook_secret_configured(ironclaw_server):
    """
    Webhook endpoint rejects requests when HTTP_WEBHOOK_SECRET is not configured.
    This tests the fail-closed security posture.
    """
    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    async with httpx.AsyncClient() as client:
        # When no webhook secret is configured on the server, all requests fail
        r = await client.post(
            f"{ironclaw_server}/webhook",
            json={"content": "test message"},
            headers=headers,
        )
        # Server should reject with 503 Service Unavailable (fail closed)
        assert r.status_code in (401, 503)


@pytest.mark.asyncio
async def test_webhook_hmac_signature_valid(ironclaw_server_with_webhook_secret):
    """Valid X-Hub-Signature-256 HMAC signature is accepted."""
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello from webhook"}
    body_bytes = json.dumps(body_data).encode()
    signature = compute_signature(secret, body_bytes)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": signature,
            },
        )
        assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}"
        resp = r.json()
        assert resp["status"] == "ok"


@pytest.mark.asyncio
async def test_webhook_invalid_hmac_signature_rejected(
    ironclaw_server_with_webhook_secret,
):
    """Invalid X-Hub-Signature-256 signature is rejected with 401."""
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello"}
    body_bytes = json.dumps(body_data).encode()
    invalid_signature = "sha256=0000000000000000000000000000000000000000000000000000000000000000"

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": invalid_signature,
            },
        )
        assert r.status_code == 401, f"Expected 401, got {r.status_code}"
        resp = r.json()
        assert resp["status"] == "error"
        assert "Invalid webhook signature" in resp.get("response", "")


@pytest.mark.asyncio
async def test_webhook_wrong_secret_rejected(ironclaw_server_with_webhook_secret):
    """Signature computed with wrong secret is rejected."""
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello"}
    body_bytes = json.dumps(body_data).encode()
    # Compute signature with wrong secret
    wrong_signature = compute_signature("wrong-secret", body_bytes)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": wrong_signature,
            },
        )
        assert r.status_code == 401
        resp = r.json()
        assert resp["status"] == "error"


@pytest.mark.asyncio
async def test_webhook_malformed_signature_rejected(
    ironclaw_server_with_webhook_secret,
):
    """Malformed X-Hub-Signature-256 header is rejected."""
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello"}
    body_bytes = json.dumps(body_data).encode()

    async with httpx.AsyncClient() as client:
        # Missing sha256= prefix
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": "deadbeef",
            },
        )
        assert r.status_code == 401


@pytest.mark.asyncio
async def test_webhook_missing_signature_header_rejected(
    ironclaw_server_with_webhook_secret,
):
    """Missing X-Hub-Signature-256 header is rejected when no body secret provided."""
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello"}
    body_bytes = json.dumps(body_data).encode()

    async with httpx.AsyncClient() as client:
        # No X-Hub-Signature-256 header and no body secret
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
            },
        )
        assert r.status_code == 401
        resp = r.json()
        assert "Webhook authentication required" in resp.get("response", "")
        assert "X-Hub-Signature-256" in resp.get("response", "")


@pytest.mark.asyncio
async def test_webhook_deprecated_body_secret_still_works(
    ironclaw_server_with_webhook_secret,
):
    """
    Deprecated: body 'secret' field still works for backward compatibility.
    This test ensures we don't break existing clients during the migration period.
    """
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    # Old-style request with secret in body
    body_data = {"content": "hello", "secret": secret}
    body_bytes = json.dumps(body_data).encode()

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
            },
        )
        # Should succeed (backward compatibility)
        assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}"
        resp = r.json()
        assert resp["status"] == "ok"


@pytest.mark.asyncio
async def test_webhook_header_takes_precedence_over_body_secret(
    ironclaw_server_with_webhook_secret,
):
    """
    When both X-Hub-Signature-256 header and body secret are provided,
    header takes precedence.
    """
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello", "secret": "wrong-secret-in-body"}
    body_bytes = json.dumps(body_data).encode()
    # Compute signature with correct secret
    signature = compute_signature(secret, body_bytes)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": signature,
            },
        )
        # Should succeed because header signature is valid (takes precedence)
        assert r.status_code == 200
        resp = r.json()
        assert resp["status"] == "ok"


@pytest.mark.asyncio
async def test_webhook_case_insensitive_header_lookup(
    ironclaw_server_with_webhook_secret,
):
    """HTTP headers are case-insensitive. Test with different cases."""
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello"}
    body_bytes = json.dumps(body_data).encode()
    signature = compute_signature(secret, body_bytes)

    async with httpx.AsyncClient() as client:
        # Try with lowercase
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "x-hub-signature-256": signature,
            },
        )
        assert r.status_code == 200


@pytest.mark.asyncio
async def test_webhook_wrong_content_type_rejected(
    ironclaw_server_with_webhook_secret,
):
    """Webhook only accepts application/json Content-Type."""
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_data = {"content": "hello"}
    body_bytes = json.dumps(body_data).encode()
    signature = compute_signature(secret, body_bytes)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "text/plain",
                "X-Hub-Signature-256": signature,
            },
        )
        assert r.status_code == 415  # Unsupported Media Type
        resp = r.json()
        assert "application/json" in resp.get("response", "")


@pytest.mark.asyncio
async def test_webhook_invalid_json_rejected(ironclaw_server_with_webhook_secret):
    """Invalid JSON in body is rejected."""
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    body_bytes = b"not valid json"
    signature = compute_signature(secret, body_bytes)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": signature,
            },
        )
        assert r.status_code == 401 or r.status_code == 400


@pytest.mark.asyncio
async def test_webhook_message_queued_for_processing(
    ironclaw_server_with_webhook_secret,
):
    """Message via webhook is queued and can be retrieved."""
    secret = ironclaw_server_with_webhook_secret["secret"]
    base_url = ironclaw_server_with_webhook_secret["url"]

    headers = {"Authorization": f"Bearer {AUTH_TOKEN}"}
    test_message = "webhook test message 12345"
    body_data = {"content": test_message}
    body_bytes = json.dumps(body_data).encode()
    signature = compute_signature(secret, body_bytes)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            f"{base_url}/webhook",
            content=body_bytes,
            headers={
                **headers,
                "Content-Type": "application/json",
                "X-Hub-Signature-256": signature,
            },
        )
        assert r.status_code == 200
        resp = r.json()
        assert resp["status"] == "ok"
        # Message ID should be present
        assert "message_id" in resp
        assert resp["message_id"] != "00000000-0000-0000-0000-000000000000"
