"""S1.1 — CRM Contact / Customer Master backend tests.

Covers:
- ContactTag model + service (create/update/delete)
- Contact model + service (create/update/delete/merge)
- Contact selectors (list, by_type, search, with_tags)
- Contact API (CRUD, search, merge)
- Tag API (CRUD)
- Contact child resources (phones, emails, addresses)
- Permission codes registered
"""

from __future__ import annotations

import pytest

from simorgh.apps.crm.models import Contact, ContactAddress, ContactEmail, ContactPhone, ContactTag
from simorgh.apps.crm import services, selectors
from simorgh.apps.iam.registry import sync_registry_to_db
from simorgh.apps.memberships.models import Membership


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def alice_admin_membership(alice, tenant_acme, acme_tree, role_admin) -> Membership:
    """Alice with full perms in tenant_acme."""
    m = Membership.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        role=role_admin,
    )
    m.users.add(alice)
    return m


@pytest.fixture
def tag_vip(tenant_acme) -> ContactTag:
    return ContactTag.objects.create(tenant=tenant_acme, name="VIP", color="#FFD700")


@pytest.fixture
def contact_alice_company(tenant_acme, acme_tree) -> Contact:
    return Contact.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        name="Acme Corp",
        type="company",
        is_customer=True,
    )


@pytest.fixture
def contact_person(tenant_acme, acme_tree) -> Contact:
    return Contact.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        name="John Doe",
        type="person",
    )


# ---------------------------------------------------------------------------
# ContactTag model / services
# ---------------------------------------------------------------------------

@pytest.mark.django_db
class TestContactTagServices:
    def test_create_tag(self, tenant_acme):
        tag = services.create_tag(tenant_id=tenant_acme.pk, name="VIP", color="#F00")
        assert tag.pk is not None
        assert tag.name == "VIP"
        assert tag.color == "#F00"
        assert tag.tenant_id == tenant_acme.pk

    def test_create_tag_strips_whitespace(self, tenant_acme):
        tag = services.create_tag(tenant_id=tenant_acme.pk, name="  beta  ")
        assert tag.name == "beta"

    def test_unique_tag_name_per_tenant(self, tenant_acme):
        services.create_tag(tenant_id=tenant_acme.pk, name="Dup")
        with pytest.raises(Exception):
            services.create_tag(tenant_id=tenant_acme.pk, name="Dup")

    def test_update_tag(self, tenant_acme, tag_vip):
        updated = services.update_tag(tag_vip, name="Premium", color="#00F")
        assert updated.name == "Premium"
        assert updated.color == "#00F"

    def test_delete_tag(self, tenant_acme, tag_vip):
        pk = tag_vip.pk
        services.delete_tag(tag_vip)
        assert not ContactTag.objects.filter(pk=pk).exists()


# ---------------------------------------------------------------------------
# Contact model / services
# ---------------------------------------------------------------------------

@pytest.mark.django_db
class TestContactServices:
    def test_create_contact_minimal(self, tenant_acme, acme_tree):
        contact = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Test Corp",
            type="company",
        )
        assert contact.pk is not None
        assert contact.name == "Test Corp"
        assert contact.type == "company"
        assert contact.tenant_id == tenant_acme.pk

    def test_create_contact_with_children(self, tenant_acme, acme_tree):
        contact = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Jane Smith",
            phones=[{"type": "mobile", "number": "+1234567890", "is_default": True}],
            emails=[{"type": "work", "address": "jane@example.com", "is_default": True}],
            addresses=[{"type": "billing", "street": "123 Main St", "city": "NY", "country": "US", "is_default": True}],
        )
        assert contact.phones.count() == 1
        assert contact.emails.count() == 1
        assert contact.addresses.count() == 1
        assert contact.phones.first().number == "+1234567890"

    def test_update_contact(self, tenant_acme, acme_tree, contact_person):
        updated = services.update_contact(
            contact_person,
            name="Jane Doe",
            is_customer=True,
            website="https://jane.dev",
        )
        assert updated.name == "Jane Doe"
        assert updated.is_customer is True
        assert updated.website == "https://jane.dev"

    def test_soft_delete_contact(self, tenant_acme, acme_tree, contact_person):
        pk = contact_person.pk
        services.delete_contact(contact_person)
        # Still exists in DB
        assert Contact.objects.filter(pk=pk).exists()
        # But deleted_at is set (soft delete)
        assert Contact.objects.get(pk=pk).deleted_at is not None

    def test_add_remove_tag(self, tenant_acme, acme_tree, contact_person, tag_vip):
        services.add_tag_to_contact(contact_person, tag_vip)
        assert contact_person.tags.filter(pk=tag_vip.pk).exists()
        services.remove_tag_from_contact(contact_person, tag_vip)
        assert not contact_person.tags.filter(pk=tag_vip.pk).exists()

    def test_merge_contacts(self, tenant_acme, acme_tree):
        primary = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Alice",
            phones=[{"type": "mobile", "number": "+111"}],
        )
        duplicate = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Alice Duplicate",
            phones=[{"type": "mobile", "number": "+222"}],
            emails=[{"type": "work", "address": "alice@example.com"}],
        )
        result = services.merge_contacts(primary=primary, duplicate=duplicate)
        # Primary survives, duplicate is soft-deleted
        assert result.pk == primary.pk
        assert Contact.objects.get(pk=duplicate.pk).deleted_at is not None
        # All phones transferred to primary
        assert result.phones.count() == 2

    def test_merge_same_contact_raises(self, tenant_acme, acme_tree, contact_person):
        with pytest.raises(ValueError, match="Cannot merge a contact with itself"):
            services.merge_contacts(primary=contact_person, duplicate=contact_person)

    def test_merge_different_tenants_raises(self, tenant_acme, tenant_globex, acme_tree):
        c1 = Contact.objects.create(
            tenant=tenant_acme,
            organization_node=acme_tree["root"],
            name="C1",
            type="person",
        )
        globex_node = __import__("simorgh.apps.organizations.services", fromlist=["create_node"]).create_node(
            tenant_id=tenant_globex.pk, name="Globex HQ"
        )
        c2 = Contact.objects.create(
            tenant=tenant_globex,
            organization_node=globex_node,
            name="C2",
            type="person",
        )
        with pytest.raises(ValueError, match="different tenants"):
            services.merge_contacts(primary=c1, duplicate=c2)


