"""Tests for Phase 6 SP3 linkage audit fixes.

Coverage:
* FRSME-MPL013 — formalise_mou requires both parties to confirm; the
  ``confirm_mou_party`` service idempotently records each acceptance.
* FRSME-MPL010 follow-up — ``end_connection`` flips an active connection
  to ENDED and notifies both parties.
* request_connection now exposes ``was_already_active`` so the view layer
  can render an honest message instead of the misleading "request sent".
"""
from __future__ import annotations

from datetime import timedelta

import pytest
from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.utils import timezone

from apps.core.notifications.models import Notification
from apps.core.permissions.roles import UserRole
from apps.smehub.linkage import services
from apps.smehub.linkage.models import (
    Connection,
    MoU,
    MoUPartyConfirmation,
    PartnerDirectoryEntry,
)
from apps.smehub.onboarding.models import EntrepreneurProfile

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


@pytest.fixture
def admin():
    return User.objects.create_user(
        email="adm-p6@example.com", password="x",
        is_superuser=True, is_staff=True, role=UserRole.SYSTEM_ADMIN,
    )


@pytest.fixture
def entrepreneur(admin):
    user = User.objects.create_user(
        email="ent-p6@example.com", password="x", role=UserRole.ENTREPRENEUR,
    )
    profile = EntrepreneurProfile.objects.create(user=user, country="UG")
    profile.verify(by_user=admin)
    profile.save()
    return profile


@pytest.fixture
def partner_user():
    return User.objects.create_user(
        email="partner-p6@example.com", password="x", role=UserRole.PARTNER,
    )


@pytest.fixture
def verified_partner(admin, partner_user):
    entry = PartnerDirectoryEntry.objects.create(
        organisation_name="P6 Partner",
        org_type=PartnerDirectoryEntry.OrgType.NGO,
        managed_by=partner_user,
    )
    entry.verify(by_user=admin)
    entry.save()
    return entry


# ---------------------------------------------------------------------------
# FRSME-MPL013 — two-party MoU sign-off
# ---------------------------------------------------------------------------

def _build_mou(entrepreneur, verified_partner, partner_user):
    cr = services.request_connection(entrepreneur=entrepreneur, target=verified_partner)
    connection = services.accept_connection(cr, by_user=partner_user)
    return services.init_mou(connection=connection, title="P6 MoU", by_user=entrepreneur.user)


def test_formalise_blocks_when_no_confirmations(
    entrepreneur, verified_partner, partner_user, admin,
):
    mou = _build_mou(entrepreneur, verified_partner, partner_user)
    with pytest.raises(services.LinkageStateError, match="must confirm"):
        services.formalise_mou(mou, signed_file=ContentFile(b"x", name="x.pdf"), by_user=admin)


def test_formalise_blocks_when_only_one_party_confirms(
    entrepreneur, verified_partner, partner_user, admin,
):
    mou = _build_mou(entrepreneur, verified_partner, partner_user)
    services.confirm_mou_party(mou, party="entrepreneur", by_user=entrepreneur.user)
    with pytest.raises(services.LinkageStateError, match="must confirm"):
        services.formalise_mou(mou, signed_file=ContentFile(b"x", name="x.pdf"), by_user=admin)


def test_formalise_passes_after_both_parties_confirm(
    entrepreneur, verified_partner, partner_user, admin,
):
    mou = _build_mou(entrepreneur, verified_partner, partner_user)
    services.confirm_mou_party(mou, party="entrepreneur", by_user=entrepreneur.user)
    services.confirm_mou_party(mou, party="counterparty", by_user=partner_user)
    services.formalise_mou(mou, signed_file=ContentFile(b"x", name="x.pdf"), by_user=admin)
    assert MoU.objects.get(pk=mou.pk).status == MoU.Status.FORMALISED


def test_confirm_mou_party_is_idempotent(
    entrepreneur, verified_partner, partner_user,
):
    """Re-confirming by the same party refreshes the row, doesn't stack."""
    mou = _build_mou(entrepreneur, verified_partner, partner_user)
    services.confirm_mou_party(mou, party="entrepreneur", by_user=entrepreneur.user)
    services.confirm_mou_party(mou, party="entrepreneur", by_user=entrepreneur.user, note="updated")
    assert MoUPartyConfirmation.objects.filter(mou=mou).count() == 1
    assert MoUPartyConfirmation.objects.get(mou=mou).note == "updated"


def test_confirm_mou_party_rejects_invalid_party_token(
    entrepreneur, verified_partner, partner_user,
):
    mou = _build_mou(entrepreneur, verified_partner, partner_user)
    with pytest.raises(services.LinkageStateError):
        services.confirm_mou_party(mou, party="bogus", by_user=entrepreneur.user)


# ---------------------------------------------------------------------------
# end_connection
# ---------------------------------------------------------------------------

def test_end_connection_flips_status_and_notifies(
    entrepreneur, verified_partner, partner_user, admin,
):
    cr = services.request_connection(entrepreneur=entrepreneur, target=verified_partner)
    connection = services.accept_connection(cr, by_user=partner_user)

    services.end_connection(connection, by_user=admin, reason="Project complete.")

    fresh = Connection.objects.get(pk=connection.pk)
    assert fresh.status == Connection.Status.ENDED
    assert fresh.ended_at is not None
    assert "Project complete." in fresh.ended_reason
    # Both parties received a "connection ended" notification.
    assert Notification.objects.filter(
        recipient=entrepreneur.user, message__icontains="has ended",
    ).exists()


def test_end_connection_blocks_already_ended(
    entrepreneur, verified_partner, partner_user, admin,
):
    cr = services.request_connection(entrepreneur=entrepreneur, target=verified_partner)
    connection = services.accept_connection(cr, by_user=partner_user)
    services.end_connection(connection, by_user=admin)
    with pytest.raises(services.LinkageStateError):
        services.end_connection(connection, by_user=admin)


# ---------------------------------------------------------------------------
# Duplicate-suppression UX
# ---------------------------------------------------------------------------

def test_duplicate_request_marks_was_already_active(
    entrepreneur, verified_partner,
):
    first = services.request_connection(entrepreneur=entrepreneur, target=verified_partner)
    assert first.was_already_active is False
    second = services.request_connection(entrepreneur=entrepreneur, target=verified_partner)
    assert second.pk == first.pk
    assert second.was_already_active is True
