"""CRM services — business logic for contacts and tags.

All write operations go through these functions; views and tasks must NOT
call ORM save/delete directly on CRM models.
"""

from __future__ import annotations

from django.db import transaction


# ---------------------------------------------------------------------------
# Contact Tag services
# ---------------------------------------------------------------------------

def create_tag(*, tenant_id: int, name: str, color: str = "") -> "ContactTag":
    """Create a new contact tag for a tenant."""
    from simorgh.apps.crm.models import ContactTag

    return ContactTag.objects.create(tenant_id=tenant_id, name=name.strip(), color=color)


def update_tag(tag: "ContactTag", *, name: str | None = None, color: str | None = None) -> "ContactTag":
    """Update tag fields."""
    if name is not None:
        tag.name = name.strip()
    if color is not None:
        tag.color = color
    tag.save()
    return tag


def delete_tag(tag: "ContactTag") -> None:
    """Delete a tag; removes it from all contacts automatically via M2M."""
    tag.delete()


# ---------------------------------------------------------------------------
# Contact services
# ---------------------------------------------------------------------------

def create_contact(
    *,
    tenant_id: int,
    organization_node_id: int,
    name: str,
    type: str = "person",
    parent_company_id: int | None = None,
    website: str = "",
    notes: str = "",
    is_customer: bool = False,
    is_vendor: bool = False,
    custom_fields: dict | None = None,
    phones: list[dict] | None = None,
    emails: list[dict] | None = None,
    addresses: list[dict] | None = None,
) -> "Contact":
    """Create a contact with optional phones, emails, and addresses.

    phones  example: [{"type": "mobile", "number": "+1234", "is_default": True}]
    emails  example: [{"type": "work", "address": "a@b.com", "is_default": True}]
    addresses example: [{"type": "billing", "street": "...", "city": "...", "is_default": True}]
    """
    from simorgh.apps.crm.models import Contact, ContactAddress, ContactEmail, ContactPhone

    with transaction.atomic():
        contact = Contact.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name.strip(),
            type=type,
            parent_company_id=parent_company_id,
            website=website,
            notes=notes,
            is_customer=is_customer,
            is_vendor=is_vendor,
            custom_fields=custom_fields or {},
        )
        for ph in phones or []:
            ContactPhone.objects.create(contact=contact, **ph)
        for em in emails or []:
            ContactEmail.objects.create(contact=contact, **em)
        for addr in addresses or []:
            ContactAddress.objects.create(contact=contact, **addr)

    return contact


def update_contact(
    contact: "Contact",
    *,
    name: str | None = None,
    type: str | None = None,
    parent_company_id: int | None = None,
    website: str | None = None,
    notes: str | None = None,
    is_customer: bool | None = None,
    is_vendor: bool | None = None,
    custom_fields: dict | None = None,
) -> "Contact":
    """Update mutable fields on an existing contact."""
    fields = []
    if name is not None:
        contact.name = name.strip()
        fields.append("name")
    if type is not None:
        contact.type = type
        fields.append("type")
    if parent_company_id is not None:
        contact.parent_company_id = parent_company_id
        fields.append("parent_company")
    if website is not None:
        contact.website = website
        fields.append("website")
    if notes is not None:
        contact.notes = notes
        fields.append("notes")
    if is_customer is not None:
        contact.is_customer = is_customer
        fields.append("is_customer")
    if is_vendor is not None:
        contact.is_vendor = is_vendor
        fields.append("is_vendor")
    if custom_fields is not None:
        contact.custom_fields = custom_fields
        fields.append("custom_fields")
    if fields:
        contact.save(update_fields=[*fields, "updated_at"])
    return contact


def delete_contact(contact: "Contact") -> None:
    """Soft-delete a contact."""
    contact.delete()


def add_tag_to_contact(contact: "Contact", tag: "ContactTag") -> None:
    """Add a tag to a contact (idempotent)."""
    contact.tags.add(tag)


