"""Tests for S1.2 CRM Pipeline — Leads / Opportunities / Activities.

Covers:
- Pipeline CRUD
- Stage CRUD
- Lead CRUD + qualify + lose
- Opportunity CRUD + move-stage + won + lost
- Activity CRUD + complete
- Service-layer business logic
- Permission / tenant isolation stubs
- Search integration
"""

from __future__ import annotations

import pytest
from django.test import override_settings

from simorgh.apps.accounts.models import User
from simorgh.apps.crm.models import (
    CRMActivity,
    Lead,
    Opportunity,
    Pipeline,
    PipelineStage,
)
from simorgh.apps.crm import services
from simorgh.apps.crm import selectors
from simorgh.apps.organizations.services import create_node
from simorgh.apps.tenants.models import Tenant


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def tenant(db) -> Tenant:
    return Tenant.objects.create(slug="pipe-test", name="PipeTest")


@pytest.fixture
def org_node(tenant):
    return create_node(tenant_id=tenant.pk, name="HQ")


@pytest.fixture
def user(db) -> User:
    return User.objects.create_user("+989111111111", password="x", email="pipeuser@example.com")


@pytest.fixture
def pipeline(tenant, org_node) -> Pipeline:
    return services.create_pipeline(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        name="Default Pipeline",
        is_default=True,
    )


@pytest.fixture
def stage_new(pipeline) -> PipelineStage:
    return services.create_stage(
        pipeline=pipeline,
        name="New",
        order=0,
        probability_pct=10,
        color="#a0a0a0",
    )


@pytest.fixture
def stage_won(pipeline) -> PipelineStage:
    return services.create_stage(
        pipeline=pipeline,
        name="Won",
        order=99,
        probability_pct=100,
        color="#22c55e",
        is_won=True,
    )


@pytest.fixture
def lead(tenant, org_node, user) -> Lead:
    return services.create_lead(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        title="Hot lead",
        source="website",
        owner_id=user.pk,
        score=50,
    )


@pytest.fixture
def opportunity(tenant, org_node, stage_new, lead) -> Opportunity:
    return services.create_opportunity(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        pipeline_stage_id=stage_new.pk,
        title="Big deal",
        amount=10000,
        currency="USD",
        lead_id=lead.pk,
    )


@pytest.fixture
def activity(tenant, lead, opportunity) -> CRMActivity:
    return services.create_activity(
        tenant_id=tenant.pk,
        type="call",
        title="Intro call",
        lead_id=lead.pk,
        opportunity_id=opportunity.pk,
    )


# ---------------------------------------------------------------------------
# Pipeline tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_create_pipeline_basic(tenant, org_node):
    p = services.create_pipeline(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        name="Sales",
    )
    assert p.pk is not None
    assert p.name == "Sales"
    assert p.tenant_id == tenant.pk


@pytest.mark.django_db
def test_create_pipeline_default_unsets_previous(tenant, org_node):
    p1 = services.create_pipeline(
        tenant_id=tenant.pk, organization_node_id=org_node.pk, name="P1", is_default=True
    )
    assert p1.is_default is True

    p2 = services.create_pipeline(
        tenant_id=tenant.pk, organization_node_id=org_node.pk, name="P2", is_default=True
    )
    p1.refresh_from_db()
    assert p1.is_default is False
    assert p2.is_default is True


@pytest.mark.django_db
def test_update_pipeline_name(pipeline):
    updated = services.update_pipeline(pipeline, name="Renamed Pipeline")
    assert updated.name == "Renamed Pipeline"
    pipeline.refresh_from_db()
    assert pipeline.name == "Renamed Pipeline"


@pytest.mark.django_db
def test_pipelines_for_tenant_selector(tenant, org_node):
    services.create_pipeline(tenant_id=tenant.pk, organization_node_id=org_node.pk, name="Alpha")
    services.create_pipeline(tenant_id=tenant.pk, organization_node_id=org_node.pk, name="Beta")
    result = selectors.pipelines_for_tenant(tenant.pk)
    assert result.count() == 2


@pytest.mark.django_db
def test_pipeline_tenant_isolation(tenant, org_node):
    other = Tenant.objects.create(slug="other-pipe", name="Other")
    other_node = create_node(tenant_id=other.pk, name="Root")
    services.create_pipeline(tenant_id=other.pk, organization_node_id=other_node.pk, name="Other Pipeline")
    assert selectors.pipelines_for_tenant(tenant.pk).count() == 0


