#!/usr/bin/env python3
"""Materialize the normal follow-up lifecycle for existing opportunities.

Dry-run is the default. v4928.1.5.127 no longer creates automatic
``CONFIRM_DELIVERY`` tasks. Existing processes start directly at the normal
commercial follow-up for their stage. Delivery verification remains an
explicit operator action.

The backfill is intentionally conservative:
- zero-value opportunities are excluded by default;
- opportunities with any pending human task are skipped;
- recovery/nurture queues remain supported;
- no customer communication is sent.
"""
from __future__ import annotations

import argparse
from datetime import datetime, timezone
from pathlib import Path
import sys
from typing import Any

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from sqlalchemy import text

STAGE_FAMILY = {
    "INFO_SENT": "info",
    "QUOTE_SENT": "quote",
    "PROFORMA_SENT": "payment",
    "INVOICE_SENT": "payment",
    "WAITING_PAYMENT": "payment",
}

ACTION_LABEL = {
    "info": "follow_up_information",
    "quote": "follow_up_quote",
    "payment": "follow_up_payment",
    "generic": "follow_up_generic",
}


def _rows(limit: int, include_zero_value: bool = False) -> list[dict[str, Any]]:
    from app.db import engine
    from app.opportunity_service import ensure_opportunity_schema

    ensure_opportunity_schema()
    sql = text("""
        SELECT
            o.id::text,
            o.customer_name,
            o.title,
            o.stage,
            o.value_amount,
            o.lifecycle_state,
            o.last_customer_activity_at,
            o.last_operator_activity_at,
            o.last_commercial_activity_at,
            o.last_message_at,
            o.next_follow_up_at,
            o.nurture_until,
            o.follow_up_attempts
        FROM opportunities o
        WHERE o.status = 'open'
          AND (
            o.stage IN ('INFO_SENT','QUOTE_SENT','PROFORMA_SENT','INVOICE_SENT','WAITING_PAYMENT')
            OR o.lifecycle_state IN ('recovery','nurture')
          )
          AND (
            CAST(:include_zero_value AS BOOLEAN)
            OR COALESCE(o.value_amount, 0) > 0
            OR o.lifecycle_state IN ('recovery','nurture')
          )
          AND NOT EXISTS (
            SELECT 1
            FROM tasks t
            WHERE t.opportunity_id = o.id
              AND t.status = 'pending'
              AND COALESCE(t.action_required, TRUE) = TRUE
          )
        ORDER BY
          CASE WHEN o.lifecycle_state = 'recovery' THEN 0
               WHEN o.stage = 'WAITING_PAYMENT' THEN 1
               WHEN COALESCE(o.value_amount,0) > 0 THEN 2
               ELSE 3 END,
          COALESCE(o.value_amount,0) DESC,
          COALESCE(o.last_commercial_activity_at, o.last_message_at, o.created_at) ASC
        LIMIT :limit
    """)
    with engine.begin() as conn:
        return [
            dict(row)
            for row in conn.execute(
                sql,
                {
                    "limit": max(1, int(limit)),
                    "include_zero_value": bool(include_zero_value),
                },
            ).mappings().all()
        ]


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--apply", action="store_true", help="Create missing tasks. Default is dry-run.")
    parser.add_argument("--limit", type=int, default=50)
    parser.add_argument(
        "--include-zero-value",
        action="store_true",
        help="Include zero-value opportunities. Disabled by default.",
    )
    args = parser.parse_args()

    from app.db import engine
    from app.followup_service import create_follow_up_task, materialize_initial_follow_up_for_opportunity

    candidates = _rows(args.limit, include_zero_value=args.include_zero_value)
    print(f"Candidates without pending human task: {len(candidates)}")
    created = 0
    now = datetime.now(timezone.utc)

    for row in candidates:
        opportunity_id = str(row.get("id") or "")
        state = str(row.get("lifecycle_state") or "active")
        stage = str(row.get("stage") or "")
        family = STAGE_FAMILY.get(stage, "generic")
        value = float(row.get("value_amount") or 0)
        label = str(row.get("customer_name") or row.get("title") or opportunity_id)
        action = ACTION_LABEL.get(family, "follow_up_generic")

        if state == "recovery":
            action = "recovery_review"
        elif state == "nurture":
            action = "nurture_review"

        print(f"{action:22} | {stage:18} | {value:9.2f} | {label}")
        if not args.apply:
            continue

        # Backfill only from message/commercial timestamps, never from updated_at.
        with engine.begin() as conn:
            conn.execute(text("""
                UPDATE opportunities
                SET last_customer_activity_at = COALESCE(last_customer_activity_at, last_message_at),
                    last_commercial_activity_at = COALESCE(
                        last_commercial_activity_at,
                        last_operator_activity_at,
                        last_customer_activity_at,
                        last_message_at
                    ),
                    metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object(
                        'lifecycle_backfill_version', 'v4928.1.5.127',
                        'lifecycle_backfill_used_updated_at', FALSE,
                        'automatic_confirm_delivery_disabled', TRUE
                    )
                WHERE id = CAST(:opportunity_id AS UUID)
            """), {"opportunity_id": opportunity_id})

        if state == "recovery":
            result = create_follow_up_task(
                opportunity_id=opportunity_id,
                action_code="RECOVER_OPPORTUNITY",
                route="financeiro" if family == "payment" else "vendas",
                action="Recuperar oportunidade sem resposta",
                note="Rever canal, abordagem, timing e decidir entre nova tentativa, acompanhamento futuro ou perda.",
                reason="RECOVERY_BACKFILL_V127",
                delay_days=1,
                created_by="migration_v4928_1_5_127",
                idempotency_suffix="recovery-backfill-v127",
                follow_up_family=family,
                follow_up_stage=99,
                follow_up_max_stage=99,
                cascade=False,
                contact_purpose="recovery_review",
                due_at_override=now,
            )
        elif state == "nurture":
            due_at = row.get("nurture_until") or row.get("next_follow_up_at") or now
            result = create_follow_up_task(
                opportunity_id=opportunity_id,
                action_code="REVIEW_NURTURE",
                route="vendas",
                action="Rever oportunidade em acompanhamento futuro",
                note="Retomar contacto na data acordada ou rever se o timing continua válido.",
                reason="NURTURE_BACKFILL_V127",
                delay_days=1,
                created_by="migration_v4928_1_5_127",
                idempotency_suffix="nurture-backfill-v127",
                follow_up_family="generic",
                follow_up_stage=99,
                follow_up_max_stage=99,
                cascade=False,
                contact_purpose="nurture_review",
                due_at_override=due_at,
            )
        else:
            result = materialize_initial_follow_up_for_opportunity(
                opportunity_id=opportunity_id,
                family=family,
                due_at=None,
                reason="LEGACY_OPEN_OPPORTUNITY_BACKFILL_V127",
                created_by="migration_v4928_1_5_127",
            )
        if result.get("status") == "created":
            created += 1

    if args.apply:
        print(f"Created: {created}")
    else:
        print("Dry-run only. Use --apply after reviewing the candidates.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