def remove_tag_from_contact(contact: "Contact", tag: "ContactTag") -> None:
    """Remove a tag from a contact."""
    contact.tags.remove(tag)


def merge_contacts(*, primary: "Contact", duplicate: "Contact") -> "Contact":
    """Merge ``duplicate`` into ``primary``.

    All child rows (phones, emails, addresses) are re-parented to ``primary``.
    Tags from duplicate are added to primary. Duplicate is then soft-deleted.
    """
    from simorgh.apps.crm.models import ContactAddress, ContactEmail, ContactPhone

    if primary.pk == duplicate.pk:
        raise ValueError("Cannot merge a contact with itself.")
    if primary.tenant_id != duplicate.tenant_id:
        raise ValueError("Cannot merge contacts from different tenants.")

    with transaction.atomic():
        ContactPhone.objects.filter(contact=duplicate).update(contact=primary)
        ContactEmail.objects.filter(contact=duplicate).update(contact=primary)
        ContactAddress.objects.filter(contact=duplicate).update(contact=primary)
        for tag in duplicate.tags.all():
            primary.tags.add(tag)
        duplicate.delete()

    return primary


# ===========================================================================
# Pipeline / Stage services  (S1.2)
# ===========================================================================

def create_pipeline(
    *,
    tenant_id: int,
    organization_node_id: int,
    name: str,
    is_default: bool = False,
) -> "Pipeline":
    """Create a new sales pipeline.  If ``is_default`` is True, any existing
    default pipeline for this tenant is unset first."""
    from simorgh.apps.crm.models import Pipeline

    with transaction.atomic():
        if is_default:
            Pipeline.objects.filter(tenant_id=tenant_id, is_default=True).update(is_default=False)
        pipeline = Pipeline.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name.strip(),
            is_default=is_default,
        )
    return pipeline


def update_pipeline(
    pipeline: "Pipeline",
    *,
    name: str | None = None,
    is_default: bool | None = None,
) -> "Pipeline":
    """Update mutable pipeline fields."""
    from simorgh.apps.crm.models import Pipeline

    fields = []
    with transaction.atomic():
        if name is not None:
            pipeline.name = name.strip()
            fields.append("name")
        if is_default is not None:
            if is_default:
                Pipeline.objects.filter(
                    tenant_id=pipeline.tenant_id, is_default=True
                ).exclude(pk=pipeline.pk).update(is_default=False)
            pipeline.is_default = is_default
            fields.append("is_default")
        if fields:
            pipeline.save(update_fields=fields)
    return pipeline


def create_stage(
    *,
    pipeline: "Pipeline",
    name: str,
    order: int = 0,
    probability_pct: int = 0,
    color: str = "#6366f1",
    is_won: bool = False,
    is_lost: bool = False,
) -> "PipelineStage":
    """Create a new stage inside a pipeline."""
    from simorgh.apps.crm.models import PipelineStage

    return PipelineStage.objects.create(
        pipeline=pipeline,
        name=name.strip(),
        order=order,
        probability_pct=probability_pct,
        color=color,
        is_won=is_won,
        is_lost=is_lost,
    )


def update_stage(
    stage: "PipelineStage",
    *,
    name: str | None = None,
    order: int | None = None,
    probability_pct: int | None = None,
    color: str | None = None,
    is_won: bool | None = None,
    is_lost: bool | None = None,
) -> "PipelineStage":
    """Update mutable stage fields."""
    fields = []
    if name is not None:
        stage.name = name.strip(); fields.append("name")
    if order is not None:
        stage.order = order; fields.append("order")
    if probability_pct is not None:
        stage.probability_pct = probability_pct; fields.append("probability_pct")
    if color is not None:
        stage.color = color; fields.append("color")
    if is_won is not None:
        stage.is_won = is_won; fields.append("is_won")
    if is_lost is not None:
        stage.is_lost = is_lost; fields.append("is_lost")
    if fields:
        stage.save(update_fields=fields)
    return stage