# ---------------------------------------------------------------------------
# Stage tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_create_stage(pipeline):
    stage = services.create_stage(
        pipeline=pipeline,
        name="Proposal",
        order=1,
        probability_pct=50,
        color="#3b82f6",
    )
    assert stage.pk is not None
    assert stage.pipeline_id == pipeline.pk
    assert stage.probability_pct == 50


@pytest.mark.django_db
def test_update_stage_probability(stage_new):
    updated = services.update_stage(stage_new, probability_pct=75)
    assert updated.probability_pct == 75
    stage_new.refresh_from_db()
    assert stage_new.probability_pct == 75


@pytest.mark.django_db
def test_stages_for_pipeline_selector(pipeline, stage_new, stage_won):
    stages = list(selectors.stages_for_pipeline(pipeline.pk))
    names = [s.name for s in stages]
    assert "New" in names
    assert "Won" in names


# ---------------------------------------------------------------------------
# Lead tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_create_lead(tenant, org_node, user):
    lead = services.create_lead(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        title="Test Lead",
        source="referral",
        owner_id=user.pk,
        score=30,
    )
    assert lead.pk is not None
    assert lead.title == "Test Lead"
    assert lead.status == "new"
    assert lead.source == "referral"


@pytest.mark.django_db
def test_update_lead(lead):
    updated = services.update_lead(lead, title="Updated Lead", score=80)
    assert updated.title == "Updated Lead"
    assert updated.score == 80


@pytest.mark.django_db
def test_update_lead_status(lead):
    updated = services.update_lead(lead, status="contacted")
    assert updated.status == "contacted"


@pytest.mark.django_db
def test_lose_lead(lead):
    lost = services.lose_lead(lead, reason="No budget")
    assert lost.status == "lost"


@pytest.mark.django_db
def test_delete_lead(lead):
    lead_pk = lead.pk
    services.delete_lead(lead)
    assert Lead.objects.filter(pk=lead_pk, deleted_at__isnull=False).exists()


@pytest.mark.django_db
def test_leads_for_tenant_selector(tenant, org_node, user):
    services.create_lead(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        title="Lead A", owner_id=user.pk
    )
    services.create_lead(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        title="Lead B", owner_id=user.pk
    )
    assert selectors.leads_for_tenant(tenant.pk).count() == 2


@pytest.mark.django_db
def test_leads_filtered_by_status(tenant, org_node, user):
    l1 = services.create_lead(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        title="New Lead", owner_id=user.pk
    )
    l2 = services.create_lead(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        title="Lost Lead", owner_id=user.pk
    )
    services.lose_lead(l2)
    new_leads = selectors.leads_for_tenant(tenant.pk, status="new")
    assert new_leads.count() == 1
    assert new_leads.first().pk == l1.pk


@pytest.mark.django_db
def test_qualify_lead(lead, stage_new, user):
    opp = services.qualify_lead(
        lead,
        pipeline_stage_id=stage_new.pk,
        title="Qualified Deal",
        amount=5000,
        actor_id=user.pk,
    )
    lead.refresh_from_db()
    assert lead.status == "converted"
    assert opp.pk is not None
    assert opp.title == "Qualified Deal"
    assert opp.lead_id == lead.pk
    assert opp.pipeline_stage_id == stage_new.pk


# ---------------------------------------------------------------------------
# Opportunity tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_create_opportunity(tenant, org_node, stage_new):
    opp = services.create_opportunity(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        pipeline_stage_id=stage_new.pk,
        title="Enterprise Deal",
        amount=99000,
        currency="USD",
    )
    assert opp.pk is not None
    assert opp.title == "Enterprise Deal"
    assert opp.status == "open"
    assert float(opp.amount) == 99000.0


@pytest.mark.django_db
def test_update_opportunity(opportunity):
    updated = services.update_opportunity(opportunity, title="Renamed Deal", amount=20000)
    assert updated.title == "Renamed Deal"
    assert float(updated.amount) == 20000.0


@pytest.mark.django_db
def test_move_stage(opportunity, stage_won, user):
    moved = services.move_stage(opportunity, stage_id=stage_won.pk, actor_id=user.pk)
    assert moved.pipeline_stage_id == stage_won.pk


@pytest.mark.django_db
def test_mark_won(opportunity, user):
    won = services.mark_won(opportunity, actor_id=user.pk)
    assert won.status == "won"
    assert won.closed_at is not None


@pytest.mark.django_db
def test_mark_lost(opportunity, user):
    lost = services.mark_lost(opportunity, reason="Budget cut", actor_id=user.pk)
    assert lost.status == "lost"
    assert lost.lost_reason == "Budget cut"
    assert lost.closed_at is not None


