#!/usr/bin/env python3
"""
GHL Composable Module — MCTB (Multi-Channel Touch Base)
========================================================
5-touch nurture sequence for new leads:
  Touch 1: SMS (immediate — within 2 minutes of lead)
  Touch 2: Email (Day 1 — 4 hours after SMS)
  Touch 3: SMS (Day 2 — 24 hours after lead)
  Touch 4: Call task (Day 3 — 48 hours — assigned to team member in GHL)
  Touch 5: SMS (Day 5 — final touch, closes the loop)

Triggered by new lead in GHL (webhook from AIVA call, web form, or ad).
Personalised to tradie type (Plumber, Electrician, Builder, etc.)
Tracks touch state per contact in PostgreSQL.

This is the COMPOSABLE MODULE (business logic layer).
For the GHL API deployment script (creating workflows, custom fields),
see GHL_MODULES/mctb_widget/ghl_mctb_deploy.py

Usage:
    python E:\\genesis-system\\GHL\\modules\\mctb.py start \\
        --contact-id "abc123" \\
        --location-id "loc456" \\
        --first-name "Craig" \\
        --business-type "Plumber" \\
        --booking-link "https://book.precisionplumbing.com.au"

    python E:\\genesis-system\\GHL\\modules\\mctb.py process
        # Runs pending touches for all active sequences

    python E:\\genesis-system\\GHL\\modules\\mctb.py stats --location-id "loc456"

Author: Genesis Systems Builder
Created: 2026-02-23
"""

from __future__ import annotations

import argparse
import json
import logging
import os
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, Optional

GENESIS_ROOT = Path(r"E:\genesis-system")
sys.path.insert(0, str(GENESIS_ROOT / "data" / "genesis-memory"))
sys.path.insert(0, str(GENESIS_ROOT))

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s — %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("MCTB")

GHL_API_BASE = "https://services.leadconnectorhq.com"
GHL_API_VERSION = "2021-07-28"

# ── Touch sequence config (delays in hours from lead creation) ─────────────────
TOUCH_SEQUENCE = [
    {"touch": 1, "channel": "sms",   "delay_hours": 0,   "label": "Immediate SMS"},
    {"touch": 2, "channel": "email", "delay_hours": 4,   "label": "Day 1 Email"},
    {"touch": 3, "channel": "sms",   "delay_hours": 24,  "label": "Day 2 SMS"},
    {"touch": 4, "channel": "task",  "delay_hours": 48,  "label": "Day 3 Call Task"},
    {"touch": 5, "channel": "sms",   "delay_hours": 120, "label": "Day 5 Final SMS"},
]

# ── Templates (personalised by business_type) ─────────────────────────────────
SMS_TEMPLATES = {
    1: {
        "default": (
            "Hi {first_name}, it's {business_name}. We just missed your call — "
            "we'd love to help. Book a time here: {booking_link}"
        ),
        "Plumber": (
            "Hi {first_name}, this is {business_name}. We saw your enquiry and want "
            "to help sort it out. Book a time or call us back: {booking_link}"
        ),
        "Electrician": (
            "Hi {first_name}, {business_name} here. Thanks for reaching out — "
            "we'd love to help with your electrical job. Book in: {booking_link}"
        ),
        "Builder": (
            "Hi {first_name}, thanks for your enquiry to {business_name}. "
            "We'd love to discuss your project. Book a consult: {booking_link}"
        ),
    },
    3: {
        "default": (
            "Hi {first_name}, just following up from {business_name}. "
            "We still have availability this week — book here: {booking_link}"
        ),
        "Plumber": (
            "Hi {first_name}, {business_name} here again. Still happy to help "
            "with your plumbing job. Grab a time here: {booking_link}"
        ),
        "Electrician": (
            "Hi {first_name}, {business_name} following up. We have spots available "
            "this week for your job. Book now: {booking_link}"
        ),
        "Builder": (
            "Hi {first_name}, following up from {business_name}. "
            "Ready to chat about your project whenever you are: {booking_link}"
        ),
    },
    5: {
        "default": (
            "Hi {first_name}, last message from {business_name}. If you still need "
            "help, we are here: {booking_link}. Happy to help anytime."
        ),
        "Plumber": (
            "Hi {first_name}, last check-in from {business_name}. If the plumbing "
            "job is still on, we would love to help: {booking_link}"
        ),
        "Electrician": (
            "Hi {first_name}, final follow-up from {business_name}. "
            "If you need an electrician, we are here: {booking_link}"
        ),
        "Builder": (
            "Hi {first_name}, last message from {business_name}. "
            "If your project is still alive, we would love to quote: {booking_link}"
        ),
    },
}