# ===========================================================================
# Lead services
# ===========================================================================

def create_lead(
    *,
    tenant_id: int,
    organization_node_id: int,
    title: str,
    contact_id: int | None = None,
    source: str = "manual",
    owner_id: int | None = None,
    score: int = 0,
    notes: str = "",
    custom_fields: dict | None = None,
) -> "Lead":
    """Create a new lead and dispatch a creation event."""
    from simorgh.apps.crm.models import Lead
    from simorgh.apps.events.bus import dispatch

    lead = Lead.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        title=title.strip(),
        contact_id=contact_id,
        source=source,
        owner_id=owner_id,
        score=score,
        notes=notes,
        custom_fields=custom_fields or {},
    )
    dispatch(
        "crm.lead.created",
        {"lead_id": str(lead.public_id), "tenant_id": tenant_id, "owner_id": owner_id},
    )
    return lead


def update_lead(
    lead: "Lead",
    *,
    title: str | None = None,
    contact_id: int | None = None,
    source: str | None = None,
    score: int | None = None,
    notes: str | None = None,
    status: str | None = None,
    custom_fields: dict | None = None,
) -> "Lead":
    """Update mutable lead fields."""
    fields = []
    if title is not None:
        lead.title = title.strip(); fields.append("title")
    if contact_id is not None:
        lead.contact_id = contact_id; fields.append("contact")
    if source is not None:
        lead.source = source; fields.append("source")
    if score is not None:
        lead.score = score; fields.append("score")
    if notes is not None:
        lead.notes = notes; fields.append("notes")
    if status is not None:
        lead.status = status; fields.append("status")
    if custom_fields is not None:
        lead.custom_fields = custom_fields; fields.append("custom_fields")
    if fields:
        lead.save(update_fields=[*fields, "updated_at"])
    return lead


def assign_lead(lead: "Lead", *, owner_id: int) -> "Lead":
    """Reassign a lead to a different owner."""
    lead.owner_id = owner_id
    lead.save(update_fields=["owner", "updated_at"])
    return lead


def qualify_lead(
    lead: "Lead",
    *,
    pipeline_stage_id: int,
    title: str | None = None,
    amount: float = 0,
    currency: str = "USD",
    expected_close=None,
    owner_id: int | None = None,
    contact_id: int | None = None,
    actor_id: int | None = None,
) -> "Opportunity":
    """Convert a lead into an Opportunity.

    Sets lead.status = CONVERTED, creates an Opportunity linked to the lead,
    and dispatches ``crm.lead.qualified``.
    """
    from simorgh.apps.crm.models import Lead, Opportunity, PipelineStage
    from simorgh.apps.events.bus import dispatch

    stage = PipelineStage.objects.select_related("pipeline").get(pk=pipeline_stage_id)
    with transaction.atomic():
        lead.status = Lead.Status.CONVERTED if hasattr(Lead, "Status") else "converted"
        lead.save(update_fields=["status", "updated_at"])
        opp = Opportunity.objects.create(
            tenant_id=lead.tenant_id,
            organization_node_id=lead.organization_node_id,
            lead=lead,
            pipeline_stage=stage,
            title=(title or lead.title).strip(),
            contact_id=contact_id or lead.contact_id,
            amount=amount,
            currency=currency,
            expected_close=expected_close,
            owner_id=owner_id or lead.owner_id,
            probability=stage.probability_pct,
        )
    dispatch(
        "crm.lead.qualified",
        {
            "lead_id": str(lead.public_id),
            "opportunity_id": str(opp.public_id),
            "tenant_id": lead.tenant_id,
            "actor_id": actor_id,
        },
    )
    return opp


