"""
backend/services/client_graph/aggregation_service.py
Client 360 Aggregation Service

Produces Client360Snapshot read models by joining across
crm_people, crm_leads, crm_opportunities, intel_interactions,
intel_reminders, intel_qd_scores, crm_property_interests.

This is a derived read model — never the sole source of truth.
As specified in Doc 07 (Client360Snapshot contract) and Doc 08 (Adapter Spec).
"""
from __future__ import annotations

import json
import logging
from typing import Any

logger = logging.getLogger("velocity.client_graph.aggregation")


def _json_string_list(value: Any) -> list[str]:
    """Normalize canonical array fields that may arrive as jsonb, text[], or JSON text."""
    if value is None:
        return []
    if isinstance(value, list | tuple):
        return [str(item) for item in value if item is not None]
    if isinstance(value, str):
        normalized = value.strip()
        if not normalized:
            return []
        try:
            parsed = json.loads(normalized)
        except json.JSONDecodeError:
            return [normalized]
        if isinstance(parsed, list):
            return [str(item) for item in parsed if item is not None]
        if parsed is None:
            return []
        return [str(parsed)]
    return [str(value)]


def _serialize_person(row: Any) -> dict[str, Any]:
    return {
        "person_id": str(row["person_id"]),
        "full_name": row["full_name"],
        "primary_email": row["primary_email"],
        "primary_phone": row["primary_phone"],
        "buyer_type": row["buyer_type"],
        "persona_labels": _json_string_list(row["persona_labels"]),
        "source_confidence": float(row["source_confidence"] or 0.0),
        "created_at": row["created_at"].isoformat() if row["created_at"] else None,
    }


def _serialize_lead(row: Any) -> dict[str, Any]:
    return {
        "lead_id": str(row["lead_id"]),
        "status": row["status"],
        "budget_band": row["budget_band"],
        "urgency": row["urgency"],
        "financing_posture": row["financing_posture"],
        "timeline_to_decision": row["timeline_to_decision"],
        "objections": _json_string_list(row["objections"]),
        "motivations": _json_string_list(row["motivations"]),
        "created_at": row["created_at"].isoformat() if row["created_at"] else None,
    }


def _serialize_opportunity(row: Any) -> dict[str, Any]:
    return {
        "opportunity_id": str(row["opportunity_id"]),
        "stage": row["stage"],
        "value": float(row["value"]) if row["value"] else None,
        "probability": row["probability"],
        "expected_close_date": row["expected_close_date"].isoformat() if row["expected_close_date"] else None,
        "next_action": row["next_action"],
        "project_id": str(row["project_id"]) if row["project_id"] else None,
        "unit_id": str(row["unit_id"]) if row["unit_id"] else None,
    }


def _serialize_interaction(row: Any) -> dict[str, Any]:
    return {
        "interaction_id": str(row["interaction_id"]),
        "channel": row["channel"],
        "interaction_type": row["interaction_type"],
        "happened_at": row["happened_at"].isoformat() if row["happened_at"] else None,
        "summary": row["summary"],
    }


def _serialize_reminder(row: Any) -> dict[str, Any]:
    return {
        "reminder_id": str(row["reminder_id"]),
        "reminder_type": row["reminder_type"],
        "title": row["title"],
        "due_at": row["due_at"].isoformat() if row["due_at"] else None,
        "status": row["status"],
        "priority": row["priority"],
    }


def _serialize_qd_score(row: Any) -> dict[str, Any]:
    return {
        "score_type": row["score_type"],
        "current_value": float(row["current_value"]),
        "computed_at": row["computed_at"].isoformat() if row["computed_at"] else None,
        "reasoning": row["reasoning"],
    }


def _serialize_property_interest(row: Any) -> dict[str, Any]:
    return {
        "interest_id": str(row["interest_id"]),
        "project_name": row["project_name"],
        "unit_preference": row["unit_preference"],
        "configuration": row["configuration"],
        "budget_min": float(row["budget_min"]) if row["budget_min"] else None,
        "budget_max": float(row["budget_max"]) if row["budget_max"] else None,
        "priority": row["priority"],
    }