EMAIL_TEMPLATES = {
    2: {
        "subject": "Your enquiry to {business_name}",
        "body": (
            "Hi {first_name},\n\n"
            "Thanks for reaching out to {business_name}.\n\n"
            "We want to make sure your job gets looked after properly. "
            "The easiest way to lock in a time is through our booking link:\n\n"
            "{booking_link}\n\n"
            "Alternatively, reply to this email or call us directly and we will sort it out.\n\n"
            "Looking forward to helping you out.\n\n"
            "{business_name}"
        ),
    },
}

TASK_TEMPLATES = {
    4: {
        "title": "Call {first_name} — MCTB Day 3 Follow-Up",
        "description": (
            "This is an automated follow-up task from the MCTB sequence.\n"
            "Lead: {first_name}\n"
            "Business type: {business_type}\n"
            "Booking link sent: {booking_link}\n\n"
            "Action: Call {first_name} to personally follow up on their enquiry. "
            "If no answer, leave a brief voicemail. Mark this task complete after the call."
        ),
    },
}


# ── PostgreSQL DDL ─────────────────────────────────────────────────────────────
CREATE_MCTB_TABLE = """
CREATE TABLE IF NOT EXISTS mctb_sequences (
    id SERIAL PRIMARY KEY,
    sequence_id TEXT UNIQUE NOT NULL,
    contact_id TEXT NOT NULL,
    location_id TEXT NOT NULL,
    first_name TEXT,
    business_name TEXT,
    business_type TEXT DEFAULT 'default',
    booking_link TEXT,
    current_touch INT DEFAULT 0,
    max_touch INT DEFAULT 5,
    status TEXT DEFAULT 'active',
    lead_created_at TIMESTAMPTZ NOT NULL,
    next_touch_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    stopped_reason TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_mctb_contact ON mctb_sequences(contact_id);
CREATE INDEX IF NOT EXISTS idx_mctb_location ON mctb_sequences(location_id);
CREATE INDEX IF NOT EXISTS idx_mctb_status ON mctb_sequences(status);
CREATE INDEX IF NOT EXISTS idx_mctb_next_touch ON mctb_sequences(next_touch_at);

CREATE TABLE IF NOT EXISTS mctb_touch_log (
    id SERIAL PRIMARY KEY,
    sequence_id TEXT NOT NULL,
    touch_number INT NOT NULL,
    channel TEXT NOT NULL,
    sent_at TIMESTAMPTZ DEFAULT NOW(),
    success BOOL DEFAULT TRUE,
    error_message TEXT,
    UNIQUE (sequence_id, touch_number)
);
"""


# ── Database ───────────────────────────────────────────────────────────────────
def get_pg_connection():
    try:
        from elestio_config import PostgresConfig
        import psycopg2
        return psycopg2.connect(**PostgresConfig.get_connection_params())
    except ImportError:
        import psycopg2
        return psycopg2.connect(
            host=os.environ.get("PG_HOST", "localhost"),
            port=int(os.environ.get("PG_PORT", 5432)),
            dbname=os.environ.get("PG_DBNAME", "genesis"),
            user=os.environ.get("PG_USER", "genesis"),
            password=os.environ.get("PG_PASSWORD", ""),
        )


def ensure_tables(conn) -> None:
    cur = conn.cursor()
    cur.execute(CREATE_MCTB_TABLE)
    conn.commit()
    cur.close()


def create_sequence(conn, sequence_id: str, contact_id: str, location_id: str,
                    data: Dict[str, Any], lead_created_at: datetime) -> None:
    # Calculate next touch time (touch 1 = immediate)
    next_touch_at = lead_created_at + timedelta(hours=TOUCH_SEQUENCE[0]["delay_hours"])
    cur = conn.cursor()
    cur.execute(
        """
        INSERT INTO mctb_sequences
            (sequence_id, contact_id, location_id, first_name, business_name,
             business_type, booking_link, current_touch, lead_created_at, next_touch_at)
        VALUES (%s, %s, %s, %s, %s, %s, %s, 0, %s, %s)
        ON CONFLICT (sequence_id) DO NOTHING
        """,
        (
            sequence_id, contact_id, location_id,
            data.get("first_name", ""),
            data.get("business_name", ""),
            data.get("business_type", "default"),
            data.get("booking_link", ""),
            lead_created_at,
            next_touch_at,
        )
    )
    conn.commit()
    cur.close()