def lose_lead(lead: "Lead", *, reason: str = "", actor_id: int | None = None) -> "Lead":
    """Mark a lead as LOST."""
    from simorgh.apps.events.bus import dispatch

    lead.status = "lost"
    lead.notes = (lead.notes + f"\n[lost reason] {reason}").strip() if reason else lead.notes
    lead.save(update_fields=["status", "notes", "updated_at"])
    dispatch(
        "crm.lead.lost",
        {"lead_id": str(lead.public_id), "tenant_id": lead.tenant_id, "actor_id": actor_id},
    )
    return lead


def delete_lead(lead: "Lead") -> None:
    """Soft-delete a lead."""
    lead.delete()


# ===========================================================================
# Opportunity services
# ===========================================================================

def create_opportunity(
    *,
    tenant_id: int,
    organization_node_id: int,
    pipeline_stage_id: int,
    title: str,
    contact_id: int | None = None,
    amount: float = 0,
    currency: str = "USD",
    expected_close=None,
    owner_id: int | None = None,
    lead_id: int | None = None,
    probability: int | None = None,
) -> "Opportunity":
    """Create an Opportunity directly (without going through lead qualification)."""
    from simorgh.apps.crm.models import Opportunity, PipelineStage
    from simorgh.apps.events.bus import dispatch

    stage = PipelineStage.objects.get(pk=pipeline_stage_id)
    opp = Opportunity.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        lead_id=lead_id,
        pipeline_stage=stage,
        title=title.strip(),
        contact_id=contact_id,
        amount=amount,
        currency=currency,
        expected_close=expected_close,
        owner_id=owner_id,
        probability=probability if probability is not None else stage.probability_pct,
    )
    dispatch(
        "crm.opportunity.created",
        {"opportunity_id": str(opp.public_id), "tenant_id": tenant_id, "owner_id": owner_id},
    )
    return opp


def update_opportunity(
    opp: "Opportunity",
    *,
    title: str | None = None,
    contact_id: int | None = None,
    amount: float | None = None,
    currency: str | None = None,
    expected_close=None,
    probability: int | None = None,
) -> "Opportunity":
    """Update mutable opportunity fields."""
    fields = []
    if title is not None:
        opp.title = title.strip(); fields.append("title")
    if contact_id is not None:
        opp.contact_id = contact_id; fields.append("contact")
    if amount is not None:
        opp.amount = amount; fields.append("amount")
    if currency is not None:
        opp.currency = currency; fields.append("currency")
    if expected_close is not None:
        opp.expected_close = expected_close; fields.append("expected_close")
    if probability is not None:
        opp.probability = probability; fields.append("probability")
    if fields:
        opp.save(update_fields=[*fields, "updated_at"])
    return opp


def move_stage(opp: "Opportunity", *, stage_id: int, actor_id: int | None = None) -> "Opportunity":
    """Move an opportunity to a different pipeline stage."""
    from simorgh.apps.crm.models import PipelineStage
    from simorgh.apps.events.bus import dispatch

    stage = PipelineStage.objects.get(pk=stage_id)
    opp.pipeline_stage = stage
    opp.save(update_fields=["pipeline_stage", "updated_at"])
    dispatch(
        "crm.opportunity.stage_changed",
        {
            "opportunity_id": str(opp.public_id),
            "stage_id": str(stage.public_id),
            "tenant_id": opp.tenant_id,
            "actor_id": actor_id,
        },
    )
    return opp


def mark_won(opp: "Opportunity", *, closed_at=None, actor_id: int | None = None) -> "Opportunity":
    """Mark an opportunity as WON."""
    from django.utils import timezone
    from simorgh.apps.events.bus import dispatch

    opp.status = "won"
    opp.closed_at = closed_at or timezone.now()
    opp.save(update_fields=["status", "closed_at", "updated_at"])
    dispatch(
        "crm.opportunity.won",
        {"opportunity_id": str(opp.public_id), "tenant_id": opp.tenant_id, "actor_id": actor_id},
    )
    return opp


