from datetime import timedelta

import pytest
from django.core.exceptions import PermissionDenied
from django.urls import reverse
from django.utils import timezone

from apps.accounts.models import Role
from apps.editorial import workflow
from apps.editorial.models import Article, Revision, Status
from apps.editorial.views.manage import preview_token

pytestmark = pytest.mark.django_db


def test_published_queryset_excludes_drafts_scheduled_and_trash(article_factory):
    visible = article_factory(statut=Status.PUBLIE)
    article_factory(statut=Status.BROUILLON)
    article_factory(statut=Status.PUBLIE, publie_le=timezone.now() + timedelta(days=1))
    trashed = article_factory(statut=Status.PUBLIE)
    trashed.trash()
    assert list(Article.objects.published()) == [visible]


def test_slug_is_frozen_after_publication(article_factory, editeur):
    a = article_factory(titre="Premier titre", statut=Status.BROUILLON, publie_le=None)
    assert a.slug == "premier-titre"
    a.titre = "Titre corrigé"
    a.save()
    assert a.slug == "titre-corrige"  # jamais publié : le slug suit le titre
    workflow.transition(a, "publish", editeur)
    a.titre = "Encore un autre titre"
    a.save()
    assert a.slug == "titre-corrige"  # publié : l'URL ne change plus


def test_redacteur_cannot_publish_but_can_submit(article_factory, redacteur):
    a = article_factory(auteur=redacteur, statut=Status.BROUILLON, publie_le=None)
    with pytest.raises(PermissionDenied):
        workflow.transition(a, "publish", redacteur)
    workflow.transition(a, "submit", redacteur)
    a.refresh_from_db()
    assert a.statut == Status.RELECTURE and a.soumis_le


def test_redacteur_cannot_touch_others_content(article_factory, redacteur, editeur):
    a = article_factory(auteur=editeur, statut=Status.BROUILLON, publie_le=None)
    with pytest.raises(PermissionDenied):
        workflow.transition(a, "trash", redacteur)


def test_editor_schedule_then_reject_and_archive(article_factory, editeur):
    future = timezone.now() + timedelta(hours=3)
    a = article_factory(statut=Status.RELECTURE, publie_le=future)
    msg = workflow.transition(a, "publish", editeur)
    assert "programmée" in msg and a.is_scheduled and not a.is_published
    workflow.transition(a, "archive", editeur)
    assert a.statut == Status.ARCHIVE and a.archive_le
    assert Revision.objects.filter(object_id=a.pk).count() == 2


def test_trash_restore_destroy(article_factory, editeur, admin):
    a = article_factory()
    workflow.transition(a, "trash", editeur)
    assert a.is_trashed
    with pytest.raises(PermissionDenied):
        workflow.destroy(a, editeur)  # seul l'administrateur supprime définitivement
    workflow.transition(a, "restore", editeur)
    assert not a.is_trashed
    workflow.transition(a, "trash", editeur)
    workflow.destroy(a, admin)
    assert not Article.objects.filter(pk=a.pk).exists()


def test_revert_restores_text(article_factory, editeur):
    a = article_factory(titre="V1", contenu="<p>Texte 1</p>")
    rev = workflow.record_revision(a, editeur, "save")
    a.titre, a.contenu = "V2", "<p>Texte 2</p>"
    a.save()
    workflow.revert(a, rev, editeur)
    a.refresh_from_db()
    assert a.titre == "V1" and a.contenu == "<p>Texte 1</p>"


# --- Vues de gestion -----------------------------------------------------------

def test_redacteur_creates_and_submits_article(client, redacteur, category):
    client.force_login(redacteur)
    resp = client.post(reverse("editorial:article-create"), {
        "titre": "Mon reportage", "resume": "", "contenu": "<p>Bonjour<script>alert(1)</script></p>",
        "categorie": category.pk, "submit_action": "submit", "legende_image": "",
    })
    assert resp.status_code == 302
    a = Article.objects.get(titre="Mon reportage")
    assert a.statut == Status.RELECTURE
    assert a.auteur == redacteur
    assert "<script>" not in a.contenu
    assert not a.est_a_la_une  # champ réservé aux éditeurs ignoré


def test_redacteur_cannot_edit_published(client, redacteur, article_factory):
    a = article_factory(auteur=redacteur, statut=Status.PUBLIE)
    client.force_login(redacteur)
    resp = client.post(reverse("editorial:article-edit", args=[a.pk]), {"titre": "x", "contenu": "<p>x</p>", "categorie": a.categorie_id})
    assert resp.status_code == 403