def get_due_sequences(conn) -> List[Dict[str, Any]]:
    """Return all active sequences where next_touch_at is now or past."""
    cur = conn.cursor()
    cur.execute(
        """
        SELECT sequence_id, contact_id, location_id, first_name,
               business_name, business_type, booking_link,
               current_touch, lead_created_at
        FROM mctb_sequences
        WHERE status = 'active'
          AND next_touch_at <= NOW()
        ORDER BY next_touch_at ASC
        LIMIT 50
        """,
    )
    rows = cur.fetchall()
    cur.close()
    return [
        {
            "sequence_id": r[0], "contact_id": r[1], "location_id": r[2],
            "first_name": r[3], "business_name": r[4], "business_type": r[5],
            "booking_link": r[6], "current_touch": r[7], "lead_created_at": r[8],
        }
        for r in rows
    ]


def advance_sequence(conn, sequence_id: str, next_touch_num: int,
                      lead_created_at: datetime) -> None:
    """Set current_touch to next and calculate next_touch_at."""
    if next_touch_num > len(TOUCH_SEQUENCE):
        # Sequence complete
        cur = conn.cursor()
        cur.execute(
            "UPDATE mctb_sequences SET status='completed', current_touch=%s, completed_at=NOW() WHERE sequence_id=%s",
            (next_touch_num - 1, sequence_id)
        )
        conn.commit()
        cur.close()
        return

    next_touch_config = TOUCH_SEQUENCE[next_touch_num - 1]
    next_touch_at = lead_created_at + timedelta(hours=next_touch_config["delay_hours"])
    cur = conn.cursor()
    cur.execute(
        "UPDATE mctb_sequences SET current_touch=%s, next_touch_at=%s WHERE sequence_id=%s",
        (next_touch_num - 1, next_touch_at, sequence_id)
    )
    conn.commit()
    cur.close()


def log_touch(conn, sequence_id: str, touch_number: int, channel: str,
              success: bool, error: str = "") -> None:
    cur = conn.cursor()
    cur.execute(
        """
        INSERT INTO mctb_touch_log (sequence_id, touch_number, channel, success, error_message)
        VALUES (%s, %s, %s, %s, %s)
        ON CONFLICT (sequence_id, touch_number) DO NOTHING
        """,
        (sequence_id, touch_number, channel, success, error or None)
    )
    conn.commit()
    cur.close()


def stop_sequence(conn, sequence_id: str, reason: str = "lead_converted") -> None:
    """Stop a sequence — call when lead converts or opts out."""
    cur = conn.cursor()
    cur.execute(
        "UPDATE mctb_sequences SET status='stopped', stopped_reason=%s WHERE sequence_id=%s",
        (reason, sequence_id)
    )
    conn.commit()
    cur.close()


def get_stats(conn, location_id: str) -> Dict[str, Any]:
    cur = conn.cursor()
    cur.execute(
        """
        SELECT status, COUNT(*) FROM mctb_sequences
        WHERE location_id = %s GROUP BY status
        """,
        (location_id,)
    )
    rows = cur.fetchall()
    cur.close()
    by_status = {r[0]: r[1] for r in rows}
    total = sum(by_status.values())
    return {
        "location_id": location_id,
        "total_sequences": total,
        "active": by_status.get("active", 0),
        "completed": by_status.get("completed", 0),
        "stopped": by_status.get("stopped", 0),
    }


# ── GHL API helpers ───────────────────────────────────────────────────────────
def ghl_send_sms(contact_id: str, location_id: str, message: str,
                  api_key: str) -> bool:
    try:
        import requests as req
        resp = req.post(
            f"{GHL_API_BASE}/conversations/messages",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "Version": GHL_API_VERSION,
            },
            json={"type": "SMS", "contactId": contact_id, "message": message},
            timeout=15,
        )
        if resp.status_code in (200, 201):
            logger.info(f"SMS sent — contact {contact_id}")
            return True
        logger.error(f"SMS failed: {resp.status_code}")
        return False
    except Exception as e:
        logger.error(f"SMS exception: {e}")
        return False


