"""Tool: contacts — Manage the global contact base.

Search, browse, tag, note, and view cross-campaign history for all contacts.
One master record per person across all campaigns.
"""

from __future__ import annotations

import csv
import io
import json
import logging
import time
from datetime import datetime, timezone

from ..db.global_contact_queries import (
    add_global_contact_note,
    add_global_contact_tag,
    get_cross_campaign_history,
    get_global_contact,
    get_global_contact_stats,
    remove_global_contact_tag,
    search_global_contacts,
    update_global_contact_lifecycle,
    upsert_global_contact,
)
from ..db.queries import get_setting
from ..linkedin import get_account_id, get_linkedin_client
from ..formatter import source_badge, stars, table

logger = logging.getLogger(__name__)


def _ts_to_date(ts: int | None) -> str:
    if not ts:
        return "—"
    dt = datetime.fromtimestamp(ts, tz=timezone.utc)
    return dt.strftime("%Y-%m-%d")


def _ts_to_relative(ts: int | None) -> str:
    if not ts:
        return "—"
    now = int(time.time())
    diff = now - ts
    days = abs(diff) // 86400
    if diff < 0:
        return f"in {days}d" if days > 0 else "just now"
    if days == 0:
        return "today"
    elif days == 1:
        return "yesterday"
    elif days < 30:
        return f"{days}d ago"
    elif days < 365:
        return f"{days // 30}mo ago"
    else:
        return f"{days // 365}y ago"


def _lifecycle_icon(stage: str) -> str:
    return {
        "prospect": "○",
        "contacted": "◔",
        "connected": "◑",
        "engaged": "◕",
        "customer": "●",
        "lost": "✗",
        "churned": "↻",
        "do_not_contact": "⊘",
    }.get(stage, "?")


async def run_contacts(
    action: str = "list",
    query: str = "",
    contact_id: str = "",
    lifecycle_stage: str = "",
    tag: str = "",
    note: str = "",
    min_fit_score: float = 0.0,
    limit: int = 25,
    format: str = "table",
) -> str:
    """Route contacts actions."""
    setup_done = get_setting("setup_complete", False)
    if not setup_done:
        return "Setup required. Run setup_profile first."

    action = action.strip().lower()

    if action == "list":
        return _handle_list(lifecycle_stage, tag, min_fit_score, limit)
    elif action == "search":
        return _handle_search(query, lifecycle_stage, tag, min_fit_score, limit)
    elif action == "view":
        return _handle_view(contact_id)
    elif action == "tag":
        return _handle_tag(contact_id, tag)
    elif action == "note":
        return _handle_note(contact_id, note)
    elif action == "stage":
        return _handle_stage(contact_id, lifecycle_stage)
    elif action == "stats":
        return _handle_stats()
    elif action == "export":
        return _handle_export(lifecycle_stage, tag, format)
    elif action == "linkedin_search":
        return await _handle_linkedin_search(query, limit)
    else:
        return (
            f"Unknown action: {action!r}\n\n"
            "Available actions: list, search, view, tag, note, stage, stats, export, linkedin_search"
        )


# ──────────────────────────────────────────────
# Action handlers
# ──────────────────────────────────────────────

def _handle_list(
    lifecycle_stage: str, tag: str, min_fit_score: float, limit: int
) -> str:
    contacts = search_global_contacts(
        lifecycle_stage=lifecycle_stage,
        tag=tag,
        min_fit_score=min_fit_score,
        limit=limit,
        order_by="updated_at DESC",
    )
    if not contacts:
        filters = []
        if lifecycle_stage:
            filters.append(f"stage={lifecycle_stage}")
        if tag:
            filters.append(f"tag={tag}")
        if min_fit_score > 0:
            filters.append(f"score>={min_fit_score}")
        filter_str = f" (filters: {', '.join(filters)})" if filters else ""
        return f"No contacts found{filter_str}.\n\nContacts are added automatically when you create campaigns."

    return _format_contact_list(contacts, limit)


def _handle_search(
    query: str, lifecycle_stage: str, tag: str, min_fit_score: float, limit: int
) -> str:
    if not query:
        return "Search query is required. Usage: contacts(action='search', query='CEO fintech')"

    contacts = search_global_contacts(
        query=query,
        lifecycle_stage=lifecycle_stage,
        tag=tag,
        min_fit_score=min_fit_score,
        limit=limit,
    )
    if not contacts:
        return f"No contacts found matching '{query}'."

    return _format_contact_list(contacts, limit, title=f"Search: \"{query}\"")


