"""G18 — Report webhook distribution.

Verifies the distribute_report → deliver_webhook fan-out, HMAC signing,
delivery row idempotency, and the global-vs-template-specific routing.
"""
from __future__ import annotations

import hashlib
import hmac
from datetime import date, timedelta

import pytest
from django.contrib.auth import get_user_model

from apps.mel.indicators.models import LogFrame
from apps.mel.reports.models import (
    Report,
    ReportDistributionWebhook,
    ReportStatus,
    ReportTemplate,
    ReportWebhookDelivery,
    WebhookDeliveryStatus,
)
from apps.mel.reports.tasks import deliver_webhook


User = get_user_model()
pytestmark = pytest.mark.django_db


@pytest.fixture
def template():
    lf = LogFrame.objects.create(name="LF G18", slug="lf-g18")
    return ReportTemplate.objects.create(
        name="G18 template", slug="g18-template", logframe=lf,
    )


@pytest.fixture
def report(template):
    today = date.today()
    return Report.objects.create(
        template=template,
        period_label="2026Q1",
        period_start=today - timedelta(days=30),
        period_end=today,
        status=ReportStatus.PUBLISHED,
        consolidated_data={"indicator_rollup": {"counts": {"green": 1}}},
    )


def test_deliver_webhook_posts_signed_payload(template, report, monkeypatch):
    hook = ReportDistributionWebhook.objects.create(
        name="ops-slack", url="https://example.invalid/webhook",
        template=template, secret="topsecret",
    )

    captured = {}

    class _Resp:
        status_code = 200
        text = "ok"

    def _post(url, data=None, headers=None, timeout=None):
        captured["url"] = url
        captured["data"] = data
        captured["headers"] = headers
        return _Resp()

    import apps.mel.reports.tasks as tasks_mod
    monkeypatch.setattr(tasks_mod.requests, "post", _post)

    result = deliver_webhook.apply(args=(hook.pk, report.pk)).get()
    assert result == "ok"
    sig = captured["headers"]["X-IILMP-Signature"]
    assert sig.startswith("sha256=")
    expected = hmac.new(b"topsecret", captured["data"], hashlib.sha256).hexdigest()
    assert sig == f"sha256={expected}"

    delivery = ReportWebhookDelivery.objects.get(webhook=hook, report=report)
    assert delivery.status == WebhookDeliveryStatus.SUCCESS
    assert delivery.last_response_status == 200
    assert delivery.attempts == 1


def test_deliver_webhook_marks_failure_on_4xx(template, report, monkeypatch):
    hook = ReportDistributionWebhook.objects.create(
        name="bad-hook", url="https://example.invalid/webhook", template=template,
    )

    class _Resp:
        status_code = 404
        text = "not found"

    import apps.mel.reports.tasks as tasks_mod
    monkeypatch.setattr(
        tasks_mod.requests, "post",
        lambda *a, **kw: _Resp(),
    )
    result = deliver_webhook.apply(args=(hook.pk, report.pk)).get()
    assert "failed" in result
    delivery = ReportWebhookDelivery.objects.get(webhook=hook, report=report)
    assert delivery.status == WebhookDeliveryStatus.FAILED


def test_webhook_delivery_idempotent_on_unique_together(template, report, monkeypatch):
    hook = ReportDistributionWebhook.objects.create(
        name="idem-hook", url="https://example.invalid/webhook", template=template,
    )

    class _Resp:
        status_code = 200
        text = "ok"

    import apps.mel.reports.tasks as tasks_mod
    monkeypatch.setattr(tasks_mod.requests, "post", lambda *a, **kw: _Resp())

    deliver_webhook.apply(args=(hook.pk, report.pk)).get()
    deliver_webhook.apply(args=(hook.pk, report.pk)).get()
    assert ReportWebhookDelivery.objects.filter(webhook=hook, report=report).count() == 1


def test_inactive_or_unbound_webhooks_filter_correctly(template):
    other_template = ReportTemplate.objects.create(
        name="other", slug="other-t",
    )
    # global active
    g = ReportDistributionWebhook.objects.create(name="g", url="https://example.invalid", template=None)
    # template-specific active
    ReportDistributionWebhook.objects.create(name="t1", url="https://example.invalid", template=template)
    # different template
    ReportDistributionWebhook.objects.create(name="t2", url="https://example.invalid", template=other_template)
    # inactive
    ReportDistributionWebhook.objects.create(name="off", url="https://example.invalid", is_active=False)

    from django.db.models import Q
    matched = ReportDistributionWebhook.objects.filter(is_active=True).filter(
        Q(template=template) | Q(template__isnull=True)
    )
    assert {w.name for w in matched} == {"g", "t1"}
