"""CRM domain models.

Model inventory
---------------
ContactType       — enum: person / company / lead
ContactTag        — per-tenant flat label
Contact           — core entity: person or company
ContactAddress    — multiple addresses per contact (billing/shipping/other)
ContactPhone      — multiple phones per contact
ContactEmail      — multiple emails per contact

Design decisions
----------------
* All business entities extend ``TenantScopedModel`` (tenant + org_node FKs).
* ``UUIDModel`` exposes ``public_id`` for external API identifiers.
* ``SoftDeleteModel`` on Contact so audit trails survive deletion.
* Phones/emails stored as child rows (1:N) — cleaner than JSONB arrays and
  queryable. JSONField ``custom_fields`` handles arbitrary extra attributes.
* Addresses stored as child rows too; is_default ensures at most one default
  per (contact, address_type) via a UniqueConstraint.
"""

from __future__ import annotations

from typing import ClassVar

from django.conf import settings
from django.core.validators import MaxValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import (
    SoftDeleteModel,
    TenantScopedModel,
    TimeStampedModel,
    UUIDModel,
)


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class ContactType(models.TextChoices):
    PERSON = "person", _("Person")
    COMPANY = "company", _("Company")
    LEAD = "lead", _("Lead")


class AddressType(models.TextChoices):
    BILLING = "billing", _("Billing")
    SHIPPING = "shipping", _("Shipping")
    OTHER = "other", _("Other")


class PhoneType(models.TextChoices):
    MOBILE = "mobile", _("Mobile")
    WORK = "work", _("Work")
    HOME = "home", _("Home")
    FAX = "fax", _("Fax")
    OTHER = "other", _("Other")


class EmailType(models.TextChoices):
    WORK = "work", _("Work")
    PERSONAL = "personal", _("Personal")
    OTHER = "other", _("Other")


# ---------------------------------------------------------------------------
# ContactTag
# ---------------------------------------------------------------------------

class ContactTag(UUIDModel, TimeStampedModel):
    """Flat, tenant-scoped label applied to contacts."""

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="contact_tags",
        verbose_name=_("tenant"),
    )
    name = models.CharField(_("name"), max_length=64)
    color = models.CharField(_("color"), max_length=16, blank=True, default="")

    class Meta:
        verbose_name = _("contact tag")
        verbose_name_plural = _("contact tags")
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("tenant", "name"),
                name="crm_contacttag_unique_per_tenant",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.tenant_id}:{self.name}"


# ---------------------------------------------------------------------------
# Contact
# ---------------------------------------------------------------------------

class Contact(UUIDModel, TenantScopedModel, SoftDeleteModel):
    """Core CRM entity — a person, company, or unqualified lead.

    ``parent_company`` links individual contacts to their employer (company
    contact). It is nullable — standalone persons and companies have no parent.
    ``tags`` is a M2M through a simple join table (no extra metadata needed).
    ``custom_fields`` is the extensibility seam until the Schema Engine is wired.
    """

    type = models.CharField(
        _("type"),
        max_length=16,
        choices=ContactType.choices,
        default=ContactType.PERSON,
        db_index=True,
    )
    name = models.CharField(_("name"), max_length=255, db_index=True)
    parent_company = models.ForeignKey(
        "self",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="employees",
        verbose_name=_("parent company"),
        limit_choices_to={"type": ContactType.COMPANY},
    )
    tags = models.ManyToManyField(
        ContactTag,
        blank=True,
        related_name="contacts",
        verbose_name=_("tags"),
    )
    website = models.URLField(_("website"), max_length=255, blank=True, default="")
    notes = models.TextField(_("notes"), blank=True, default="")
    is_customer = models.BooleanField(_("is customer"), default=False, db_index=True)
    is_vendor = models.BooleanField(_("is vendor"), default=False, db_index=True)
    custom_fields = models.JSONField(_("custom fields"), default=dict, blank=True)

    class Meta:
        verbose_name = _("contact")
        verbose_name_plural = _("contacts")
        ordering: ClassVar[list[str]] = ["name"]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "type")),
            models.Index(fields=("tenant", "name")),
        ]

    def __str__(self) -> str:
        return f"{self.name} ({self.get_type_display()})"