def _handle_view(contact_id: str) -> str:
    if not contact_id:
        return "contact_id is required. Usage: contacts(action='view', contact_id='...')"

    history = get_cross_campaign_history(contact_id)
    if not history:
        return f"Contact {contact_id!r} not found."

    gc = history["global_contact"]
    campaigns = history["campaigns"]
    signals = history["signals"]

    lines = [
        f"Contact: {gc['name']}",
        "=" * 60,
        "",
        f"  Title:     {gc.get('title') or '—'}",
        f"  Company:   {gc.get('company') or '—'}",
        f"  LinkedIn:  {('[Profile](' + gc['linkedin_url'] + ')') if gc.get('linkedin_url') else gc.get('linkedin_id') or '—'}",
        f"  Email:     {gc.get('email') or '—'}",
        f"  Location:  {gc.get('location') or '—'}",
        "",
        f"  Lifecycle: {_lifecycle_icon(gc.get('lifecycle_stage', 'prospect'))} {gc.get('lifecycle_stage', 'prospect')}",
        f"  Fit Score: {stars(gc.get('fit_score') or 0)} ({gc.get('fit_score', 0):.2f})",
        f"  Source:    {source_badge(gc.get('source', 'search'), gc.get('source_detail', ''))}",
        f"  First seen:     {_ts_to_date(gc.get('created_at'))}",
        f"  Last activity:  {_ts_to_relative(gc.get('last_interaction_at'))}",
        f"  Campaigns:      {gc.get('total_campaigns', 0)}",
    ]

    # Tags
    tags = json.loads(gc.get("tags_json") or "[]")
    if tags:
        lines.append(f"  Tags:      {', '.join(tags)}")

    # Notes
    notes = json.loads(gc.get("notes_json") or "[]")
    if notes:
        lines.append("")
        lines.append("  Notes:")
        for n in notes[-5:]:  # Show last 5
            lines.append(f"    [{_ts_to_date(n.get('created_at'))}] {n.get('text', '')}")

    # Enriched profile data
    _append_enriched_profile(lines, gc)

    # Campaign history
    if campaigns:
        lines.append("")
        lines.append(f"Campaign History ({len(campaigns)} campaigns)")
        lines.append("-" * 60)
        for camp in campaigns:
            outreach = camp.get("outreach") or {}
            status = outreach.get("status", "—")
            msgs = camp.get("messages", [])
            engs = camp.get("engagements", [])
            lines.append(
                f"  {camp['campaign_name']} ({camp.get('campaign_status', '?')})"
            )
            lines.append(
                f"    Status: {status}  |  "
                f"Fit: {camp.get('fit_score', 0):.2f}  |  "
                f"Messages: {len(msgs)}  |  Engagements: {len(engs)}"
            )

            # Show messages
            for msg in msgs[-3:]:  # Last 3 messages
                role = "You" if msg.get("role") == "sdr" else "Them"
                text = (msg.get("text") or "")[:80]
                lines.append(f"      [{role}] {text}")

            # Show engagements
            for eng in engs[-2:]:  # Last 2 engagements
                atype = eng.get("action_type", "?")
                text = (eng.get("text") or eng.get("reaction_type") or "")[:60]
                lines.append(f"      [{atype}] {text}")

    # Signals
    if signals:
        lines.append("")
        lines.append(f"Buying Signals ({len(signals)})")
        lines.append("-" * 60)
        for sig in signals[:5]:
            stype = sig.get("signal_type", "?")
            content = (sig.get("content") or "")[:60]
            when = _ts_to_relative(sig.get("detected_at"))
            lines.append(f"  [{stype}] {content} ({when})")

    lines.append("")
    lines.append(f"ID: {contact_id}")
    return "\n".join(lines)


def _handle_tag(contact_id: str, tag: str) -> str:
    if not contact_id:
        return "contact_id is required. Usage: contacts(action='tag', contact_id='...', tag='enterprise')"
    if not tag:
        return "tag is required. Prefix with '-' to remove. Examples: 'enterprise', '-enterprise'"

    gc = get_global_contact(contact_id)
    if not gc:
        return f"Contact {contact_id!r} not found."

    if tag.startswith("-"):
        remove_tag = tag[1:].strip()
        remove_global_contact_tag(contact_id, remove_tag)
        return f"Removed tag '{remove_tag}' from {gc['name']}."
    else:
        add_global_contact_tag(contact_id, tag)
        return f"Added tag '{tag.strip().lower()}' to {gc['name']}."


def _handle_note(contact_id: str, note: str) -> str:
    if not contact_id:
        return "contact_id is required. Usage: contacts(action='note', contact_id='...', note='...')"
    if not note:
        return "note text is required."

    gc = get_global_contact(contact_id)
    if not gc:
        return f"Contact {contact_id!r} not found."

    add_global_contact_note(contact_id, note)
    return f"Note added to {gc['name']}."