@pytest.mark.django_db
def test_delete_opportunity(opportunity):
    opp_pk = opportunity.pk
    services.delete_opportunity(opportunity)
    assert Opportunity.objects.filter(pk=opp_pk, deleted_at__isnull=False).exists()


@pytest.mark.django_db
def test_opportunities_for_tenant_selector(tenant, org_node, stage_new):
    services.create_opportunity(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        pipeline_stage_id=stage_new.pk, title="O1"
    )
    services.create_opportunity(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        pipeline_stage_id=stage_new.pk, title="O2"
    )
    assert selectors.opportunities_for_tenant(tenant.pk).count() == 2


@pytest.mark.django_db
def test_opportunities_filtered_by_status(tenant, org_node, stage_new, user):
    o1 = services.create_opportunity(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        pipeline_stage_id=stage_new.pk, title="Open"
    )
    o2 = services.create_opportunity(
        tenant_id=tenant.pk, organization_node_id=org_node.pk,
        pipeline_stage_id=stage_new.pk, title="Won"
    )
    services.mark_won(o2, actor_id=user.pk)
    open_opps = selectors.opportunities_for_tenant(tenant.pk, status="open")
    assert open_opps.count() == 1
    assert open_opps.first().pk == o1.pk


# ---------------------------------------------------------------------------
# Activity tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_create_activity(tenant, lead, opportunity):
    act = services.create_activity(
        tenant_id=tenant.pk,
        type="call",
        title="First Call",
        lead_id=lead.pk,
        opportunity_id=opportunity.pk,
    )
    assert act.pk is not None
    assert act.type == "call"
    assert act.is_done is False


@pytest.mark.django_db
def test_update_activity(activity):
    updated = services.update_activity(activity, title="Updated Call", notes="Went well")
    assert updated.title == "Updated Call"
    assert updated.notes == "Went well"


@pytest.mark.django_db
def test_complete_activity(activity):
    done = services.complete_activity(activity)
    assert done.is_done is True
    assert done.completed_at is not None


@pytest.mark.django_db
def test_delete_activity(activity):
    act_pk = activity.pk
    services.delete_activity(activity)
    assert not CRMActivity.objects.filter(pk=act_pk).exists()


@pytest.mark.django_db
def test_activities_for_tenant_selector(tenant, lead, opportunity):
    services.create_activity(
        tenant_id=tenant.pk, type="call", title="Call 1", lead_id=lead.pk
    )
    services.create_activity(
        tenant_id=tenant.pk, type="email", title="Email 1", opportunity_id=opportunity.pk
    )
    assert selectors.activities_for_tenant(tenant.pk).count() == 2


@pytest.mark.django_db
def test_activities_filtered_by_type(tenant, lead):
    services.create_activity(tenant_id=tenant.pk, type="call", title="Call", lead_id=lead.pk)
    services.create_activity(tenant_id=tenant.pk, type="email", title="Email", lead_id=lead.pk)
    calls = selectors.activities_for_tenant(tenant.pk, type="call")
    assert calls.count() == 1
    assert calls.first().title == "Call"


# ---------------------------------------------------------------------------
# API tests
# ---------------------------------------------------------------------------

@pytest.fixture
def api_client():
    from rest_framework.test import APIClient
    return APIClient()


@pytest.fixture
def authed_client(api_client, user, tenant):
    api_client.force_login(user)
    return api_client, tenant.slug


