"""CRM API views.

Endpoints
---------
GET  /crm/contacts/                     list contacts (filterable)
POST /crm/contacts/                     create contact
GET  /crm/contacts/{public_id}/         retrieve contact
PATCH /crm/contacts/{public_id}/        update contact
DELETE /crm/contacts/{public_id}/       soft-delete contact
POST /crm/contacts/{public_id}/tags/    add tag
DELETE /crm/contacts/{public_id}/tags/{tag_id}/  remove tag
POST /crm/contacts/{public_id}/merge/   merge into another contact
GET  /crm/contacts/search/              quick search
GET  /crm/tags/                         list tags
POST /crm/tags/                         create tag
PATCH /crm/tags/{tag_id}/               update tag
DELETE /crm/tags/{tag_id}/              delete tag

S1.2 Pipeline endpoints
---------
GET  /crm/pipelines/                    list pipelines
POST /crm/pipelines/                    create pipeline
GET  /crm/pipelines/{public_id}/        retrieve pipeline (with stages)
PATCH /crm/pipelines/{public_id}/       update pipeline
DELETE /crm/pipelines/{public_id}/      delete pipeline
POST /crm/pipelines/{public_id}/stages/ add stage
PATCH /crm/stages/{public_id}/          update stage
DELETE /crm/stages/{public_id}/         delete stage
GET  /crm/leads/                        list leads
POST /crm/leads/                        create lead
GET  /crm/leads/{public_id}/            retrieve lead
PATCH /crm/leads/{public_id}/           update lead
DELETE /crm/leads/{public_id}/          soft-delete lead
POST /crm/leads/{public_id}/qualify/    qualify → creates opportunity
POST /crm/leads/{public_id}/lose/       mark lost
GET  /crm/opportunities/                list opportunities
POST /crm/opportunities/                create opportunity
GET  /crm/opportunities/{public_id}/    retrieve opportunity
PATCH /crm/opportunities/{public_id}/   update opportunity
DELETE /crm/opportunities/{public_id}/  soft-delete opportunity
POST /crm/opportunities/{public_id}/move-stage/  move to stage
POST /crm/opportunities/{public_id}/won/         mark won
POST /crm/opportunities/{public_id}/lost/        mark lost
GET  /crm/activities/                   list activities
POST /crm/activities/                   create activity
GET  /crm/activities/{public_id}/       retrieve activity
PATCH /crm/activities/{public_id}/      update activity
DELETE /crm/activities/{public_id}/     delete activity
POST /crm/activities/{public_id}/complete/  mark done
"""

from __future__ import annotations

from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.crm import selectors as sel
from simorgh.apps.crm import services
from simorgh.apps.crm.models import (
    Contact,
    ContactAddress,
    ContactEmail,
    ContactPhone,
    ContactTag,
    CRMActivity,
    Lead,
    Opportunity,
    Pipeline,
    PipelineStage,
)


# ---------------------------------------------------------------------------
# Serialization helpers
# ---------------------------------------------------------------------------

def _serialize_phone(ph: ContactPhone) -> dict:
    return {
        "id": ph.pk,
        "type": ph.type,
        "number": ph.number,
        "is_default": ph.is_default,
    }


def _serialize_email(em: ContactEmail) -> dict:
    return {
        "id": em.pk,
        "type": em.type,
        "address": em.address,
        "is_default": em.is_default,
    }


def _serialize_address(addr: ContactAddress) -> dict:
    return {
        "id": addr.pk,
        "type": addr.type,
        "street": addr.street,
        "city": addr.city,
        "state": addr.state,
        "postal_code": addr.postal_code,
        "country": addr.country,
        "is_default": addr.is_default,
    }


def _serialize_tag(tag: ContactTag) -> dict:
    return {
        "id": tag.pk,
        "public_id": str(tag.public_id),
        "name": tag.name,
        "color": tag.color,
    }