def _handle_stage(contact_id: str, lifecycle_stage: str) -> str:
    if not contact_id:
        return "contact_id is required. Usage: contacts(action='stage', contact_id='...', lifecycle_stage='customer')"
    if not lifecycle_stage:
        return (
            "lifecycle_stage is required. Options: "
            "prospect, contacted, connected, engaged, customer, lost, churned, do_not_contact"
        )

    gc = get_global_contact(contact_id)
    if not gc:
        return f"Contact {contact_id!r} not found."

    old_stage = gc.get("lifecycle_stage", "prospect")
    update_global_contact_lifecycle(contact_id, lifecycle_stage)

    # Re-read to check if it actually changed
    gc2 = get_global_contact(contact_id)
    new_stage = gc2.get("lifecycle_stage", old_stage) if gc2 else old_stage

    if new_stage == old_stage and lifecycle_stage != old_stage:
        return (
            f"Cannot change {gc['name']} from '{old_stage}' to '{lifecycle_stage}' "
            f"(lifecycle only promotes forward)."
        )

    return f"Updated {gc['name']}: {old_stage} -> {new_stage}"


def _handle_stats() -> str:
    stats = get_global_contact_stats()

    if stats["total"] == 0:
        return "No contacts in the global base yet.\n\nContacts are added automatically when you create campaigns."

    lines = [
        "Contact Base",
        "=" * 50,
        "",
        f"  Total contacts: {stats['total']}",
        "",
        "  By Lifecycle:",
    ]

    for stage, count in stats["by_lifecycle"].items():
        icon = _lifecycle_icon(stage)
        lines.append(f"    {icon} {stage}: {count}")

    if stats["by_source"]:
        from ..constants import SOURCE_LABELS
        lines.append("")
        lines.append("  By Source:")
        for src, count in stats["by_source"].items():
            label = SOURCE_LABELS.get(src, src.replace("_", " ").title())
            lines.append(f"    {label}: {count}")

    if stats["top_tags"]:
        lines.append("")
        lines.append("  Top Tags:")
        for tag_name, count in stats["top_tags"]:
            lines.append(f"    #{tag_name}: {count}")

    return "\n".join(lines)


def _handle_export(lifecycle_stage: str, tag: str, fmt: str) -> str:
    contacts = search_global_contacts(
        lifecycle_stage=lifecycle_stage,
        tag=tag,
        limit=1000,
        order_by="name ASC",
    )

    if not contacts:
        return "No contacts to export."

    if fmt == "json":
        # Strip large blobs for export
        export = []
        for c in contacts:
            export.append({
                "id": c["id"],
                "name": c.get("name") or "",
                "title": c.get("title") or "",
                "company": c.get("company") or "",
                "linkedin_url": c.get("linkedin_url") or "",
                "email": c.get("email") or "",
                "location": c.get("location") or "",
                "lifecycle_stage": c.get("lifecycle_stage") or "prospect",
                "fit_score": c.get("fit_score") or 0.0,
                "tags": json.loads(c.get("tags_json") or "[]"),
                "total_campaigns": c.get("total_campaigns") or 0,
                "source": c.get("source") or "",
            })
        return json.dumps(export, indent=2)

    elif fmt == "csv":
        output = io.StringIO()
        writer = csv.writer(output)
        writer.writerow([
            "Name", "Title", "Company", "LinkedIn URL", "Email",
            "Location", "Lifecycle", "Fit Score", "Tags", "Campaigns", "Source",
        ])
        for c in contacts:
            tags = json.loads(c.get("tags_json") or "[]")
            writer.writerow([
                c.get("name") or "",
                c.get("title") or "",
                c.get("company") or "",
                c.get("linkedin_url") or "",
                c.get("email") or "",
                c.get("location") or "",
                c.get("lifecycle_stage") or "prospect",
                f"{c.get('fit_score', 0):.2f}",
                "; ".join(tags),
                c.get("total_campaigns") or 0,
                c.get("source") or "",
            ])
        return output.getvalue()

    else:
        # Default: markdown table
        headers = ["Name", "Company", "Stage", "Fit", "Campaigns"]
        rows = []
        for c in contacts[:50]:  # Cap table at 50 rows
            rows.append([
                (c.get("name") or "?")[:25],
                (c.get("company") or "—")[:20],
                f"{_lifecycle_icon(c.get('lifecycle_stage', 'prospect'))} {c.get('lifecycle_stage', 'prospect')}",
                stars(c.get("fit_score") or 0),
                str(c.get("total_campaigns") or 0),
            ])

        result = table(headers, rows)
        total = len(contacts)
        if total > 50:
            result += f"\n\n... and {total - 50} more. Use format='csv' or format='json' for full export."
        return result