def test_list_filters_and_tabs(client, editeur, article_factory):
    article_factory(titre="Alpha publié", statut=Status.PUBLIE)
    article_factory(titre="Beta brouillon", statut=Status.BROUILLON, publie_le=None)
    client.force_login(editeur)
    html = client.get(reverse("editorial:article-list") + "?statut=brouillon").content.decode()
    assert "Beta brouillon" in html and "Alpha publié" not in html
    html = client.get(reverse("editorial:article-list") + "?q=Alpha").content.decode()
    assert "Alpha publié" in html and "Beta brouillon" not in html


def test_bulk_publish(client, editeur, article_factory):
    drafts = [article_factory(statut=Status.BROUILLON, publie_le=None) for _ in range(3)]
    client.force_login(editeur)
    client.post(reverse("editorial:article-bulk"), {"action": "publish", "ids": [d.pk for d in drafts]})
    assert Article.objects.published().count() == 3


def test_preview_link_for_unpublished(client, article_factory):
    a = article_factory(statut=Status.BROUILLON, publie_le=None, titre="Secret en préparation")
    assert client.get(reverse("public:article", args=[a.slug])).status_code == 404
    resp = client.get(reverse("public:preview", args=[preview_token(a)]))
    assert resp.status_code == 200 and "Secret en préparation" in resp.content.decode()
    assert client.get(reverse("public:preview", args=["jeton-invalide"])).status_code == 404


def test_backoffice_requires_role(client, lecteur):
    assert client.get("/admin/").status_code == 302  # anonyme → connexion
    client.force_login(lecteur)
    assert client.get("/admin/").status_code == 403


@pytest.mark.parametrize("role,url,expected", [
    (Role.REDACTEUR, "/admin/rubriques/", 403),
    (Role.REDACTEUR, "/admin/commentaires/", 403),
    (Role.EDITEUR, "/admin/commentaires/", 200),
    (Role.EDITEUR, "/admin/utilisateurs/", 403),
    (Role.EDITEUR, "/admin/parametres/", 403),
    (Role.ADMIN, "/admin/utilisateurs/", 200),
    (Role.ADMIN, "/admin/parametres/", 200),
])
def test_role_matrix(client, user_factory, role, url, expected):
    client.force_login(user_factory(role=role))
    assert client.get(url).status_code == expected


# --- Flash info ------------------------------------------------------------------

def test_flash_banner_shows_only_checked_published_articles(client, article_factory):
    article_factory(titre="Titre coché", est_flash_info=True)
    article_factory(titre="Titre non coché")
    article_factory(titre="Brouillon coché", est_flash_info=True, statut=Status.BROUILLON, publie_le=None)
    html = client.get(reverse("public:home"), HTTP_USER_AGENT="Mozilla/5.0").content.decode()
    banner = html[html.index("Flash info"):html.index("</ul>", html.index("Flash info"))]
    assert "Titre coché" in banner
    assert "Titre non coché" not in banner and "Brouillon coché" not in banner


def test_flash_banner_hidden_when_nothing_checked(client, article_factory):
    article_factory()
    html = client.get(reverse("public:home"), HTTP_USER_AGENT="Mozilla/5.0").content.decode()
    assert "Flash info</span>" not in html


def test_redacteur_cannot_set_flash_info(client, redacteur, category):
    client.force_login(redacteur)
    client.post(reverse("editorial:article-create"), {
        "titre": "Tentative", "contenu": "<p>x</p>", "categorie": category.pk, "est_flash_info": "on", "submit_action": "save",
    })
    assert Article.objects.get(titre="Tentative").est_flash_info is False


def test_editor_sets_flash_from_form_and_bulk(client, editeur, category, article_factory):
    client.force_login(editeur)
    client.post(reverse("editorial:article-create"), {
        "titre": "Urgent", "contenu": "<p>x</p>", "categorie": category.pk, "est_flash_info": "on", "submit_action": "publish",
    })
    assert Article.objects.get(titre="Urgent").est_flash_info is True

    others = [article_factory() for _ in range(2)]
    client.post(reverse("editorial:article-bulk"), {"action": "flash_on", "ids": [a.pk for a in others]})
    assert Article.objects.filter(est_flash_info=True).count() == 3
    client.post(reverse("editorial:article-bulk"), {"action": "flash_off", "ids": [a.pk for a in others]})
    assert Article.objects.filter(est_flash_info=True).count() == 1


def test_redacteur_cannot_bulk_flash(client, redacteur, article_factory):
    a = article_factory(auteur=redacteur)
    client.force_login(redacteur)
    assert client.post(reverse("editorial:article-bulk"), {"action": "flash_on", "ids": [a.pk]}).status_code == 403