def _serialize_contact(contact: Contact, *, detail: bool = False) -> dict:
    data: dict = {
        "id": contact.pk,
        "public_id": str(contact.public_id),
        "type": contact.type,
        "name": contact.name,
        "website": contact.website,
        "is_customer": contact.is_customer,
        "is_vendor": contact.is_vendor,
        "parent_company_id": contact.parent_company_id,
        "tags": [_serialize_tag(t) for t in contact.tags.all()],
        "created_at": contact.created_at.isoformat(),
        "updated_at": contact.updated_at.isoformat(),
    }
    if detail:
        data["notes"] = contact.notes
        data["custom_fields"] = contact.custom_fields
        data["phones"] = [_serialize_phone(p) for p in contact.phones.all()]
        data["emails"] = [_serialize_email(e) for e in contact.emails.all()]
        data["addresses"] = [_serialize_address(a) for a in contact.addresses.all()]
    return data


def _get_tenant_id(request: Request) -> int | None:
    """Extract tenant id from request (set by TenantMiddleware)."""
    tenant = getattr(request, "tenant", None)
    return tenant.pk if tenant else None


# ---------------------------------------------------------------------------
# Contact list / create
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def contact_list_create(request: Request) -> Response:
    """GET /crm/contacts/ — POST /crm/contacts/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        contact_type = request.query_params.get("type")
        tag_ids_raw = request.query_params.getlist("tag")
        tag_ids = [int(t) for t in tag_ids_raw if t.isdigit()]

        if tag_ids:
            qs = sel.contacts_with_tags(tenant_id, tag_ids)
        elif contact_type:
            qs = sel.contacts_by_type(tenant_id, contact_type)
        else:
            qs = sel.contacts_for_tenant(tenant_id)

        qs = qs.prefetch_related("tags")
        return Response([_serialize_contact(c) for c in qs])

    # POST
    data = request.data
    try:
        contact = services.create_contact(
            tenant_id=tenant_id,
            organization_node_id=data["organization_node_id"],
            name=data["name"],
            type=data.get("type", "person"),
            parent_company_id=data.get("parent_company_id"),
            website=data.get("website", ""),
            notes=data.get("notes", ""),
            is_customer=data.get("is_customer", False),
            is_vendor=data.get("is_vendor", False),
            custom_fields=data.get("custom_fields"),
            phones=data.get("phones"),
            emails=data.get("emails"),
            addresses=data.get("addresses"),
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)

    c = Contact.objects.prefetch_related("tags", "phones", "emails", "addresses").get(pk=contact.pk)
    return Response(_serialize_contact(c, detail=True), status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Contact search
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def contact_search(request: Request) -> Response:
    """GET /crm/contacts/search/?q="""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    q = request.query_params.get("q", "").strip()
    qs = sel.contacts_search(tenant_id, q).prefetch_related("tags")[:50]
    return Response([_serialize_contact(c) for c in qs])


# ---------------------------------------------------------------------------
# Contact retrieve / update / delete
# ---------------------------------------------------------------------------

@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def contact_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /crm/contacts/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    contact = get_object_or_404(
        Contact.objects.prefetch_related("tags", "phones", "emails", "addresses"),
        tenant_id=tenant_id,
        public_id=public_id,
        deleted_at__isnull=True,
    )

    if request.method == "GET":
        return Response(_serialize_contact(contact, detail=True))

    if request.method == "PATCH":
        data = request.data
        updated = services.update_contact(
            contact,
            name=data.get("name"),
            type=data.get("type"),
            parent_company_id=data.get("parent_company_id"),
            website=data.get("website"),
            notes=data.get("notes"),
            is_customer=data.get("is_customer"),
            is_vendor=data.get("is_vendor"),
            custom_fields=data.get("custom_fields"),
        )
        updated = Contact.objects.prefetch_related("tags", "phones", "emails", "addresses").get(pk=updated.pk)
        return Response(_serialize_contact(updated, detail=True))

    # DELETE
    services.delete_contact(contact)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Contact tags sub-resource
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def contact_add_tag(request: Request, public_id: str) -> Response:
    """POST /crm/contacts/{public_id}/tags/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    contact = get_object_or_404(Contact, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    tag_id = request.data.get("tag_id")
    tag = get_object_or_404(ContactTag, pk=tag_id, tenant_id=tenant_id)
    services.add_tag_to_contact(contact, tag)
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def contact_remove_tag(request: Request, public_id: str, tag_id: int) -> Response:
    """DELETE /crm/contacts/{public_id}/tags/{tag_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    contact = get_object_or_404(Contact, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    tag = get_object_or_404(ContactTag, pk=tag_id, tenant_id=tenant_id)
    services.remove_tag_from_contact(contact, tag)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Contact merge
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def contact_merge(request: Request, public_id: str) -> Response:
    """POST /crm/contacts/{public_id}/merge/
    Body: {"duplicate_id": "<public_id of contact to absorb>"}
    The contact at {public_id} is the primary (survives).
    """
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    primary = get_object_or_404(Contact, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    dup_public_id = request.data.get("duplicate_id")
    if not dup_public_id:
        return Response({"detail": "duplicate_id is required."}, status=status.HTTP_400_BAD_REQUEST)
    duplicate = get_object_or_404(Contact, tenant_id=tenant_id, public_id=dup_public_id, deleted_at__isnull=True)

    try:
        result = services.merge_contacts(primary=primary, duplicate=duplicate)
    except ValueError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    result = Contact.objects.prefetch_related("tags", "phones", "emails", "addresses").get(pk=result.pk)
    return Response(_serialize_contact(result, detail=True))


# ---------------------------------------------------------------------------
# Tag list / create / update / delete
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def tag_list_create(request: Request) -> Response:
    """GET/POST /crm/tags/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        tags = sel.tags_for_tenant(tenant_id)
        return Response([_serialize_tag(t) for t in tags])

    # POST
    data = request.data
    name = data.get("name", "").strip()
    if not name:
        return Response({"detail": "name is required."}, status=status.HTTP_400_BAD_REQUEST)
    tag = services.create_tag(tenant_id=tenant_id, name=name, color=data.get("color", ""))
    return Response(_serialize_tag(tag), status=status.HTTP_201_CREATED)


@api_view(["PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def tag_detail(request: Request, tag_id: int) -> Response:
    """PATCH/DELETE /crm/tags/{tag_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    tag = get_object_or_404(ContactTag, pk=tag_id, tenant_id=tenant_id)

    if request.method == "PATCH":
        updated = services.update_tag(
            tag,
            name=request.data.get("name"),
            color=request.data.get("color"),
        )
        return Response(_serialize_tag(updated))

    # DELETE
    services.delete_tag(tag)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ===========================================================================
# Serialization helpers  — Pipeline / Lead / Opportunity / Activity
# ===========================================================================

def _serialize_stage(stage: PipelineStage) -> dict:
    return {
        "id": stage.pk,
        "public_id": str(stage.public_id),
        "name": stage.name,
        "order": stage.order,
        "probability_pct": stage.probability_pct,
        "color": stage.color,
        "is_won": stage.is_won,
        "is_lost": stage.is_lost,
    }


def _serialize_pipeline(pipeline: Pipeline, *, detail: bool = False) -> dict:
    data: dict = {
        "id": pipeline.pk,
        "public_id": str(pipeline.public_id),
        "name": pipeline.name,
        "is_default": pipeline.is_default,
    }
    if detail:
        data["stages"] = [_serialize_stage(s) for s in pipeline.stages.order_by("order")]
    return data


def _serialize_lead(lead: Lead) -> dict:
    return {
        "id": lead.pk,
        "public_id": str(lead.public_id),
        "title": lead.title,
        "status": lead.status,
        "source": lead.source,
        "score": lead.score,
        "contact_id": lead.contact_id,
        "owner_id": lead.owner_id,
        "notes": lead.notes,
        "custom_fields": lead.custom_fields,
        "created_at": lead.created_at.isoformat(),
        "updated_at": lead.updated_at.isoformat(),
    }


def _serialize_opportunity(opp: Opportunity) -> dict:
    return {
        "id": opp.pk,
        "public_id": str(opp.public_id),
        "title": opp.title,
        "status": opp.status,
        "amount": str(opp.amount),
        "currency": opp.currency,
        "probability": opp.probability,
        "expected_close": opp.expected_close.isoformat() if opp.expected_close else None,
        "contact_id": opp.contact_id,
        "owner_id": opp.owner_id,
        "lead_id": opp.lead_id,
        "pipeline_stage_id": opp.pipeline_stage_id,
        "pipeline_stage": _serialize_stage(opp.pipeline_stage) if opp.pipeline_stage_id else None,
        "closed_at": opp.closed_at.isoformat() if opp.closed_at else None,
        "lost_reason": opp.lost_reason,
        "created_at": opp.created_at.isoformat(),
        "updated_at": opp.updated_at.isoformat(),
    }


def _serialize_activity(act: CRMActivity) -> dict:
    return {
        "id": act.pk,
        "public_id": str(act.public_id),
        "type": act.type,
        "title": act.title,
        "notes": act.notes,
        "is_done": act.is_done,
        "due_at": act.due_at.isoformat() if act.due_at else None,
        "completed_at": act.completed_at.isoformat() if act.completed_at else None,
        "owner_id": act.owner_id,
        "contact_id": act.contact_id,
        "opportunity_id": act.opportunity_id,
        "lead_id": act.lead_id,
        "created_at": act.created_at.isoformat(),
        "updated_at": act.updated_at.isoformat(),
    }


# ===========================================================================
# Pipeline views
# ===========================================================================

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def pipeline_list_create(request: Request) -> Response:
    """GET/POST /crm/pipelines/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        pipelines = sel.pipelines_for_tenant(tenant_id)
        return Response([_serialize_pipeline(p) for p in pipelines])

    # POST
    name = request.data.get("name", "").strip()
    if not name:
        return Response({"detail": "name is required."}, status=status.HTTP_400_BAD_REQUEST)
    org_node_id = (
        request.data.get("organization_node_id")
        or getattr(getattr(request, "tenant", None), "default_org_node_id", None)
    )
    pipeline = services.create_pipeline(
        tenant_id=tenant_id,
        organization_node_id=org_node_id or 1,
        name=name,
        is_default=request.data.get("is_default", False),
    )
    return Response(_serialize_pipeline(pipeline), status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def pipeline_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /crm/pipelines/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    pipeline = get_object_or_404(Pipeline, tenant_id=tenant_id, public_id=public_id)

    if request.method == "GET":
        return Response(_serialize_pipeline(pipeline, detail=True))

    if request.method == "PATCH":
        updated = services.update_pipeline(
            pipeline,
            name=request.data.get("name"),
            is_default=request.data.get("is_default"),
        )
        return Response(_serialize_pipeline(updated))

    # DELETE
    pipeline.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def stage_create(request: Request, public_id: str) -> Response:
    """POST /crm/pipelines/{public_id}/stages/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    pipeline = get_object_or_404(Pipeline, tenant_id=tenant_id, public_id=public_id)
    name = request.data.get("name", "").strip()
    if not name:
        return Response({"detail": "name is required."}, status=status.HTTP_400_BAD_REQUEST)
    stage = services.create_stage(
        pipeline=pipeline,
        name=name,
        order=request.data.get("order", 0),
        probability_pct=request.data.get("probability_pct", 0),
        color=request.data.get("color", "#6366f1"),
        is_won=request.data.get("is_won", False),
        is_lost=request.data.get("is_lost", False),
    )
    return Response(_serialize_stage(stage), status=status.HTTP_201_CREATED)


@api_view(["PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def stage_detail(request: Request, public_id: str) -> Response:
    """PATCH/DELETE /crm/stages/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    stage = get_object_or_404(PipelineStage, public_id=public_id, pipeline__tenant_id=tenant_id)

    if request.method == "PATCH":
        updated = services.update_stage(
            stage,
            name=request.data.get("name"),
            order=request.data.get("order"),
            probability_pct=request.data.get("probability_pct"),
            color=request.data.get("color"),
            is_won=request.data.get("is_won"),
            is_lost=request.data.get("is_lost"),
        )
        return Response(_serialize_stage(updated))

    # DELETE
    stage.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)


# ===========================================================================
# Lead views
# ===========================================================================

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def lead_list_create(request: Request) -> Response:
    """GET/POST /crm/leads/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        leads = sel.leads_for_tenant(
            tenant_id,
            status=request.query_params.get("status"),
            source=request.query_params.get("source"),
            owner_id=request.query_params.get("owner_id"),
            contact_id=request.query_params.get("contact_id"),
            q=request.query_params.get("q"),
        )
        return Response([_serialize_lead(l) for l in leads])

    # POST
    title = request.data.get("title", "").strip()
    if not title:
        return Response({"detail": "title is required."}, status=status.HTTP_400_BAD_REQUEST)
    org_node_id = (
        request.data.get("organization_node_id")
        or getattr(getattr(request, "tenant", None), "default_org_node_id", None)
    )
    lead = services.create_lead(
        tenant_id=tenant_id,
        organization_node_id=org_node_id or 1,
        title=title,
        contact_id=request.data.get("contact_id"),
        source=request.data.get("source", "manual"),
        owner_id=request.data.get("owner_id"),
        score=request.data.get("score", 0),
        notes=request.data.get("notes", ""),
        custom_fields=request.data.get("custom_fields"),
    )
    return Response(_serialize_lead(lead), status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def lead_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /crm/leads/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    lead = get_object_or_404(Lead, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)

    if request.method == "GET":
        return Response(_serialize_lead(lead))

    if request.method == "PATCH":
        updated = services.update_lead(
            lead,
            title=request.data.get("title"),
            contact_id=request.data.get("contact_id"),
            source=request.data.get("source"),
            score=request.data.get("score"),
            notes=request.data.get("notes"),
            status=request.data.get("status"),
            custom_fields=request.data.get("custom_fields"),
        )
        return Response(_serialize_lead(updated))

    # DELETE
    services.delete_lead(lead)
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def lead_qualify(request: Request, public_id: str) -> Response:
    """POST /crm/leads/{public_id}/qualify/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    lead = get_object_or_404(Lead, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    stage_id = request.data.get("pipeline_stage_id")
    if not stage_id:
        return Response({"detail": "pipeline_stage_id is required."}, status=status.HTTP_400_BAD_REQUEST)

    try:
        opp = services.qualify_lead(
            lead,
            pipeline_stage_id=stage_id,
            title=request.data.get("title"),
            amount=request.data.get("amount", 0),
            currency=request.data.get("currency", "USD"),
            expected_close=request.data.get("expected_close"),
            owner_id=request.data.get("owner_id"),
            contact_id=request.data.get("contact_id"),
            actor_id=request.user.pk,
        )
    except PipelineStage.DoesNotExist:
        return Response({"detail": "Stage not found."}, status=status.HTTP_404_NOT_FOUND)

    return Response(_serialize_opportunity(opp), status=status.HTTP_201_CREATED)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def lead_lose(request: Request, public_id: str) -> Response:
    """POST /crm/leads/{public_id}/lose/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    lead = get_object_or_404(Lead, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    updated = services.lose_lead(lead, reason=request.data.get("reason", ""), actor_id=request.user.pk)
    return Response(_serialize_lead(updated))


# ===========================================================================
# Opportunity views
# ===========================================================================

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def opportunity_list_create(request: Request) -> Response:
    """GET/POST /crm/opportunities/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        opps = sel.opportunities_for_tenant(
            tenant_id,
            stage_id=request.query_params.get("stage_id"),
            owner_id=request.query_params.get("owner_id"),
            status=request.query_params.get("status"),
            contact_id=request.query_params.get("contact_id"),
            pipeline_id=request.query_params.get("pipeline_id"),
            q=request.query_params.get("q"),
        )
        return Response([_serialize_opportunity(o) for o in opps])

    # POST
    title = request.data.get("title", "").strip()
    stage_id = request.data.get("pipeline_stage_id")
    if not title or not stage_id:
        return Response({"detail": "title and pipeline_stage_id are required."}, status=status.HTTP_400_BAD_REQUEST)
    org_node_id = (
        request.data.get("organization_node_id")
        or getattr(getattr(request, "tenant", None), "default_org_node_id", None)
    )
    try:
        opp = services.create_opportunity(
            tenant_id=tenant_id,
            organization_node_id=org_node_id or 1,
            pipeline_stage_id=stage_id,
            title=title,
            contact_id=request.data.get("contact_id"),
            amount=request.data.get("amount", 0),
            currency=request.data.get("currency", "USD"),
            expected_close=request.data.get("expected_close"),
            owner_id=request.data.get("owner_id"),
            lead_id=request.data.get("lead_id"),
            probability=request.data.get("probability"),
        )
    except PipelineStage.DoesNotExist:
        return Response({"detail": "Stage not found."}, status=status.HTTP_404_NOT_FOUND)
    return Response(_serialize_opportunity(opp), status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def opportunity_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /crm/opportunities/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    opp = get_object_or_404(Opportunity, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)

    if request.method == "GET":
        return Response(_serialize_opportunity(opp))

    if request.method == "PATCH":
        updated = services.update_opportunity(
            opp,
            title=request.data.get("title"),
            contact_id=request.data.get("contact_id"),
            amount=request.data.get("amount"),
            currency=request.data.get("currency"),
            expected_close=request.data.get("expected_close"),
            probability=request.data.get("probability"),
        )
        return Response(_serialize_opportunity(updated))

    # DELETE
    services.delete_opportunity(opp)
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def opportunity_move_stage(request: Request, public_id: str) -> Response:
    """POST /crm/opportunities/{public_id}/move-stage/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    opp = get_object_or_404(Opportunity, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    stage_id = request.data.get("stage_id")
    if not stage_id:
        return Response({"detail": "stage_id is required."}, status=status.HTTP_400_BAD_REQUEST)

    try:
        updated = services.move_stage(opp, stage_id=stage_id, actor_id=request.user.pk)
    except PipelineStage.DoesNotExist:
        return Response({"detail": "Stage not found."}, status=status.HTTP_404_NOT_FOUND)
    return Response(_serialize_opportunity(updated))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def opportunity_won(request: Request, public_id: str) -> Response:
    """POST /crm/opportunities/{public_id}/won/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    opp = get_object_or_404(Opportunity, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    updated = services.mark_won(opp, closed_at=request.data.get("closed_at"), actor_id=request.user.pk)
    return Response(_serialize_opportunity(updated))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def opportunity_lost(request: Request, public_id: str) -> Response:
    """POST /crm/opportunities/{public_id}/lost/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    opp = get_object_or_404(Opportunity, tenant_id=tenant_id, public_id=public_id, deleted_at__isnull=True)
    updated = services.mark_lost(opp, reason=request.data.get("reason", ""), actor_id=request.user.pk)
    return Response(_serialize_opportunity(updated))


# ===========================================================================
# Activity views
# ===========================================================================

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def activity_list_create(request: Request) -> Response:
    """GET/POST /crm/activities/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        acts = sel.activities_for_tenant(
            tenant_id,
            type=request.query_params.get("type"),
            owner_id=request.query_params.get("owner_id"),
            is_done=request.query_params.get("is_done"),
            linked_opp_id=request.query_params.get("opportunity_id"),
            linked_lead_id=request.query_params.get("lead_id"),
        )
        return Response([_serialize_activity(a) for a in acts])

    # POST
    title = request.data.get("title", "").strip()
    if not title:
        return Response({"detail": "title is required."}, status=status.HTTP_400_BAD_REQUEST)
    act = services.create_activity(
        tenant_id=tenant_id,
        type=request.data.get("type", "task"),
        title=title,
        owner_id=request.data.get("owner_id"),
        contact_id=request.data.get("contact_id"),
        opportunity_id=request.data.get("opportunity_id"),
        lead_id=request.data.get("lead_id"),
        notes=request.data.get("notes", ""),
        due_at=request.data.get("due_at"),
    )
    return Response(_serialize_activity(act), status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def activity_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /crm/activities/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    act = get_object_or_404(CRMActivity, tenant_id=tenant_id, public_id=public_id)

    if request.method == "GET":
        return Response(_serialize_activity(act))

    if request.method == "PATCH":
        updated = services.update_activity(
            act,
            title=request.data.get("title"),
            type=request.data.get("type"),
            notes=request.data.get("notes"),
            due_at=request.data.get("due_at"),
            owner_id=request.data.get("owner_id"),
        )
        return Response(_serialize_activity(updated))

    # DELETE
    services.delete_activity(act)
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def activity_complete(request: Request, public_id: str) -> Response:
    """POST /crm/activities/{public_id}/complete/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    act = get_object_or_404(CRMActivity, tenant_id=tenant_id, public_id=public_id)
    updated = services.complete_activity(act, actor_id=request.user.pk)
    return Response(_serialize_activity(updated))