# ---------------------------------------------------------------------------
# ContactAddress
# ---------------------------------------------------------------------------

class ContactAddress(UUIDModel, TimeStampedModel):
    """A postal address attached to a contact."""

    contact = models.ForeignKey(
        Contact,
        on_delete=models.CASCADE,
        related_name="addresses",
        verbose_name=_("contact"),
    )
    type = models.CharField(
        _("type"),
        max_length=16,
        choices=AddressType.choices,
        default=AddressType.OTHER,
    )
    street = models.CharField(_("street"), max_length=255, blank=True, default="")
    city = models.CharField(_("city"), max_length=128, blank=True, default="")
    state = models.CharField(_("state/province"), max_length=128, blank=True, default="")
    postal_code = models.CharField(_("postal code"), max_length=32, blank=True, default="")
    country = models.CharField(_("country"), max_length=64, blank=True, default="")
    is_default = models.BooleanField(_("is default"), default=False)

    class Meta:
        verbose_name = _("contact address")
        verbose_name_plural = _("contact addresses")
        ordering: ClassVar[list[str]] = ["-is_default", "type"]

    def __str__(self) -> str:
        return f"{self.contact_id} — {self.get_type_display()}: {self.city}, {self.country}"


# ---------------------------------------------------------------------------
# ContactPhone
# ---------------------------------------------------------------------------

class ContactPhone(UUIDModel, TimeStampedModel):
    """A phone number attached to a contact."""

    contact = models.ForeignKey(
        Contact,
        on_delete=models.CASCADE,
        related_name="phones",
        verbose_name=_("contact"),
    )
    type = models.CharField(
        _("type"),
        max_length=16,
        choices=PhoneType.choices,
        default=PhoneType.MOBILE,
    )
    number = models.CharField(_("number"), max_length=64)
    is_default = models.BooleanField(_("is default"), default=False)

    class Meta:
        verbose_name = _("contact phone")
        verbose_name_plural = _("contact phones")
        ordering: ClassVar[list[str]] = ["-is_default", "type"]

    def __str__(self) -> str:
        return f"{self.contact_id}: {self.number}"


# ---------------------------------------------------------------------------
# ContactEmail
# ---------------------------------------------------------------------------

class ContactEmail(UUIDModel, TimeStampedModel):
    """An email address attached to a contact."""

    contact = models.ForeignKey(
        Contact,
        on_delete=models.CASCADE,
        related_name="emails",
        verbose_name=_("contact"),
    )
    type = models.CharField(
        _("type"),
        max_length=16,
        choices=EmailType.choices,
        default=EmailType.WORK,
    )
    address = models.EmailField(_("address"), max_length=254)
    is_default = models.BooleanField(_("is default"), default=False)

    class Meta:
        verbose_name = _("contact email")
        verbose_name_plural = _("contact emails")
        ordering: ClassVar[list[str]] = ["-is_default", "type"]

    def __str__(self) -> str:
        return f"{self.contact_id}: {self.address}"


# ===========================================================================
# Pipeline / Lead / Opportunity / CRM Activity  (S1.2)
# ===========================================================================

# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class LeadSource(models.TextChoices):
    WEBSITE = "website", _("Website")
    REFERRAL = "referral", _("Referral")
    COLD_CALL = "cold_call", _("Cold Call")
    SOCIAL = "social", _("Social")
    EVENT = "event", _("Event")
    TRADE_SHOW = "trade_show", _("Trade Show")
    MANUAL = "manual", _("Manual")


