import json

import pytest
from django.contrib.auth import get_user_model
from django.test import Client as DjangoClient
from rest_framework.test import APIClient

from apps.analytics.models import AccessLog
from apps.disseminations.models import Campaign, Post
from apps.disseminations.services import ensure_tracking_link
from apps.tools.models import URL


@pytest.fixture
def user(db):
    return get_user_model().objects.create_superuser(email="diss@example.com", password="pass123")


@pytest.fixture
def client(user):
    c = APIClient()
    c.force_authenticate(user=user)
    return c


@pytest.mark.django_db
class TestTrackingLink:
    def test_tracking_url_nullable(self):
        post = Post.objects.create(title="No link")
        assert post.tracking_url is None

    def test_ensure_tracking_link_no_url_returns_none(self):
        post = Post.objects.create(title="No link")
        assert ensure_tracking_link(post) is None
        assert post.tracking_url is None

    def test_ensure_tracking_link_appends_utm(self):
        campaign = Campaign.objects.create(name="Spring Launch")
        post = Post.objects.create(title="Hello", link_url="https://example.com/landing", campaign=campaign)
        url = ensure_tracking_link(post)
        assert url is not None
        assert url.short_code
        assert f"utm_content={post.id}" in url.original_url
        assert "utm_medium=social" in url.original_url
        assert "utm_campaign=spring-launch" in url.original_url

    def test_ensure_tracking_link_idempotent_keeps_code(self):
        post = Post.objects.create(title="Hi", link_url="https://example.com/a")
        first = ensure_tracking_link(post)
        code = first.short_code
        post.link_url = "https://example.com/b"
        post.save(update_fields=["link_url"])
        second = ensure_tracking_link(post)
        assert second.pk == first.pk
        assert second.short_code == code
        assert "example.com/b" in second.original_url

    def test_preserves_author_supplied_utm(self):
        post = Post.objects.create(link_url="https://example.com/x?utm_source=newsletter")
        url = ensure_tracking_link(post)
        assert "utm_source=newsletter" in url.original_url


@pytest.mark.django_db
class TestActions:
    def test_generate_tracking_link_400_without_link(self, client):
        post = Post.objects.create(title="No link")
        res = client.post(f"/api/disseminations/posts/{post.id}/generate_tracking_link/")
        assert res.status_code == 400

    def test_generate_tracking_link_happy(self, client):
        post = Post.objects.create(title="Hi", link_url="https://example.com/landing")
        res = client.post(f"/api/disseminations/posts/{post.id}/generate_tracking_link/")
        assert res.status_code == 200
        assert res.data["tracking_short_code"]
        post.refresh_from_db()
        assert f"utm_content={str(post.id).replace('-', '')}" in res.data["tracking_target_url"]

    def test_publish_now_auto_generates(self, client):
        post = Post.objects.create(title="Hi", link_url="https://example.com/landing")
        res = client.post(f"/api/disseminations/posts/{post.id}/publish_now/")
        assert res.status_code == 200
        assert res.data["status"] == "published"
        assert res.data["tracking_short_code"]

    def test_post_link_stats_shape(self, client):
        post = Post.objects.create(title="Hi", link_url="https://example.com/landing")
        client.post(f"/api/disseminations/posts/{post.id}/generate_tracking_link/")
        res = client.get(f"/api/disseminations/posts/{post.id}/link_stats/")
        assert res.status_code == 200
        assert "clicks" in res.data and "downstream" in res.data
        assert "total_clicks" in res.data["clicks"]
        assert "total_pageviews" in res.data["downstream"]

    def test_campaign_link_stats_aggregates(self, client):
        campaign = Campaign.objects.create(name="C1")
        post = Post.objects.create(link_url="https://example.com/a", campaign=campaign)
        client.post(f"/api/disseminations/posts/{post.id}/generate_tracking_link/")
        res = client.get(f"/api/disseminations/campaigns/{campaign.id}/link_stats/")
        assert res.status_code == 200
        assert res.data["clicks"]["total_clicks"] == 0  # no clicks yet


@pytest.mark.django_db
class TestClickRecording:
    @pytest.fixture(autouse=True)
    def eager(self, settings):
        # AccessLog writes are dispatched to the background pool; run inline
        # so the assertions below see the row.
        settings.BACKGROUND_TASKS_EAGER = True

    def test_click_records_urlclick_and_accesslog(self, client):
        post = Post.objects.create(title="Hi", link_url="https://example.com/landing")
        ensure_tracking_link(post)
        code = post.tracking_url.short_code

        res = client.get(f"/api/urls/{code}/", HTTP_USER_AGENT="Mozilla/5.0 (iPhone)")
        assert res.status_code in (301, 302)

        url = URL.objects.get(short_code=code)
        assert url.clicks == 1
        assert url.click_logs.count() == 1

        log = AccessLog.objects.filter(resource_type="shorten_url", resource_id=code).first()
        assert log is not None
        assert log.device == "mobile"

        stats = client.get(f"/api/disseminations/posts/{post.id}/link_stats/")
        assert stats.data["clicks"]["total_clicks"] >= 1

    def test_downstream_attribution_by_utm_content(self, client):
        """A pageview on the destination (carrying utm_content) attributes to the post."""
        from apps.analytics.models import Site

        post = Post.objects.create(title="Promo", link_url="https://shop.example.com/sale")
        # Generate via API so the id is DB-loaded (dashed) — exercises the normalization.
        gen = client.post(f"/api/disseminations/posts/{post.id}/generate_tracking_link/")
        target = gen.data["tracking_target_url"]

        site = Site.objects.create(name="Shop", domain="shop.example.com")
        body = {"k": site.public_key, "t": "pageview", "u": target, "ti": "Sale"}
        import json
        res = client.post("/api/analytics/collect", data=json.dumps(body), content_type="text/plain")
        assert res.status_code == 200

        stats = client.get(f"/api/disseminations/posts/{post.id}/link_stats/")
        assert stats.data["downstream"]["total_pageviews"] >= 1


@pytest.mark.django_db
class TestPublicShortLink:
    def test_anonymous_can_follow_short_link(self, client):
        post = Post.objects.create(title="Hi", link_url="https://example.com/landing")
        ensure_tracking_link(post)
        code = post.tracking_url.short_code
        r = DjangoClient().get(f"/api/urls/{code}/", HTTP_HOST="localhost")
        assert r.status_code in (301, 302)  # anonymous visitors can follow the link

    def test_tracking_link_is_absolute_public_url(self, client):
        post = Post.objects.create(title="Hi", link_url="https://example.com/landing")
        res = client.post(f"/api/disseminations/posts/{post.id}/generate_tracking_link/")
        assert res.data["tracking_link"].endswith(f"/api/urls/{res.data['tracking_short_code']}/")

    def test_non_http_scheme_rejected_on_create(self, client):
        res = client.post(
            "/api/urls/",
            data=json.dumps({"original_url": "javascript:alert(1)"}),
            content_type="application/json",
        )
        assert res.status_code == 400