def _append_enriched_profile(lines: list[str], gc: dict) -> None:
    """Append enriched profile details from profile_json if available."""
    raw = gc.get("profile_json") or ""
    if not raw:
        return
    try:
        profile = json.loads(raw)
    except (json.JSONDecodeError, TypeError):
        return

    # Only show section if we have rich data (not just basic search fields)
    rich_keys = {"contact_info", "connections_count", "summary", "experience",
                 "education", "skills", "follower_count", "websites"}
    if not rich_keys & set(profile.keys()):
        return

    lines.append("")
    lines.append("Enriched Profile")
    lines.append("-" * 60)

    # Contact info
    ci = profile.get("contact_info") or {}
    emails = ci.get("emails") or []
    phones = ci.get("phones") or []
    addresses = ci.get("adresses") or ci.get("addresses") or []
    if emails:
        lines.append(f"  Email:    {', '.join(emails)}")
    if phones:
        lines.append(f"  Phone:    {', '.join(phones)}")
    if addresses:
        lines.append(f"  Address:  {', '.join(addresses)}")

    # Summary / About
    summary = profile.get("summary") or ""
    if summary:
        lines.append("")
        lines.append("  About:")
        for chunk in [summary[i:i+76] for i in range(0, len(summary), 76)][:5]:
            lines.append(f"    {chunk}")
        if len(summary) > 380:
            lines.append("    ...")

    # Network stats
    stats_parts = []
    conn = profile.get("connections_count")
    if conn:
        stats_parts.append(f"{conn} connections")
    followers = profile.get("follower_count")
    if followers:
        stats_parts.append(f"{followers} followers")
    shared = profile.get("shared_connections_count")
    if shared:
        stats_parts.append(f"{shared} shared")
    distance = profile.get("network_distance") or ""
    if distance:
        stats_parts.append(distance.replace("_", " ").lower())
    if stats_parts:
        lines.append(f"  Network:  {' | '.join(stats_parts)}")

    # Websites
    websites = profile.get("websites") or []
    if websites:
        lines.append(f"  Websites: {', '.join(websites[:3])}")

    # Experience
    experience = profile.get("experience") or []
    if experience:
        lines.append("")
        lines.append("  Experience:")
        for exp in experience[:3]:
            title = exp.get("title") or ""
            company = exp.get("company") or exp.get("company_name") or ""
            start = exp.get("start_date") or exp.get("start") or ""
            end = exp.get("end_date") or exp.get("end") or "Present"
            entry = f"    {title}"
            if company:
                entry += f" @ {company}"
            if start:
                entry += f" ({start} - {end})"
            lines.append(entry)
        if len(experience) > 3:
            lines.append(f"    ... +{len(experience) - 3} more")

    # Education
    education = profile.get("education") or []
    if education:
        lines.append("")
        lines.append("  Education:")
        for edu in education[:3]:
            school = edu.get("school") or edu.get("school_name") or ""
            degree = edu.get("degree") or edu.get("degree_name") or ""
            field = edu.get("field") or edu.get("field_of_study") or ""
            entry = f"    {school}"
            if degree or field:
                entry += f" — {', '.join(filter(None, [degree, field]))}"
            lines.append(entry)

    # Skills
    skills = profile.get("skills") or []
    if skills:
        skill_names = [s if isinstance(s, str) else s.get("name", "") for s in skills[:10]]
        lines.append(f"  Skills:   {', '.join(filter(None, skill_names))}")
        if len(skills) > 10:
            lines.append(f"            ... +{len(skills) - 10} more")

    # Certifications
    certs = profile.get("certifications") or []
    if certs:
        cert_names = [c.get("name", "") for c in certs[:3]]
        lines.append(f"  Certs:    {', '.join(filter(None, cert_names))}")

    # Languages
    languages = profile.get("languages") or []
    if languages:
        lang_strs = []
        for lang in languages[:5]:
            name = lang if isinstance(lang, str) else lang.get("name", "")
            if name:
                lang_strs.append(name)
        if lang_strs:
            lines.append(f"  Languages: {', '.join(lang_strs)}")

    # Flags
    flags = []
    if profile.get("is_premium"):
        flags.append("Premium")
    if profile.get("is_creator"):
        flags.append("Creator")
    if profile.get("is_influencer"):
        flags.append("Influencer")
    if profile.get("is_open_profile"):
        flags.append("Open Profile")
    if flags:
        lines.append(f"  Flags:    {', '.join(flags)}")