class LeadStatus(models.TextChoices):
    NEW = "new", _("New")
    CONTACTED = "contacted", _("Contacted")
    QUALIFIED = "qualified", _("Qualified")
    CONVERTED = "converted", _("Converted")
    LOST = "lost", _("Lost")


class OpportunityStatus(models.TextChoices):
    OPEN = "open", _("Open")
    WON = "won", _("Won")
    LOST = "lost", _("Lost")


class ActivityType(models.TextChoices):
    CALL = "call", _("Call")
    MEETING = "meeting", _("Meeting")
    EMAIL = "email", _("Email")
    TASK = "task", _("Task")
    NOTE = "note", _("Note")


# ---------------------------------------------------------------------------
# Pipeline
# ---------------------------------------------------------------------------

class Pipeline(UUIDModel, TenantScopedModel):
    """Named sequence of stages used to track deal progress.

    One pipeline can be marked ``is_default``; the system will pick this one
    when qualifying a lead without an explicit pipeline choice.
    """

    name = models.CharField(_("name"), max_length=100)
    is_default = models.BooleanField(_("default"), default=False)

    class Meta:
        verbose_name = _("pipeline")
        verbose_name_plural = _("pipelines")
        ordering: ClassVar[list[str]] = ["name"]
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("tenant", "name"),
                name="crm_pipeline_unique_per_tenant",
            ),
        ]

    def __str__(self) -> str:
        return self.name


# ---------------------------------------------------------------------------
# PipelineStage
# ---------------------------------------------------------------------------

class PipelineStage(UUIDModel, TimeStampedModel):
    """A single step inside a pipeline (e.g. "Proposal", "Won").

    Exactly one stage per pipeline should have ``is_won=True`` and one should
    have ``is_lost=True``.  The ``probability_pct`` is used as the default
    when creating an Opportunity at this stage (unless the agent overrides it).
    """

    pipeline = models.ForeignKey(
        Pipeline,
        on_delete=models.CASCADE,
        related_name="stages",
        verbose_name=_("pipeline"),
    )
    name = models.CharField(_("name"), max_length=100)
    order = models.PositiveSmallIntegerField(_("order"), default=0)
    probability_pct = models.PositiveSmallIntegerField(
        _("probability (%)"),
        default=0,
        validators=[MaxValueValidator(100)],
    )
    color = models.CharField(_("color"), max_length=20, blank=True, default="#6366f1")
    is_won = models.BooleanField(_("is won"), default=False)
    is_lost = models.BooleanField(_("is lost"), default=False)

    class Meta:
        verbose_name = _("pipeline stage")
        verbose_name_plural = _("pipeline stages")
        ordering: ClassVar[list[str]] = ["pipeline", "order"]

    def __str__(self) -> str:
        return f"{self.pipeline.name} / {self.name}"


# ---------------------------------------------------------------------------
# Lead
# ---------------------------------------------------------------------------

class Lead(UUIDModel, TenantScopedModel, SoftDeleteModel):
    """An inbound prospect that may later be qualified into an Opportunity.

    Soft-deleted so that historical data survives conversion/loss.
    The ``owner`` is the sales rep responsible for working this lead.
    """

    title = models.CharField(_("title"), max_length=200)
    contact = models.ForeignKey(
        Contact,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="leads",
        verbose_name=_("contact"),
    )
    source = models.CharField(
        _("source"),
        max_length=20,
        choices=LeadSource.choices,
        default=LeadSource.MANUAL,
    )
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="owned_leads",
        verbose_name=_("owner"),
    )
    score = models.PositiveSmallIntegerField(
        _("score"),
        default=0,
        validators=[MaxValueValidator(100)],
    )
    status = models.CharField(
        _("status"),
        max_length=20,
        choices=LeadStatus.choices,
        default=LeadStatus.NEW,
    )
    notes = models.TextField(_("notes"), blank=True, default="")
    custom_fields = models.JSONField(_("custom fields"), default=dict, blank=True)

    class Meta:
        verbose_name = _("lead")
        verbose_name_plural = _("leads")
        ordering: ClassVar[list[str]] = ["-created_at"]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "status")),
            models.Index(fields=("tenant", "owner")),
        ]

    def __str__(self) -> str:
        return self.title


