import hashlib
import hmac
from types import SimpleNamespace
from unittest.mock import patch

import httpx
import pytest
from fastapi import FastAPI
from httpx import ASGITransport

from app.db.session import get_session
from app.routers.whatsapp import _verify_signature, router


def compute_signature(body: bytes, secret: str) -> str:
    digest = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return f"sha256={digest}"


@pytest.fixture
def webhook_app(mock_session):
    app = FastAPI()
    app.include_router(router, prefix="/api/v1")

    async def override_get_session():
        return mock_session

    app.dependency_overrides[get_session] = override_get_session
    yield app
    app.dependency_overrides.clear()


@pytest.fixture
def whatsapp_settings() -> SimpleNamespace:
    return SimpleNamespace(
        META_WEBHOOK_VERIFY_TOKEN="test-verify-token",
        META_APP_SECRET="test-app-secret",
    )


@pytest.mark.asyncio(loop_scope="session")
async def test_verify_webhook_valid_token(webhook_app, whatsapp_settings) -> None:
    transport = ASGITransport(app=webhook_app)

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.get(
                "/api/v1/webhooks/whatsapp",
                params={
                    "hub.mode": "subscribe",
                    "hub.verify_token": whatsapp_settings.META_WEBHOOK_VERIFY_TOKEN,
                    "hub.challenge": "challenge-token",
                },
            )

    assert response.status_code == 200
    assert response.text == "challenge-token"


@pytest.mark.asyncio(loop_scope="session")
async def test_verify_webhook_invalid_token(webhook_app, whatsapp_settings) -> None:
    transport = ASGITransport(app=webhook_app)

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.get(
                "/api/v1/webhooks/whatsapp",
                params={
                    "hub.mode": "subscribe",
                    "hub.verify_token": "wrong-token",
                    "hub.challenge": "challenge-token",
                },
            )

    assert response.status_code == 403


@pytest.mark.asyncio(loop_scope="session")
async def test_verify_webhook_missing_params(webhook_app, whatsapp_settings) -> None:
    transport = ASGITransport(app=webhook_app)
    invalid_requests = [
        {"hub.verify_token": whatsapp_settings.META_WEBHOOK_VERIFY_TOKEN, "hub.challenge": "abc"},
        {"hub.mode": "subscribe", "hub.challenge": "abc"},
        {
            "hub.mode": "subscribe",
            "hub.verify_token": whatsapp_settings.META_WEBHOOK_VERIFY_TOKEN,
        },
    ]

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            for params in invalid_requests:
                response = await client.get("/api/v1/webhooks/whatsapp", params=params)
                assert response.status_code == 400


@pytest.mark.asyncio(loop_scope="session")
async def test_signature_verification_valid(webhook_app, whatsapp_settings) -> None:
    body = b'{"entry": []}'
    signature = compute_signature(body, whatsapp_settings.META_APP_SECRET)
    transport = ASGITransport(app=webhook_app)

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        assert _verify_signature(body, signature) is True

        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/v1/webhooks/whatsapp",
                content=body,
                headers={"X-Hub-Signature-256": signature},
            )

    assert response.status_code == 200


@pytest.mark.asyncio(loop_scope="session")
async def test_signature_verification_tampered(webhook_app, whatsapp_settings) -> None:
    original_body = b'{"entry": []}'
    tampered_body = b'{"entry": [{"id": "tampered"}]}'
    signature = compute_signature(original_body, whatsapp_settings.META_APP_SECRET)
    transport = ASGITransport(app=webhook_app)

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        assert _verify_signature(tampered_body, signature) is False

        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/v1/webhooks/whatsapp",
                content=tampered_body,
                headers={"X-Hub-Signature-256": signature},
            )

    assert response.status_code == 401


@pytest.mark.asyncio(loop_scope="session")
async def test_signature_verification_missing_header(webhook_app, whatsapp_settings) -> None:
    body = b'{"entry": []}'
    transport = ASGITransport(app=webhook_app)

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/v1/webhooks/whatsapp", content=body)

    assert response.status_code == 401


@pytest.mark.asyncio(loop_scope="session")
async def test_signature_verification_empty_body(webhook_app, whatsapp_settings) -> None:
    signature = compute_signature(b"", whatsapp_settings.META_APP_SECRET)
    transport = ASGITransport(app=webhook_app)

    with patch("app.routers.whatsapp.get_settings", return_value=whatsapp_settings):
        async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/v1/webhooks/whatsapp",
                content=b"",
                headers={"X-Hub-Signature-256": signature},
            )

    assert response.status_code == 401
