#!/usr/bin/env python3
"""
CE Daily Outreach Contact Picker
- Picks 5 ICP-fit contacts from Lukas's LinkedIn connections
- Scores by role relevance, avoids already-sent
- Posts formatted table to Discord #outreach channel
- Run daily at 8:00 AM Vienna time via cron

Usage: python3 daily-contacts.py [--dry-run] [--count 5]
"""

import csv
import io
import json
import os
import sys
import random
import argparse
from datetime import datetime, date
from pathlib import Path
import urllib.request
import urllib.error

# ── Paths ──────────────────────────────────────────────────────────────────────
WORKSPACE = Path("/root/.openclaw/workspace")
CONNECTIONS_CSV = WORKSPACE / "work/internal-ce/operations/linkedin/Linkedin part1/Connections.csv"
SENT_JSON = WORKSPACE / "work/internal-ce/operations/linkedin/sent-contacts.json"
LOG_FILE = WORKSPACE / "work/internal-ce/operations/linkedin/outreach-log.jsonl"

# ── Discord ────────────────────────────────────────────────────────────────────
DISCORD_TOKEN = os.environ.get("DISCORD_BOT_TOKEN", "")
OUTREACH_CHANNEL_ID = "1483736700969816165"

# ── ICP Scoring ────────────────────────────────────────────────────────────────
TIER_1 = [
    "CMO", "Chief Marketing Officer", "Chief Marketing",
    "VP Marketing", "VP of Marketing", "SVP Marketing",
    "Chief Brand", "Head of Brand", "Brand Director", "VP Brand",
    "Chief Creative", "Chief Creative Officer", "CCO",
    "Chief Strategy", "Chief Commercial",
    "VP of Brand", "Director of Brand",
]
TIER_2 = [
    "Creative Director", "Head of Marketing", "VP Growth",
    "Head of Growth", "Chief Growth", "Growth Director",
    "Marketing Director", "Head of Content", "Content Director",
    "Communications Director", "Head of Communications",
    "Strategy Director", "Brand Lead", "Creative Lead",
]
TIER_3 = [
    "Founder", "Co-Founder", "CEO", "Chief Executive",
    "Managing Director", "General Manager",
    "Product Marketing", "Brand Manager", "Senior Brand",
]


def score_contact(position: str, company: str) -> int:
    pos_up = position.upper()
    for t in TIER_1:
        if t.upper() in pos_up:
            return 30
    for t in TIER_2:
        if t.upper() in pos_up:
            return 20
    for t in TIER_3:
        if t.upper() in pos_up:
            return 10
    return 0


def pitch_angle(position: str, company: str) -> str:
    pos = position.lower()
    if any(t.lower() in pos for t in ["cmo", "chief marketing", "vp marketing", "svp marketing"]):
        return "On-demand senior creative output at your team's speed — no retainer, no lag"
    if any(t.lower() in pos for t in ["chief brand", "head of brand", "brand director", "vp brand"]):
        return "Brand system execution across markets — CE maintains consistency at scale"
    if any(t.lower() in pos for t in ["chief creative", "cco", "creative director"]):
        return "AI-augmented creative — CE amplifies your team, handles the volume"
    if any(t.lower() in pos for t in ["founder", "ceo", "co-founder"]):
        return "Agency-quality strategy + execution without the overhead or timelines"
    if any(t.lower() in pos for t in ["growth", "gtm", "commercial"]):
        return "Brand-driven growth — CE connects sharp positioning to campaign execution"
    if any(t.lower() in pos for t in ["strategy", "communications"]):
        return "Strategic creative partner — CE turns briefs into polished assets fast"
    return "Senior creative strategy on demand — CE as your always-on brand partner"


def action_path(connected_on: str, position: str) -> str:
    try:
        connected = datetime.strptime(connected_on.strip(), "%d %b %Y")
        days = (datetime.now() - connected).days
    except Exception:
        days = 500

    pos = position.lower()
    is_senior = any(t.lower() in pos for t in ["cmo", "ceo", "founder", "co-founder", "vp", "chief", "director", "head of"])

    if days < 60:
        return "🟢 LinkedIn DM — fresh connection, short personal note"
    elif days < 180:
        return "🟡 Version D email — warm, reference shared context or mutual work"
    else:
        if is_senior:
            return "📧 Version D email — re-engage with a CE insight hook"
        else:
            return "📧 Version D email — reference connection, offer value first"


def load_sent() -> set:
    if not SENT_JSON.exists():
        return set()
    with open(SENT_JSON) as f:
        data = json.load(f)
    return set(data.get("sent_urls", []))