async def _handle_linkedin_search(query: str, limit: int) -> str:
    """Search LinkedIn for people by name/title/company."""
    if not query:
        return (
            "Search query is required.\n"
            "Usage: contacts(action='linkedin_search', query='John Smith Google')\n"
            "You can search by name, title, company, or any combination."
        )

    account_id = get_account_id()
    if not account_id:
        return (
            "No LinkedIn account connected.\n\n"
            "Run setup_profile first to connect your LinkedIn account."
        )

    client = get_linkedin_client()
    count = min(limit, 25)

    try:
        results, _cursor = await client.search_people(
            account_id=account_id,
            keywords=query,
            count=count,
        )

        if not results:
            return f"No LinkedIn profiles found for '{query}'."

        # Enrich each result with full LinkedIn profile
        enriched_count = 0
        for person in results:
            lid = person.get("provider_id") or person.get("public_id") or ""
            if not lid:
                continue
            try:
                profile = await client.get_profile(account_id, lid)
                if profile and isinstance(profile, dict):
                    person["_profile_json"] = json.dumps(profile)
                    if not person.get("title") and profile.get("headline"):
                        person["title"] = profile["headline"]
                    if not person.get("company"):
                        headline = profile.get("headline", "")
                        if " at " in headline:
                            person["company"] = headline.rsplit(" at ", 1)[1]
                    enriched_count += 1
            except Exception as e:
                logger.debug("Enrich failed for %s: %s", lid, e)
    except Exception as e:
        return f"LinkedIn search failed: {e}"
    finally:
        await client.close()

    # Save results to global_contacts
    saved_ids = []
    for person in results:
        linkedin_id = person.get("provider_id") or person.get("public_id") or ""
        if not linkedin_id:
            continue
        try:
            gid = upsert_global_contact(
                linkedin_id=linkedin_id,
                name=person.get("name", ""),
                title=person.get("title", ""),
                company=person.get("company", ""),
                linkedin_url=person.get("linkedin_url", ""),
                location=person.get("location", ""),
                profile_json=person.get("_profile_json", ""),
                source="linkedin_lookup",
                source_detail=f"Search: {query[:80]}",
            )
            saved_ids.append(gid)
        except Exception:
            logger.warning("Failed to save LinkedIn result to global contacts", exc_info=True)

    # Format output
    lines = [
        f"LinkedIn Search: \"{query}\"",
        "=" * 50,
        "",
    ]

    for i, person in enumerate(results, 1):
        name = person.get("name") or "?"
        headline = person.get("headline") or ""
        company = person.get("company") or ""
        title_str = person.get("title") or ""
        location = person.get("location") or ""
        url = person.get("linkedin_url") or ""

        lines.append(f"{i}. {name}")
        if headline:
            lines.append(f"   {headline}")
        elif title_str:
            detail = title_str
            if company:
                detail += f" at {company}"
            lines.append(f"   {detail}")
        if location:
            lines.append(f"   Location: {location}")
        if url:
            lines.append(f"   {url}")
        lines.append("")

    total = len(results)
    saved = len(saved_ids)
    lines.append(f"Found {total} results, enriched {enriched_count} profiles.")
    if saved > 0:
        lines.append(
            f"Saved {saved} to your contact base (source: linkedin_lookup). "
            "Use contacts(action='search') to find them, or tag/note them."
        )

    return "\n".join(lines)


# ──────────────────────────────────────────────
# Formatting helpers
# ──────────────────────────────────────────────

def _format_contact_list(
    contacts: list[dict], limit: int, title: str = "Contact Base"
) -> str:
    lines = [title, "=" * 50, ""]

    for c in contacts:
        stage = c.get("lifecycle_stage", "prospect")
        icon = _lifecycle_icon(stage)
        name = c.get("name") or "?"
        company = c.get("company") or ""
        title_str = c.get("title") or ""
        score = c.get("fit_score") or 0.0
        tags = json.loads(c.get("tags_json") or "[]")

        line1 = f"{icon} {name}"
        if company:
            line1 += f" @ {company}"
        lines.append(line1)

        details = f"   {stage}"
        if title_str:
            details += f"  |  {title_str[:40]}"
        details += f"  |  {stars(score)}"
        if tags:
            details += f"  |  #{', #'.join(tags[:3])}"
        lines.append(details)
        lines.append(f"   ID: {c['id']}")
        lines.append("")

    total = len(contacts)
    if total >= limit:
        lines.append(f"Showing {limit} contacts. Use limit= to see more, or action='search' to filter.")
    else:
        lines.append(f"Total: {total} contacts")
    return "\n".join(lines)