def mark_lost(
    opp: "Opportunity", *, reason: str = "", actor_id: int | None = None
) -> "Opportunity":
    """Mark an opportunity as LOST."""
    from django.utils import timezone
    from simorgh.apps.events.bus import dispatch

    opp.status = "lost"
    opp.lost_reason = reason
    opp.closed_at = timezone.now()
    opp.save(update_fields=["status", "lost_reason", "closed_at", "updated_at"])
    dispatch(
        "crm.opportunity.lost",
        {"opportunity_id": str(opp.public_id), "tenant_id": opp.tenant_id, "actor_id": actor_id},
    )
    return opp


def delete_opportunity(opp: "Opportunity") -> None:
    """Soft-delete an opportunity."""
    opp.delete()


# ===========================================================================
# CRM Activity services
# ===========================================================================

def create_activity(
    *,
    tenant_id: int,
    type: str = "task",
    title: str,
    owner_id: int | None = None,
    contact_id: int | None = None,
    opportunity_id: int | None = None,
    lead_id: int | None = None,
    notes: str = "",
    due_at=None,
) -> "CRMActivity":
    """Log a new CRM activity."""
    from simorgh.apps.crm.models import CRMActivity

    return CRMActivity.objects.create(
        tenant_id=tenant_id,
        type=type,
        title=title.strip(),
        owner_id=owner_id,
        contact_id=contact_id,
        opportunity_id=opportunity_id,
        lead_id=lead_id,
        notes=notes,
        due_at=due_at,
    )


def update_activity(
    activity: "CRMActivity",
    *,
    title: str | None = None,
    type: str | None = None,
    notes: str | None = None,
    due_at=None,
    owner_id: int | None = None,
) -> "CRMActivity":
    """Update mutable activity fields."""
    fields = []
    if title is not None:
        activity.title = title.strip(); fields.append("title")
    if type is not None:
        activity.type = type; fields.append("type")
    if notes is not None:
        activity.notes = notes; fields.append("notes")
    if due_at is not None:
        activity.due_at = due_at; fields.append("due_at")
    if owner_id is not None:
        activity.owner_id = owner_id; fields.append("owner")
    if fields:
        activity.save(update_fields=[*fields, "updated_at"])
    return activity


def complete_activity(activity: "CRMActivity", *, actor_id: int | None = None) -> "CRMActivity":
    """Mark an activity as done."""
    from django.utils import timezone

    activity.is_done = True
    activity.completed_at = timezone.now()
    activity.save(update_fields=["is_done", "completed_at", "updated_at"])
    return activity


def delete_activity(activity: "CRMActivity") -> None:
    """Delete a CRM activity (hard delete — no soft-delete on activities)."""
    activity.delete()



def remove_tag_from_contact(contact: "Contact", tag: "ContactTag") -> None:
    """Remove a tag from a contact."""
    contact.tags.remove(tag)


def merge_contacts(*, primary: "Contact", duplicate: "Contact") -> "Contact":
    """Merge ``duplicate`` into ``primary``.

    All child rows (phones, emails, addresses) are re-parented to ``primary``.
    Tags from duplicate are added to primary. Duplicate is then soft-deleted.

    Returns the surviving primary contact.
    """
    from simorgh.apps.crm.models import ContactAddress, ContactEmail, ContactPhone

    if primary.pk == duplicate.pk:
        raise ValueError("Cannot merge a contact with itself.")
    if primary.tenant_id != duplicate.tenant_id:
        raise ValueError("Cannot merge contacts from different tenants.")

    with transaction.atomic():
        ContactPhone.objects.filter(contact=duplicate).update(contact=primary)
        ContactEmail.objects.filter(contact=duplicate).update(contact=primary)
        ContactAddress.objects.filter(contact=duplicate).update(contact=primary)
        for tag in duplicate.tags.all():
            primary.tags.add(tag)
        duplicate.delete()

    return primary