def ghl_send_email(contact_id: str, location_id: str, subject: str,
                    body: str, api_key: str) -> bool:
    try:
        import requests as req
        resp = req.post(
            f"{GHL_API_BASE}/conversations/messages",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "Version": GHL_API_VERSION,
            },
            json={
                "type": "Email",
                "contactId": contact_id,
                "subject": subject,
                "html": body.replace("\n", "<br>"),
                "emailTo": contact_id,
            },
            timeout=15,
        )
        if resp.status_code in (200, 201):
            logger.info(f"Email sent — contact {contact_id}")
            return True
        logger.error(f"Email failed: {resp.status_code}")
        return False
    except Exception as e:
        logger.error(f"Email exception: {e}")
        return False


def ghl_create_task(contact_id: str, location_id: str, title: str,
                     description: str, api_key: str,
                     due_hours: int = 24) -> bool:
    """Create a manual call task in GHL for the assigned team member."""
    try:
        import requests as req
        due_date = (datetime.utcnow() + timedelta(hours=due_hours)).isoformat() + "Z"
        resp = req.post(
            f"{GHL_API_BASE}/contacts/{contact_id}/tasks",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "Version": GHL_API_VERSION,
            },
            json={
                "title": title,
                "body": description,
                "dueDate": due_date,
                "completed": False,
            },
            timeout=15,
        )
        if resp.status_code in (200, 201):
            logger.info(f"Task created — contact {contact_id}")
            return True
        logger.error(f"Task creation failed: {resp.status_code}")
        return False
    except Exception as e:
        logger.error(f"Task exception: {e}")
        return False


# ── Touch dispatcher ──────────────────────────────────────────────────────────
def dispatch_touch(seq: Dict[str, Any], touch_num: int,
                   api_key: str, dry_run: bool = False) -> bool:
    """Dispatch a single touch for a sequence."""
    touch_config = TOUCH_SEQUENCE[touch_num - 1]
    channel = touch_config["channel"]
    business_type = seq.get("business_type", "default")

    ctx = {
        "first_name": seq.get("first_name") or "there",
        "business_name": seq.get("business_name", ""),
        "business_type": business_type,
        "booking_link": seq.get("booking_link", ""),
    }

    if dry_run:
        logger.info(f"[DRY RUN] Touch {touch_num} ({channel}) for {seq['sequence_id']}")
        return True

    if channel == "sms":
        template_group = SMS_TEMPLATES.get(touch_num, {})
        template = template_group.get(business_type, template_group.get("default", ""))
        if not template:
            logger.warning(f"No SMS template for touch {touch_num}")
            return False
        message = template.format(**ctx)
        return ghl_send_sms(seq["contact_id"], seq["location_id"], message, api_key)

    elif channel == "email":
        template = EMAIL_TEMPLATES.get(touch_num, {})
        subject = template.get("subject", "").format(**ctx)
        body = template.get("body", "").format(**ctx)
        return ghl_send_email(seq["contact_id"], seq["location_id"], subject, body, api_key)

    elif channel == "task":
        template = TASK_TEMPLATES.get(touch_num, {})
        title = template.get("title", "Follow-up call").format(**ctx)
        description = template.get("description", "").format(**ctx)
        return ghl_create_task(seq["contact_id"], seq["location_id"],
                                title, description, api_key)

    logger.warning(f"Unknown channel: {channel}")
    return False


# ── Start sequence ─────────────────────────────────────────────────────────────
def start_sequence(
    contact_id: str,
    location_id: str,
    first_name: str,
    business_name: str,
    business_type: str,
    booking_link: str,
    api_key: str,
    dry_run: bool = False,
) -> Dict[str, Any]:
    """Start a new MCTB sequence for a lead."""
    import hashlib
    raw = f"{contact_id}_{location_id}"
    sequence_id = "mctb_" + hashlib.md5(raw.encode()).hexdigest()[:12]
    lead_created_at = datetime.utcnow()

    data = {
        "first_name": first_name,
        "business_name": business_name,
        "business_type": business_type or "default",
        "booking_link": booking_link,
    }

    conn = None
    try:
        conn = get_pg_connection()
        ensure_tables(conn)
        create_sequence(conn, sequence_id, contact_id, location_id,
                        data, lead_created_at)
    except Exception as e:
        logger.error(f"Could not create sequence in DB: {e}")
    finally:
        if conn:
            conn.close()

    # Dispatch touch 1 immediately
    seq = {
        "sequence_id": sequence_id,
        "contact_id": contact_id,
        "location_id": location_id,
        **data,
        "current_touch": 0,
        "lead_created_at": lead_created_at,
    }
    sent = dispatch_touch(seq, 1, api_key, dry_run)

    # Advance to touch 2 in DB
    if not dry_run:
        conn = None
        try:
            conn = get_pg_connection()
            log_touch(conn, sequence_id, 1, "sms", sent)
            advance_sequence(conn, sequence_id, 2, lead_created_at)
        except Exception as e:
            logger.error(f"Sequence advance failed: {e}")
        finally:
            if conn:
                conn.close()

    return {
        "sequence_id": sequence_id,
        "contact_id": contact_id,
        "touch_1_sent": sent,
        "status": "started",
        "dry_run": dry_run,
    }


