from __future__ import annotations

import hashlib
import hmac
from unittest.mock import AsyncMock, MagicMock, patch

from fastapi import FastAPI
from fastapi.testclient import TestClient

from app.config import Settings
from app.routers import internal_email


def _settings() -> Settings:
    return Settings(
        NEON_DATABASE_URL="postgresql://user:pass@localhost:5432/testdb",
        REDIS_URL="redis://localhost:6379/0",
        BETTER_AUTH_URL="https://frontend.example.com",
        INTERNAL_EMAIL_SHARED_SECRET="top-secret",
    )


def _signature(body: bytes, secret: str = "top-secret") -> str:
    return hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()


def _app() -> FastAPI:
    app = FastAPI()
    app.include_router(internal_email.router)
    app.dependency_overrides[internal_email.get_settings] = _settings
    return app


def test_missing_hmac_returns_401() -> None:
    body = b'{"kind":"verify-email","to":"owner@example.com","url":"https://example.com"}'

    with TestClient(_app()) as client:
        response = client.post(
            "/internal/auth/email",
            content=body,
            headers={"content-type": "application/json"},
        )

    assert response.status_code == 401
    assert response.json() == {"detail": "Invalid HMAC signature"}


def test_wrong_hmac_returns_401() -> None:
    body = b'{"kind":"verify-email","to":"owner@example.com","url":"https://example.com"}'

    with TestClient(_app()) as client:
        response = client.post(
            "/internal/auth/email",
            content=body,
            headers={
                "content-type": "application/json",
                "X-Internal-Auth": _signature(body, secret="wrong-secret"),
            },
        )

    assert response.status_code == 401
    assert response.json() == {"detail": "Invalid HMAC signature"}


def test_valid_hmac_dispatches_email() -> None:
    body = b'{"kind":"verify-email","to":"owner@example.com","name":"Owner","url":"https://example.com/verify"}'
    dispatch = AsyncMock()

    with (
        patch("app.routers.internal_email.dispatch_auth_email", dispatch),
        patch("app.routers.internal_email.ScalewayEmailClient", return_value=MagicMock()),
        TestClient(_app()) as client,
    ):
        response = client.post(
            "/internal/auth/email",
            content=body,
            headers={
                "content-type": "application/json",
                "X-Internal-Auth": _signature(body),
            },
        )

    assert response.status_code == 202
    assert response.json() == {"status": "accepted"}
    dispatch.assert_awaited_once()
    assert dispatch.await_args is not None
    _, kwargs = dispatch.await_args
    assert kwargs["kind"] == "verify-email"
    assert kwargs["to"] == "owner@example.com"
    assert kwargs["name"] == "Owner"
    assert kwargs["url"] == "https://example.com/verify"


def test_unknown_kind_with_valid_hmac_returns_400() -> None:
    body = b'{"kind":"unknown-kind","to":"owner@example.com","url":"https://example.com"}'
    dispatch = AsyncMock()

    with (
        patch("app.routers.internal_email.dispatch_auth_email", dispatch),
        TestClient(_app()) as client,
    ):
        response = client.post(
            "/internal/auth/email",
            content=body,
            headers={
                "content-type": "application/json",
                "X-Internal-Auth": _signature(body),
            },
        )

    assert response.status_code == 400
    assert "Invalid payload" in response.json()["detail"]
    dispatch.assert_not_awaited()


def test_malformed_json_returns_400() -> None:
    body = b'{"kind":"verify-email"'

    with TestClient(_app()) as client:
        response = client.post(
            "/internal/auth/email",
            content=body,
            headers={
                "content-type": "application/json",
                "X-Internal-Auth": _signature(body),
            },
        )

    assert response.status_code == 400
    assert "Invalid payload" in response.json()["detail"]


def test_internal_email_router_is_hidden_from_openapi() -> None:
    schema = _app().openapi()

    assert "/internal/auth/email" not in schema.get("paths", {})