def save_sent(sent_set: set):
    SENT_JSON.parent.mkdir(parents=True, exist_ok=True)
    with open(SENT_JSON, "w") as f:
        json.dump({
            "sent_urls": sorted(list(sent_set)),
            "total_sent": len(sent_set),
            "last_run": datetime.utcnow().isoformat() + "Z",
        }, f, indent=2)


def load_contacts() -> list:
    with open(CONNECTIONS_CSV) as f:
        content = f.read()
    lines = content.split("\n")
    csv_content = "\n".join(lines[3:])  # skip LinkedIn header notes
    reader = csv.DictReader(io.StringIO(csv_content))
    return list(reader)


def pick_contacts(n: int = 5) -> list:
    contacts = load_contacts()
    sent = load_sent()

    scored = []
    for r in contacts:
        url = r.get("URL", "").strip()
        if not url or url in sent:
            continue
        pos = r.get("Position", "").strip()
        company = r.get("Company", "").strip()
        s = score_contact(pos, company)
        if s > 0:
            scored.append((s, r))

    # Sort by score desc, shuffle within same tier for variety
    scored.sort(key=lambda x: -x[0])

    # Take top pool (3x) and sample from it for daily variety
    pool = scored[:n * 3]
    random.shuffle(pool)
    picks = [r for _, r in pool[:n]]

    return picks


def format_discord_message(picks: list) -> str:
    today = date.today().strftime("%A, %d %b %Y")
    lines = [
        f"## 📋 Daily Outreach — {today}",
        f"**5 contacts for today, Lukas. Prioritised by CE fit.**",
        "",
    ]

    for i, r in enumerate(picks, 1):
        first = r.get("First Name", "").strip()
        last = r.get("Last Name", "").strip()
        name = f"{first} {last}".strip()
        company = r.get("Company", "").strip()
        position = r.get("Position", "").strip()
        url = r.get("URL", "").strip()
        connected = r.get("Connected On", "").strip()

        pitch = pitch_angle(position, company)
        action = action_path(connected, position)

        lines.append(f"**{i}. {name}** — {position} @ {company}")
        lines.append(f"🔗 {url}")
        lines.append(f"💡 *Pitch angle:* {pitch}")
        lines.append(f"✅ *Action:* {action}")
        lines.append("")

    lines += [
        "---",
        "**Template:** `work/internal-ce/operations/products/outreach-and-newsletter.md` (Version D = Lukas voice)",
        "**Track replies** → update in #outreach thread",
    ]

    return "\n".join(lines)


def post_to_discord(message: str, dry_run: bool = False) -> bool:
    if dry_run:
        print("── DRY RUN ──────────────────────────────────")
        print(message)
        print("─────────────────────────────────────────────")
        return True

    if not DISCORD_TOKEN:
        print("ERROR: DISCORD_BOT_TOKEN not set", file=sys.stderr)
        return False

    payload = json.dumps({"content": message}).encode("utf-8")
    req = urllib.request.Request(
        f"https://discord.com/api/v10/channels/{OUTREACH_CHANNEL_ID}/messages",
        data=payload,
        headers={
            "Authorization": f"Bot {DISCORD_TOKEN}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.status in (200, 201)
    except urllib.error.HTTPError as e:
        print(f"Discord error {e.code}: {e.read().decode()}", file=sys.stderr)
        return False


def log_run(picks: list):
    LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    entry = {
        "date": date.today().isoformat(),
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "contacts": [
            {
                "name": f"{r.get('First Name','')} {r.get('Last Name','')}".strip(),
                "url": r.get("URL", "").strip(),
                "position": r.get("Position", "").strip(),
                "company": r.get("Company", "").strip(),
            }
            for r in picks
        ],
    }
    with open(LOG_FILE, "a") as f:
        f.write(json.dumps(entry) + "\n")


def main():
    parser = argparse.ArgumentParser(description="CE Daily Outreach Contact Picker")
    parser.add_argument("--dry-run", action="store_true", help="Print output, don't post or update state")
    parser.add_argument("--count", type=int, default=5, help="Number of contacts to pick (default: 5)")
    args = parser.parse_args()

    picks = pick_contacts(args.count)
    if not picks:
        print("No new ICP contacts left to send!", file=sys.stderr)
        sys.exit(1)

    message = format_discord_message(picks)
    success = post_to_discord(message, dry_run=args.dry_run)

    if success and not args.dry_run:
        # Mark as sent
        sent = load_sent()
        for r in picks:
            url = r.get("URL", "").strip()
            if url:
                sent.add(url)
        save_sent(sent)
        log_run(picks)
        print(f"✅ Posted {len(picks)} contacts to #outreach")


if __name__ == "__main__":
    main()