# ── Process pending touches ───────────────────────────────────────────────────
def process_pending_touches(api_key: str, dry_run: bool = False) -> Dict[str, Any]:
    """
    Cron-style: process all sequences with due touches.
    Run this every 15–30 minutes via scheduler or n8n.
    """
    conn = None
    results = {"processed": 0, "dispatched": 0, "failed": 0, "completed": 0}

    try:
        conn = get_pg_connection()
        ensure_tables(conn)
        due_sequences = get_due_sequences(conn)
        logger.info(f"Found {len(due_sequences)} due sequences")

        for seq in due_sequences:
            next_touch = seq["current_touch"] + 1
            if next_touch > len(TOUCH_SEQUENCE):
                stop_sequence(conn, seq["sequence_id"], reason="completed")
                results["completed"] += 1
                continue

            results["processed"] += 1
            touch_config = TOUCH_SEQUENCE[next_touch - 1]

            sent = dispatch_touch(seq, next_touch, api_key, dry_run)

            if not dry_run:
                log_touch(conn, seq["sequence_id"], next_touch,
                          touch_config["channel"], sent)
                lead_created_at = seq["lead_created_at"]
                if hasattr(lead_created_at, "replace"):
                    lead_created_at = lead_created_at.replace(tzinfo=None)
                advance_sequence(conn, seq["sequence_id"],
                                 next_touch + 1, lead_created_at)

            if sent:
                results["dispatched"] += 1
            else:
                results["failed"] += 1

            time.sleep(0.3)

    except Exception as e:
        logger.error(f"Process touches failed: {e}")
    finally:
        if conn:
            conn.close()

    return results


# ── CLI ───────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="GHL MCTB Multi-Channel Touch Base Module")
    sub = parser.add_subparsers(dest="command")

    start_p = sub.add_parser("start", help="Start a new MCTB sequence")
    start_p.add_argument("--contact-id", required=True)
    start_p.add_argument("--location-id", required=True)
    start_p.add_argument("--first-name", default="")
    start_p.add_argument("--business-name", required=True)
    start_p.add_argument("--business-type", default="default",
                         help="Plumber | Electrician | Builder | default")
    start_p.add_argument("--booking-link", required=True)
    start_p.add_argument("--dry-run", action="store_true")

    proc_p = sub.add_parser("process", help="Process all pending touches")
    proc_p.add_argument("--dry-run", action="store_true")

    stop_p = sub.add_parser("stop", help="Stop a sequence")
    stop_p.add_argument("--sequence-id", required=True)
    stop_p.add_argument("--reason", default="lead_converted")

    stats_p = sub.add_parser("stats", help="Show stats for a location")
    stats_p.add_argument("--location-id", required=True)

    args = parser.parse_args()
    api_key = os.environ.get("GHL_API_KEY", "")

    if args.command == "start":
        result = start_sequence(
            contact_id=args.contact_id,
            location_id=args.location_id,
            first_name=args.first_name,
            business_name=args.business_name,
            business_type=args.business_type,
            booking_link=args.booking_link,
            api_key=api_key,
            dry_run=args.dry_run,
        )
        print(json.dumps(result, indent=2))

    elif args.command == "process":
        result = process_pending_touches(api_key=api_key, dry_run=args.dry_run)
        print(json.dumps(result, indent=2))

    elif args.command == "stop":
        conn = get_pg_connection()
        ensure_tables(conn)
        stop_sequence(conn, args.sequence_id, args.reason)
        conn.close()
        print(json.dumps({"stopped": args.sequence_id, "reason": args.reason}))

    elif args.command == "stats":
        conn = get_pg_connection()
        ensure_tables(conn)
        stats = get_stats(conn, args.location_id)
        conn.close()
        print(json.dumps(stats, indent=2))

    else:
        parser.print_help()