# ---------------------------------------------------------------------------
# Selectors
# ---------------------------------------------------------------------------

@pytest.mark.django_db
class TestContactSelectors:
    def test_contacts_for_tenant(self, tenant_acme, acme_tree, contact_person, contact_alice_company):
        qs = selectors.contacts_for_tenant(tenant_acme.pk)
        assert qs.filter(pk=contact_person.pk).exists()
        assert qs.filter(pk=contact_alice_company.pk).exists()

    def test_contacts_by_type(self, tenant_acme, acme_tree, contact_person, contact_alice_company):
        persons = selectors.contacts_by_type(tenant_acme.pk, "person")
        assert persons.filter(pk=contact_person.pk).exists()
        assert not persons.filter(pk=contact_alice_company.pk).exists()

    def test_contacts_search_by_name(self, tenant_acme, acme_tree, contact_person):
        qs = selectors.contacts_search(tenant_acme.pk, "John")
        assert qs.filter(pk=contact_person.pk).exists()

    def test_contacts_search_by_phone(self, tenant_acme, acme_tree):
        c = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Bob",
            phones=[{"type": "mobile", "number": "+9999999"}],
        )
        qs = selectors.contacts_search(tenant_acme.pk, "9999999")
        assert qs.filter(pk=c.pk).exists()

    def test_contacts_with_tags(self, tenant_acme, acme_tree, contact_person, tag_vip):
        services.add_tag_to_contact(contact_person, tag_vip)
        qs = selectors.contacts_with_tags(tenant_acme.pk, [tag_vip.pk])
        assert qs.filter(pk=contact_person.pk).exists()

    def test_tags_for_tenant(self, tenant_acme, tag_vip):
        tags = selectors.tags_for_tenant(tenant_acme.pk)
        assert tags.filter(pk=tag_vip.pk).exists()


# ---------------------------------------------------------------------------
# Contact API
# ---------------------------------------------------------------------------