@pytest.mark.django_db
def test_api_pipeline_list(authed_client, pipeline):
    client, slug = authed_client
    resp = client.get("/api/v1/crm/pipelines/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    data = resp.json()
    assert isinstance(data, list)
    assert len(data) == 1
    assert data[0]["name"] == pipeline.name


@pytest.mark.django_db
def test_api_pipeline_create(authed_client, org_node):
    client, slug = authed_client
    resp = client.post("/api/v1/crm/pipelines/", {"name": "New Pipeline"}, HTTP_X_TENANT=slug)
    assert resp.status_code == 201
    assert resp.json()["name"] == "New Pipeline"


@pytest.mark.django_db
def test_api_pipeline_detail_with_stages(authed_client, pipeline, stage_new):
    client, slug = authed_client
    resp = client.get(f"/api/v1/crm/pipelines/{pipeline.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    data = resp.json()
    assert "stages" in data
    assert len(data["stages"]) == 1


@pytest.mark.django_db
def test_api_stage_create(authed_client, pipeline):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/pipelines/{pipeline.public_id}/stages/",
        {"name": "Proposal", "order": 1, "probability_pct": 50},
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["name"] == "Proposal"


@pytest.mark.django_db
def test_api_lead_list(authed_client, lead):
    client, slug = authed_client
    resp = client.get("/api/v1/crm/leads/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert len(resp.json()) == 1


@pytest.mark.django_db
def test_api_lead_create(authed_client, org_node):
    client, slug = authed_client
    resp = client.post(
        "/api/v1/crm/leads/",
        {"title": "API Lead", "source": "website", "organization_node_id": org_node.pk},
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["title"] == "API Lead"


@pytest.mark.django_db
def test_api_lead_detail(authed_client, lead):
    client, slug = authed_client
    resp = client.get(f"/api/v1/crm/leads/{lead.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert resp.json()["public_id"] == str(lead.public_id)


@pytest.mark.django_db
def test_api_lead_update(authed_client, lead):
    client, slug = authed_client
    resp = client.patch(
        f"/api/v1/crm/leads/{lead.public_id}/",
        {"score": 99},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["score"] == 99


@pytest.mark.django_db
def test_api_lead_delete(authed_client, lead):
    client, slug = authed_client
    resp = client.delete(f"/api/v1/crm/leads/{lead.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 204
    lead.refresh_from_db()
    assert lead.deleted_at is not None


@pytest.mark.django_db
def test_api_lead_qualify(authed_client, lead, stage_new):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/leads/{lead.public_id}/qualify/",
        {"pipeline_stage_id": stage_new.pk, "title": "Qualified", "amount": 5000},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["title"] == "Qualified"


@pytest.mark.django_db
def test_api_lead_lose(authed_client, lead):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/leads/{lead.public_id}/lose/",
        {"reason": "No interest"},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["status"] == "lost"


@pytest.mark.django_db
def test_api_opportunity_list(authed_client, opportunity):
    client, slug = authed_client
    resp = client.get("/api/v1/crm/opportunities/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert len(resp.json()) == 1


@pytest.mark.django_db
def test_api_opportunity_create(authed_client, stage_new):
    client, slug = authed_client
    resp = client.post(
        "/api/v1/crm/opportunities/",
        {"title": "New Opp", "pipeline_stage_id": stage_new.pk, "amount": 1000},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["title"] == "New Opp"


@pytest.mark.django_db
def test_api_opportunity_detail(authed_client, opportunity):
    client, slug = authed_client
    resp = client.get(f"/api/v1/crm/opportunities/{opportunity.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert resp.json()["public_id"] == str(opportunity.public_id)


@pytest.mark.django_db
def test_api_opportunity_update(authed_client, opportunity):
    client, slug = authed_client
    resp = client.patch(
        f"/api/v1/crm/opportunities/{opportunity.public_id}/",
        {"title": "Updated Opp"},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["title"] == "Updated Opp"


@pytest.mark.django_db
def test_api_opportunity_move_stage(authed_client, opportunity, stage_won):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/opportunities/{opportunity.public_id}/move-stage/",
        {"stage_id": stage_won.pk},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["pipeline_stage_id"] == stage_won.pk


@pytest.mark.django_db
def test_api_opportunity_won(authed_client, opportunity):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/opportunities/{opportunity.public_id}/won/",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["status"] == "won"


@pytest.mark.django_db
def test_api_opportunity_lost(authed_client, opportunity):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/opportunities/{opportunity.public_id}/lost/",
        {"reason": "Competitor win"},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["status"] == "lost"
    assert data["lost_reason"] == "Competitor win"


@pytest.mark.django_db
def test_api_activity_list(authed_client, activity):
    client, slug = authed_client
    resp = client.get("/api/v1/crm/activities/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert len(resp.json()) == 1


@pytest.mark.django_db
def test_api_activity_create(authed_client, lead):
    client, slug = authed_client
    resp = client.post(
        "/api/v1/crm/activities/",
        {"title": "API Activity", "type": "meeting", "lead_id": lead.pk},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["title"] == "API Activity"


@pytest.mark.django_db
def test_api_activity_complete(authed_client, activity):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/crm/activities/{activity.public_id}/complete/",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["is_done"] is True


@pytest.mark.django_db
def test_api_requires_tenant_header(api_client, user, pipeline):
    api_client.force_login(user)
    resp = api_client.get("/api/v1/crm/pipelines/")
    assert resp.status_code == 400
