"""Identity stitching: collapses records from all sources into Person entries keyed by email."""

from datetime import datetime, timezone

from models import FacebookLead, MailchimpMember, Person, StripeCustomer

# Statuses considered "lost" for trial-cohort accounting.
DEAL_LOST_STATUSES = {"canceled", "incomplete_expired", "unpaid"}


def build_people(
    fb_leads: list[FacebookLead],
    mc_members: list[MailchimpMember],
    stripe_customers: list[StripeCustomer],
) -> list[Person]:
    """Group records by normalized email and build one Person per unique email.

    `first_touch_at` is the earliest of: Facebook lead time, Mailchimp subscribe time,
    Stripe customer creation time. `first_touch_source` is "facebook" / "newsletter" /
    "stripe" accordingly, or "unknown" if no timestamps exist on the record.

    When the same email appears multiple times in one source (e.g., across Mailchimp
    audiences, or duplicate Stripe customers), the earliest record wins.
    """
    people: dict[str, Person] = {}

    for lead in fb_leads:
        p = people.setdefault(lead.email, _empty_person(lead.email))
        if p.facebook_lead_at is None or lead.created_at < p.facebook_lead_at:
            p.facebook_lead_at = lead.created_at
            p.facebook_campaign_id = lead.campaign_id
            p.facebook_campaign_name = lead.campaign_name

    for member in mc_members:
        p = people.setdefault(member.email, _empty_person(member.email))
        if (
            p.newsletter_subscribed_at is None
            or member.subscribed_at < p.newsletter_subscribed_at
        ):
            p.newsletter_subscribed_at = member.subscribed_at

    for customer in stripe_customers:
        p = people.setdefault(customer.email, _empty_person(customer.email))
        if (
            p.stripe_customer_created_at is None
            or customer.customer_created_at < p.stripe_customer_created_at
        ):
            p.stripe_customer_created_at = customer.customer_created_at
            p.newspaper_subscribed_at = customer.first_paid_at
            p.newspaper_subscription_status = customer.subscription_status
            p.deal_started_at = customer.deal_started_at
            p.deal_status = customer.deal_status
            p.deal_trial_end = customer.deal_trial_end

    now = datetime.now(timezone.utc)
    for p in people.values():
        _assign_first_touch(p)
        p.deal_outcome = _compute_deal_outcome(p, now)

    return list(people.values())


def _compute_deal_outcome(p: Person, now: datetime) -> str | None:
    """Classify a deal subscriber as in_trial / converted / lost / other.

    None when the person never took the deal.
    """
    if p.deal_started_at is None:
        return None
    if p.deal_status == "trialing":
        return "in_trial"
    if p.deal_status == "active":
        return "converted"
    if p.deal_status in DEAL_LOST_STATUSES:
        return "lost"
    return "other"


def _empty_person(email: str) -> Person:
    return Person(email=email, first_touch_at=None, first_touch_source="unknown")


def _assign_first_touch(p: Person) -> None:
    candidates: list[tuple] = []
    if p.facebook_lead_at is not None:
        candidates.append((p.facebook_lead_at, "facebook"))
    if p.newsletter_subscribed_at is not None:
        candidates.append((p.newsletter_subscribed_at, "newsletter"))
    if p.stripe_customer_created_at is not None:
        candidates.append((p.stripe_customer_created_at, "stripe"))
    if candidates:
        p.first_touch_at, p.first_touch_source = min(candidates, key=lambda c: c[0])
    else:
        p.first_touch_source = "unknown"