# ---------------------------------------------------------------------------
# Opportunity
# ---------------------------------------------------------------------------

class Opportunity(UUIDModel, TenantScopedModel, SoftDeleteModel):
    """A qualified sales deal attached to a pipeline stage.

    ``probability`` overrides the stage default when set.  ``closed_at`` is
    written by the service layer when status moves to WON or LOST.
    """

    lead = models.ForeignKey(
        Lead,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="opportunities",
        verbose_name=_("lead"),
    )
    pipeline_stage = models.ForeignKey(
        PipelineStage,
        on_delete=models.PROTECT,
        related_name="opportunities",
        verbose_name=_("pipeline stage"),
    )
    title = models.CharField(_("title"), max_length=200)
    contact = models.ForeignKey(
        Contact,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="opportunities",
        verbose_name=_("contact"),
    )
    amount = models.DecimalField(
        _("amount"),
        max_digits=14,
        decimal_places=2,
        default=0,
    )
    currency = models.CharField(_("currency"), max_length=3, default="USD")
    expected_close = models.DateField(_("expected close"), null=True, blank=True)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="owned_opportunities",
        verbose_name=_("owner"),
    )
    probability = models.PositiveSmallIntegerField(
        _("probability (%)"),
        null=True,
        blank=True,
        validators=[MaxValueValidator(100)],
    )
    status = models.CharField(
        _("status"),
        max_length=10,
        choices=OpportunityStatus.choices,
        default=OpportunityStatus.OPEN,
    )
    closed_at = models.DateTimeField(_("closed at"), null=True, blank=True)
    lost_reason = models.TextField(_("lost reason"), blank=True, default="")

    class Meta:
        verbose_name = _("opportunity")
        verbose_name_plural = _("opportunities")
        ordering: ClassVar[list[str]] = ["-created_at"]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "pipeline_stage")),
            models.Index(fields=("tenant", "owner", "status")),
        ]

    def __str__(self) -> str:
        return self.title


# ---------------------------------------------------------------------------
# CRM Activity
# ---------------------------------------------------------------------------

class CRMActivity(UUIDModel, TimeStampedModel):
    """A logged interaction (call, meeting, task …) linked to CRM entities.

    Belongs directly to a ``tenant`` (not a TenantScopedModel) because
    activities often span org-node boundaries (e.g. a cross-team call).
    All three object FK columns are nullable / optional — an activity can be
    attached to any combination of Contact, Lead, Opportunity.
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="crm_activities",
        verbose_name=_("tenant"),
    )
    type = models.CharField(
        _("type"),
        max_length=20,
        choices=ActivityType.choices,
        default=ActivityType.TASK,
    )
    contact = models.ForeignKey(
        Contact,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="activities",
        verbose_name=_("contact"),
    )
    opportunity = models.ForeignKey(
        Opportunity,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="activities",
        verbose_name=_("opportunity"),
    )
    lead = models.ForeignKey(
        Lead,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="activities",
        verbose_name=_("lead"),
    )
    title = models.CharField(_("title"), max_length=200)
    notes = models.TextField(_("notes"), blank=True, default="")
    due_at = models.DateTimeField(_("due at"), null=True, blank=True)
    completed_at = models.DateTimeField(_("completed at"), null=True, blank=True)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="crm_activities",
        verbose_name=_("owner"),
    )
    is_done = models.BooleanField(_("done"), default=False)

    class Meta:
        verbose_name = _("CRM activity")
        verbose_name_plural = _("CRM activities")
        ordering: ClassVar[list[str]] = ["-created_at"]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "owner", "is_done")),
            models.Index(fields=("tenant", "due_at")),
        ]

    def __str__(self) -> str:
        return self.title