async def get_client_360(conn: Any, tenant_id: str, person_id: str) -> dict[str, Any] | None:
    """
    Aggregate a full Client360Snapshot for a given person_id.
    This is a read model — derived from canonical tables, never primary truth.
    """
    # 1. Core identity
    person_row = await conn.fetchrow(
        """
        SELECT person_id, full_name, primary_email, primary_phone,
               buyer_type, persona_labels, source_confidence, created_at
        FROM crm_people
        WHERE person_id = $1::uuid
          AND tenant_id = $2
        """,
        person_id,
        tenant_id,
    )
    if not person_row:
        return None

    identity = _serialize_person(person_row)

    # 2. Account links
    account_rows = await conn.fetch(
        """
        SELECT ca.account_id, ca.account_name, ca.account_type, ca.industry
        FROM crm_accounts ca
        INNER JOIN crm_leads cl ON cl.account_id = ca.account_id
        WHERE cl.person_id = $1::uuid
          AND cl.tenant_id = $2
          AND ca.tenant_id = $2
        LIMIT 5
        """,
        person_id,
        tenant_id,
    )
    account_links = [
        {
            "account_id": str(r["account_id"]),
            "account_name": r["account_name"],
            "account_type": r["account_type"],
            "industry": r["industry"],
        }
        for r in account_rows
    ]

    # 3. Active lead
    lead_row = await conn.fetchrow(
        """
        SELECT lead_id, status, budget_band, urgency, financing_posture,
               timeline_to_decision, objections, motivations, created_at
        FROM crm_leads
        WHERE person_id = $1::uuid
          AND tenant_id = $2
        ORDER BY created_at DESC
        LIMIT 1
        """,
        person_id,
        tenant_id,
    )
    lead = _serialize_lead(lead_row) if lead_row else None

    # 4. Active opportunities (top 5)
    opp_rows = await conn.fetch(
        """
        SELECT co.opportunity_id, co.stage, co.value, co.probability,
               co.expected_close_date, co.next_action, co.project_id, co.unit_id
        FROM crm_opportunities co
        INNER JOIN crm_leads cl ON cl.lead_id = co.lead_id
        WHERE cl.person_id = $1::uuid
          AND cl.tenant_id = $2
          AND co.tenant_id = $2
        ORDER BY co.updated_at DESC
        LIMIT 5
        """,
        person_id,
        tenant_id,
    )
    active_opportunities = [_serialize_opportunity(r) for r in opp_rows]

    # 5. Recent interactions (last 10)
    interaction_rows = await conn.fetch(
        """
        SELECT interaction_id, channel, interaction_type, happened_at, summary
        FROM intel_interactions
        WHERE person_id = $1::uuid
          AND tenant_id = $2
        ORDER BY happened_at DESC
        LIMIT 10
        """,
        person_id,
        tenant_id,
    )
    recent_interactions = [_serialize_interaction(r) for r in interaction_rows]

    # 6. Property interests
    interest_rows = await conn.fetch(
        """
        SELECT interest_id, project_name, unit_preference, configuration,
               budget_min, budget_max, priority
        FROM crm_property_interests
        WHERE person_id = $1::uuid
          AND tenant_id = $2
        ORDER BY priority ASC, interest_id ASC
        LIMIT 10
        """,
        person_id,
        tenant_id,
    )
    property_interests = [_serialize_property_interest(r) for r in interest_rows]

    # 7. Pending tasks / reminders
    task_rows = await conn.fetch(
        """
        SELECT reminder_id, reminder_type, title, due_at, status, priority
        FROM intel_reminders
        WHERE person_id = $1::uuid
          AND tenant_id = $2
          AND status IN ('pending', 'snoozed')
        ORDER BY due_at ASC NULLS LAST
        LIMIT 10
        """,
        person_id,
        tenant_id,
    )
    tasks = [_serialize_reminder(r) for r in task_rows]

    # 8. QD overview (all score types)
    qd_rows = await conn.fetch(
        """
        SELECT score_type, current_value, computed_at, reasoning
        FROM intel_qd_scores
        WHERE person_id = $1::uuid
          AND tenant_id = $2
        """,
        person_id,
        tenant_id,
    )
    qd_overview = {r["score_type"]: _serialize_qd_score(r) for r in qd_rows}

    # 9. Risk flags — heuristic derivation
    risk_flags: list[str] = []
    if lead and lead.get("urgency") in ("high", "critical") and not active_opportunities:
        risk_flags.append("high_urgency_without_active_opportunity")
    if not recent_interactions:
        risk_flags.append("no_recent_interactions")
    if qd_overview.get("intent_score", {}).get("current_value", 1.0) < 0.3:
        risk_flags.append("low_intent_score")
    if not property_interests:
        risk_flags.append("no_property_interests_recorded")

    # 10. Recommended next actions — simple heuristic
    recommended_next_actions: list[str] = []
    if tasks:
        overdue = [t for t in tasks if t.get("status") == "pending"]
        if overdue:
            recommended_next_actions.append(f"Complete pending task: {overdue[0]['title']}")
    if lead and lead.get("urgency") in ("high", "critical"):
        recommended_next_actions.append("High-urgency client — prioritize callback within 24h")
    if not recent_interactions and lead:
        recommended_next_actions.append("No recent interactions — schedule follow-up")

    return {
        "client_ref": person_id,
        "snapshot_type": "client_360",
        "identity": identity,
        "account_links": account_links,
        "current_lead": lead,
        "active_opportunities": active_opportunities,
        "recent_interactions": recent_interactions,
        "property_interests": property_interests,
        "tasks": tasks,
        "qd_overview": qd_overview,
        "risk_flags": risk_flags,
        "recommended_next_actions": recommended_next_actions,
        "note": "Derived read model. Not primary truth. Refresh from canonical tables.",
    }