@pytest.mark.django_db
class TestContactAPI:
    BASE = "/api/v1/crm/contacts/"

    def test_list_contacts_requires_auth(self, api_client, tenant_acme):
        resp = api_client.get(self.BASE, HTTP_X_TENANT=tenant_acme.slug)
        assert resp.status_code in (401, 403)

    def test_list_contacts(self, api_client, alice, alice_admin_membership, tenant_acme, contact_person):
        api_client.force_login(alice)
        resp = api_client.get(self.BASE, HTTP_X_TENANT=tenant_acme.slug)
        assert resp.status_code == 200
        ids = [c["id"] for c in resp.json()]
        assert contact_person.pk in ids

    def test_create_contact(self, api_client, alice, alice_admin_membership, tenant_acme, acme_tree):
        api_client.force_login(alice)
        resp = api_client.post(
            self.BASE,
            data={
                "name": "New Contact",
                "type": "person",
                "organization_node_id": acme_tree["root"].pk,
                "is_customer": True,
                "phones": [{"type": "mobile", "number": "+989001", "is_default": True}],
                "emails": [{"type": "work", "address": "new@example.com", "is_default": True}],
            },
            format="json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 201
        body = resp.json()
        assert body["name"] == "New Contact"
        assert body["is_customer"] is True
        assert len(body["phones"]) == 1
        assert len(body["emails"]) == 1

    def test_retrieve_contact_detail(self, api_client, alice, alice_admin_membership, tenant_acme, contact_person):
        api_client.force_login(alice)
        resp = api_client.get(
            f"{self.BASE}{contact_person.public_id}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 200
        assert resp.json()["id"] == contact_person.pk

    def test_update_contact(self, api_client, alice, alice_admin_membership, tenant_acme, contact_person):
        api_client.force_login(alice)
        resp = api_client.patch(
            f"{self.BASE}{contact_person.public_id}/",
            data={"name": "Updated Name", "is_customer": True},
            format="json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 200
        assert resp.json()["name"] == "Updated Name"

    def test_delete_contact_soft(self, api_client, alice, alice_admin_membership, tenant_acme, contact_person):
        api_client.force_login(alice)
        resp = api_client.delete(
            f"{self.BASE}{contact_person.public_id}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 204
        # Soft-delete: record still in DB
        assert Contact.objects.filter(pk=contact_person.pk).exists()
        assert Contact.objects.get(pk=contact_person.pk).deleted_at is not None

    def test_search_contacts(self, api_client, alice, alice_admin_membership, tenant_acme, contact_person):
        api_client.force_login(alice)
        resp = api_client.get(
            f"{self.BASE}search/",
            {"q": "John"},
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 200
        ids = [c["id"] for c in resp.json()]
        assert contact_person.pk in ids

    def test_filter_by_type(self, api_client, alice, alice_admin_membership, tenant_acme, contact_person, contact_alice_company):
        api_client.force_login(alice)
        resp = api_client.get(self.BASE, {"type": "company"}, HTTP_X_TENANT=tenant_acme.slug)
        assert resp.status_code == 200
        ids = [c["id"] for c in resp.json()]
        assert contact_alice_company.pk in ids
        assert contact_person.pk not in ids

    def test_merge_contacts_api(self, api_client, alice, alice_admin_membership, tenant_acme, acme_tree):
        api_client.force_login(alice)
        primary = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Primary",
        )
        dup = services.create_contact(
            tenant_id=tenant_acme.pk,
            organization_node_id=acme_tree["root"].pk,
            name="Dup",
        )
        resp = api_client.post(
            f"{self.BASE}{primary.public_id}/merge/",
            data={"duplicate_id": str(dup.public_id)},
            format="json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 200
        assert Contact.objects.get(pk=dup.pk).deleted_at is not None


# ---------------------------------------------------------------------------
# Tag API
# ---------------------------------------------------------------------------

@pytest.mark.django_db
class TestTagAPI:
    BASE = "/api/v1/crm/tags/"

    def test_list_tags(self, api_client, alice, alice_admin_membership, tenant_acme, tag_vip):
        api_client.force_login(alice)
        resp = api_client.get(self.BASE, HTTP_X_TENANT=tenant_acme.slug)
        assert resp.status_code == 200
        ids = [t["id"] for t in resp.json()]
        assert tag_vip.pk in ids

    def test_create_tag(self, api_client, alice, alice_admin_membership, tenant_acme):
        api_client.force_login(alice)
        resp = api_client.post(
            self.BASE,
            data={"name": "New Tag", "color": "#ABC"},
            format="json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 201
        assert resp.json()["name"] == "New Tag"

    def test_update_tag(self, api_client, alice, alice_admin_membership, tenant_acme, tag_vip):
        api_client.force_login(alice)
        resp = api_client.patch(
            f"{self.BASE}{tag_vip.pk}/",
            data={"name": "Premium"},
            format="json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 200
        assert resp.json()["name"] == "Premium"

    def test_delete_tag(self, api_client, alice, alice_admin_membership, tenant_acme, tag_vip):
        api_client.force_login(alice)
        resp = api_client.delete(
            f"{self.BASE}{tag_vip.pk}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
        assert resp.status_code == 204
        assert not ContactTag.objects.filter(pk=tag_vip.pk).exists()


# ---------------------------------------------------------------------------
# Permissions registered
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_crm_permissions_registered():
    sync_registry_to_db()
    from simorgh.apps.iam.models import Permission
    expected = [
        "crm.contact.view", "crm.contact.add", "crm.contact.change",
        "crm.contact.delete", "crm.contact.merge", "crm.contact.export",
        "crm.tag.view", "crm.tag.manage",
    ]
    for codename in expected:
        assert Permission.objects.filter(codename=codename).exists(), f"Missing permission: {codename}"