async def get_contact_list(
    conn: Any,
    tenant_id: str,
    search: str | None = None,
    buyer_type: str | None = None,
    status: str | None = None,
    limit: int = 50,
    offset: int = 0,
) -> dict[str, Any]:
    """
    Paginated contact list with lead status and QD summary.
    Implements the 'summary query' pattern from Doc 09.
    """
    clauses: list[str] = ["p.tenant_id = $1"]
    params: list[Any] = [tenant_id]

    if search:
        params.append(f"%{search}%")
        clauses.append(
            f"(p.full_name ILIKE ${len(params)} OR p.primary_email ILIKE ${len(params)} OR p.primary_phone ILIKE ${len(params)})"
        )
    if buyer_type:
        params.append(buyer_type)
        clauses.append(f"p.buyer_type = ${len(params)}")
    if status:
        params.append(status)
        clauses.append(f"cl.status = ${len(params)}::crm_lead_status")

    where = "WHERE " + " AND ".join(clauses)
    params_for_count = params.copy()

    params.append(limit)
    params.append(offset)

    query = f"""
        SELECT
            p.person_id,
            p.full_name,
            p.primary_email,
            p.primary_phone,
            p.buyer_type,
            p.legacy_li_id,
            p.created_at,
            cl.lead_id,
            cl.status AS lead_status,
            cl.budget_band,
            cl.urgency,
            pi.project_name AS primary_interest,
            COALESCE(qs.intent_value, 0.0) AS intent_score,
            COALESCE(qs.engagement_value, qs.intent_value, 0.0) AS engagement_score,
            COALESCE(qs.urgency_value, 0.0) AS urgency_score,
            (SELECT COUNT(*) FROM intel_interactions ii WHERE ii.person_id = p.person_id AND ii.tenant_id = p.tenant_id) AS interaction_count,
            (SELECT MAX(happened_at) FROM intel_interactions ii WHERE ii.person_id = p.person_id AND ii.tenant_id = p.tenant_id) AS last_interaction_at,
            (SELECT COUNT(*) FROM intel_reminders ir WHERE ir.person_id = p.person_id AND ir.tenant_id = p.tenant_id AND ir.status = 'pending') AS pending_tasks
        FROM crm_people p
        LEFT JOIN LATERAL (
            SELECT lead_id, status, budget_band, urgency
            FROM crm_leads
            WHERE person_id = p.person_id
              AND tenant_id = p.tenant_id
            ORDER BY created_at DESC
            LIMIT 1
        ) cl ON TRUE
        LEFT JOIN LATERAL (
            SELECT project_name
            FROM crm_property_interests
            WHERE person_id = p.person_id
              AND tenant_id = p.tenant_id
            ORDER BY priority ASC, created_at DESC
            LIMIT 1
        ) pi ON TRUE
        LEFT JOIN LATERAL (
            SELECT
                MAX(CASE WHEN score_type = 'intent_score' THEN current_value END) AS intent_value,
                MAX(CASE WHEN score_type = 'engagement_score' THEN current_value END) AS engagement_value,
                MAX(CASE WHEN score_type = 'urgency_score' THEN current_value END) AS urgency_value
            FROM intel_qd_scores
            WHERE person_id = p.person_id
              AND tenant_id = p.tenant_id
        ) qs ON TRUE
        {where}
        ORDER BY last_interaction_at DESC NULLS LAST, p.created_at DESC
        LIMIT ${len(params) - 1} OFFSET ${len(params)}
    """

    count_query = f"""
        SELECT COUNT(*)
        FROM crm_people p
        LEFT JOIN crm_leads cl ON cl.person_id = p.person_id AND cl.tenant_id = p.tenant_id
        {where}
    """

    rows = await conn.fetch(query, *params)
    total_row = await conn.fetchrow(count_query, *params_for_count)
    total = int(total_row[0]) if total_row else 0

    contacts = []
    for r in rows:
        contacts.append({
            "person_id": str(r["person_id"]),
            "full_name": r["full_name"],
            "primary_email": r["primary_email"],
            "primary_phone": r["primary_phone"],
            "buyer_type": r["buyer_type"],
            "lead_id": str(r["lead_id"]) if r["lead_id"] else None,
            "legacy_li_id": str(r["legacy_li_id"]) if r["legacy_li_id"] else None,
            "lead_status": r["lead_status"],
            "budget_band": r["budget_band"],
            "urgency": r["urgency"],
            "primary_interest": r["primary_interest"],
            "intent_score": float(r["intent_score"]),
            "engagement_score": float(r["engagement_score"]),
            "urgency_score": float(r["urgency_score"]),
            "interaction_count": int(r["interaction_count"]),
            "last_interaction_at": r["last_interaction_at"].isoformat() if r["last_interaction_at"] else None,
            "pending_tasks": int(r["pending_tasks"]),
            "created_at": r["created_at"].isoformat() if r["created_at"] else None,
        })

    return {
        "contacts": contacts,
        "total": total,
        "limit": limit,
        "offset": offset,
    }
