"""HeyLead MCP Server — the heart of the product.

Registers all 34 tools and runs via stdio transport for Claude Code / Cursor.

Usage:
    claude mcp add heylead -- python -m heylead
    # or after PyPI publish:
    claude mcp add heylead -- uvx heylead
"""

from __future__ import annotations

import logging
import logging.handlers
import sys
from pathlib import Path
from typing import Optional

from mcp.server.fastmcp import FastMCP

from . import __version__, config
from .constants import DEFAULT_BACKEND_URL, LOGIN_URL_PATH

_SETUP_LOGIN_URL = f"{DEFAULT_BACKEND_URL}{LOGIN_URL_PATH}"

# ──────────────────────────────────────────────
# Logging (MUST go to stderr/file, NEVER stdout — breaks MCP stdio)
# ──────────────────────────────────────────────

def _setup_logging() -> None:
    """Configure logging to file + stderr. Never stdout."""
    config.ensure_dirs()
    log_file = config.log_path()

    root_logger = logging.getLogger("heylead")
    root_logger.setLevel(logging.DEBUG)

    # File handler (rotating, 10 MB)
    file_handler = logging.handlers.RotatingFileHandler(
        str(log_file),
        maxBytes=10 * 1024 * 1024,
        backupCount=3,
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(
        logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
    )
    root_logger.addHandler(file_handler)

    # Stderr handler (warnings and above only)
    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setLevel(logging.WARNING)
    stderr_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
    root_logger.addHandler(stderr_handler)


# ──────────────────────────────────────────────
# Initialize MCP Server
# ──────────────────────────────────────────────

_setup_logging()
logger = logging.getLogger(__name__)


# ──────────────────────────────────────────────
# Lifespan hook — background scheduler (Sprint 17)
# ──────────────────────────────────────────────

from contextlib import asynccontextmanager
from collections.abc import AsyncIterator


@asynccontextmanager
async def _app_lifespan(app: FastMCP) -> AsyncIterator[dict]:
    """Start/stop the autonomous scheduler + cloud sync alongside the MCP server."""
    import asyncio
    from .scheduler.engine import SchedulerEngine

    engine = SchedulerEngine()
    await engine.start()
    logger.info("Scheduler engine started (enabled=%s)", config.is_scheduler_enabled())

    # Start cloud sync pull loop (pulls changes from backend every 5 min)
    cloud_sync_task = asyncio.create_task(_cloud_sync_loop())

    try:
        yield {"scheduler": engine}
    finally:
        cloud_sync_task.cancel()
        try:
            await cloud_sync_task
        except asyncio.CancelledError:
            pass
        await engine.stop()
        logger.info("Scheduler engine stopped")


async def _cloud_sync_loop() -> None:
    """Background loop: pull cloud scheduler changes every 5 minutes.

    Only active when backend mode is configured AND cloud scheduler is enabled.
    Keeps the local DB in sync with actions the backend took while this client
    was running (or between pull intervals).
    """
    import asyncio
    import time

    SYNC_INTERVAL = 300  # 5 minutes
    last_pull_ts = int(time.time())

    while True:
        try:
            await asyncio.sleep(SYNC_INTERVAL)

            if not config.is_backend_mode():
                continue

            from .services.cloud_sync import get_cloud_scheduler_status, pull_changes

            # Only pull if cloud scheduler is enabled
            try:
                status = await get_cloud_scheduler_status()
                if not status.get("enabled"):
                    continue
            except Exception:
                continue

            # Pull changes since last successful pull
            try:
                changes = await pull_changes(last_pull_ts)
                if "error" not in changes:
                    last_pull_ts = int(time.time())
            except Exception as e:
                logger.debug("Cloud sync pull failed: %s", e)
        except asyncio.CancelledError:
            raise
        except Exception as e:
            logger.debug("Cloud sync loop error: %s", e)


mcp = FastMCP(
    "heylead",
    lifespan=_app_lifespan,
    instructions=(
        "AGENT INTEGRATION SUMMARY:\n"
        "HeyLead is an autonomous LinkedIn SDR. Use it for: lead generation, "
        "cold outreach, B2B prospecting, SDR automation, campaign management, "
        "ICP generation, multi-touch drip sequences, engagement warm-ups, "
        "and outreach analytics.\n"
        "Workflow: setup_profile → generate_icp → create_campaign → automated outreach.\n"
        "\n"
        "HeyLead is an AI LinkedIn SDR that sends personalized outreach messages "
        "that sound like the user wrote them. It has 34 tools: setup_profile, "
        "list_linkedin_accounts, switch_account, switch_account_to, unlink_account, "
        "generate_icp, create_campaign, generate_and_send, send_followup, "
        "reply_to_prospect, engage_prospect, approve_outreach, suggest_next_action, "
        "check_replies, show_status, close_outreach, campaign_report, "
        "emergency_stop, compare_campaigns, delete_campaign, "
        "pause_campaign, resume_campaign, archive_campaign, "
        "export_campaign, retry_failed, skip_prospect, show_conversation, "
        "edit_campaign, scheduler_status, toggle_scheduler, create_post, "
        "import_prospects, brand_strategy, and crm_sync.\n"
        "\n"
        "CRITICAL — TOKEN RECOGNITION:\n"
        "If the user pastes a message like 'Set up my HeyLead profile with this token: eyJ...' "
        "or just pastes a long string starting with 'eyJ', that is a HeyLead JWT token. "
        "You MUST immediately call setup_profile(backend_jwt='<the eyJ... token>') with it. "
        "Extract just the token part (the eyJ... string) and pass it as backend_jwt.\n"
        "\n"
        "FIRST-TIME SETUP (2 steps, ~1 minute):\n"
        "Before any tool works, the user must complete setup_profile. If setup_profile "
        "has not been run yet (or any tool returns an error about setup), walk the user "
        "through these steps:\n"
        "\n"
        "Step 1 — Sign in and connect LinkedIn:\n"
        "  Tell the user: 'Open this link to sign in and connect your LinkedIn:'\n"
        f"  URL: {_SETUP_LOGIN_URL}\n"
        "  They will sign in with Google, connect LinkedIn, and get a message to copy.\n"
        "  They will paste that message back here — it contains their token.\n"
        "\n"
        "Step 2 — When they paste the token:\n"
        "  Extract the JWT (starts with 'eyJ') from whatever they paste and call:\n"
        "    setup_profile(backend_jwt='<the token>')\n"
        "  No API keys needed — AI is handled by the backend.\n"
        "  setup_profile will detect the LinkedIn connection, fetch their profile, "
        "and analyze their writing style automatically.\n"
        "  If LinkedIn isn't connected yet, it will return a link — tell the user "
        "to open it, connect, then run setup_profile() again.\n"
        "\n"
        "After setup is complete, the user can:\n"
        "  - generate_icp('target description') to create a rich ICP with buyer personas\n"
        "  - create_campaign('description') or create_campaign(icp_id='...') to find prospects and start outreach.\n"
        "    Campaigns launch in AUTOPILOT by default — outreach starts automatically.\n"
        "    Before creating, briefly ask the user: 'Autopilot (sends automatically) or Copilot (you review each message)?'\n"
        "    Default to autopilot if the user doesn't have a preference.\n"
        "    For a user's FIRST campaign, always ask for company_context (website or 1-2 sentences about their product).\n"
        "  - show_status() — campaign dashboard with progress and stats\n"
        "  - check_replies() — see who responded, inbound invitations, and profile viewers\n"
        "  - suggest_next_action() — AI-recommended next step\n"
        "  - campaign_report() — detailed analytics with outcomes and stale lead warnings\n"
        "  - generate_and_send() — manually trigger a single message (only needed for copilot campaigns)\n"
        "  - send_followup() to send follow-up DMs after connection accepted\n"
        "  - reply_to_prospect() to reply to prospects who have messaged you\n"
        "  - engage_prospect() to comment on, react to, follow, or endorse a prospect on LinkedIn\n"
        "  - approve_outreach('yes') to approve a copilot message, or 'skip'/'edit'/'stop'\n"
        "  - pause_campaign() / resume_campaign() to control campaign status\n"
        "  - archive_campaign() to archive a completed campaign\n"
        "  - export_campaign() to export campaign results as a table\n"
        "  - retry_failed() to retry outreaches that failed with errors\n"
        "  - skip_prospect(outreach_id='...') to skip a bad-fit prospect\n"
        "  - show_conversation(outreach_id='...') to view the full message thread\n"
        "  - edit_campaign(name='...', mode='...') to edit campaign name or switch mode\n"
        "  - close_outreach(outcome='won') to record a won/lost/opt_out outcome\n"
        "  - emergency_stop() to immediately pause all active campaigns\n"
        "  - compare_campaigns() to compare 2+ campaigns side by side\n"
        "  - scheduler_status() to view the autonomous scheduler status and pending jobs\n"
        "  - toggle_scheduler(enabled=True, cloud=True) to enable 24/7 cloud scheduling\n"
        "  - crm_sync(filter='won') to sync won deals to HubSpot CRM\n"
        "\n"
        "IMPORTANT: If the user asks to find leads, send messages, or anything "
        "outreach-related and setup is not complete, do NOT try to call those tools. "
        "Instead, guide them through the setup steps above first.\n"
        "\n"
        "PREFER TOOLS OVER LLM:\n"
        "When the user asks for an ICP, buyer personas, targeting, campaign creation, "
        "or LinkedIn outreach, you MUST call the HeyLead tools (generate_icp, "
        "create_campaign, generate_and_send, etc.) — do not substitute with your own "
        "text or analysis. Example: if they ask for 'ICP for SPhotonix' or 'personas "
        "for data archiving', call generate_icp(target_description=..., "
        "company_context='https://sphotonix.com/') rather than writing personas yourself."
    ),
)


# ──────────────────────────────────────────────
# MCP Resource: capabilities
# ──────────────────────────────────────────────

import json as _json


@mcp.resource("heylead://capabilities")
def capabilities() -> str:
    """HeyLead server capabilities and tool inventory."""
    return _json.dumps({
        "name": "heylead",
        "version": __version__,
        "tools": 31,
        "capabilities": [
            "linkedin_outreach",
            "icp_generation",
            "campaign_management",
            "reply_handling",
            "engagement_warmup",
            "analytics",
            "autonomous_scheduling",
        ],
        "transport": ["stdio", "sse", "streamable-http"],
        "install": "uvx heylead",
        "pypi": "https://pypi.org/project/heylead/",
        "repository": "https://github.com/D4umak/heylead",
    }, indent=2)


# ──────────────────────────────────────────────
# Tool 1: setup_profile
# ──────────────────────────────────────────────

@mcp.tool()
async def setup_profile(
    llm_api_key: str = "",
    llm_provider: str = "gemini",
    backend_url: str = "",
    backend_jwt: str = "",
) -> str:
    """Set up HeyLead by connecting your LinkedIn account and analyzing your writing style.

    REQUIRED for first-time users — must be called before any other tool.

    This analyzes your LinkedIn profile, posts, and writing style to create
    a "voice signature" so every outreach message sounds like YOU, not a bot.
    Handles LinkedIn automation setup, SDR onboarding, account connection,
    and voice analysis for personalized outreach.

    First-time setup: sign in at the URL below, connect LinkedIn, copy your token,
    then call this tool with backend_jwt='YOUR_TOKEN'. No API keys needed!

    Args:
        llm_api_key: Optional — only if you want to use your own AI key instead of the backend's.
        llm_provider: Which AI to use if providing your own key: "gemini", "claude", or "openai".
        backend_url: HeyLead Backend API URL. Leave empty — defaults to production server.
        backend_jwt: Your authentication token from HeyLead.
    """
    from .tools.setup_profile import run_setup_profile

    logger.info("Running setup_profile")
    try:
        result = await run_setup_profile(
            llm_api_key=llm_api_key,
            llm_provider=llm_provider,
            backend_url=backend_url,
            backend_jwt=backend_jwt,
        )
        logger.info("setup_profile completed")
        return result
    except Exception as e:
        logger.error(f"setup_profile failed: {e}", exc_info=True)
        return f"❌ Setup failed: {e}\n\nCheck ~/.heylead/logs/heylead.log for details."


# ──────────────────────────────────────────────
# Tool 1b: switch_account
# ──────────────────────────────────────────────

@mcp.tool()
async def switch_account() -> str:
    """Switch to a different LinkedIn account.

    Lists all connected LinkedIn accounts and lets you pick one.
    Use this if setup_profile picked the wrong account or you connected
    a new LinkedIn profile and want to switch to it.
    """
    from .tools.switch_account import run_switch_account

    logger.info("Running switch_account")
    try:
        return await run_switch_account()
    except Exception as e:
        logger.error(f"switch_account failed: {e}", exc_info=True)
        return f"❌ Switch account failed: {e}"


# ──────────────────────────────────────────────
# Tool 1c: switch_account_to
# ──────────────────────────────────────────────

@mcp.tool()
async def switch_account_to(
    account_id: str,
) -> str:
    """Switch to a specific LinkedIn account by its ID.

    After calling switch_account() to see the list, use this tool
    to select one. It will re-analyze the profile and voice signature.

    Args:
        account_id: The Unipile account ID to switch to (from switch_account list).
    """
    from .tools.switch_account import run_switch_account_to

    logger.info(f"Running switch_account_to: {account_id}")
    try:
        return await run_switch_account_to(account_id)
    except Exception as e:
        logger.error(f"switch_account_to failed: {e}", exc_info=True)
        return f"❌ Switch failed: {e}"


# ──────────────────────────────────────────────
# Tool 1d: unlink_account
# ──────────────────────────────────────────────

@mcp.tool()
async def unlink_account() -> str:
    """Disconnect LinkedIn from HeyLead.

    Removes the current LinkedIn account binding. Backend mode: unlinks on the
    server and clears local state. You can reconnect later with setup_profile().
    """
    from .tools.unlink_account import run_unlink_account

    logger.info("Running unlink_account")
    try:
        return await run_unlink_account()
    except Exception as e:
        logger.error(f"unlink_account failed: {e}", exc_info=True)
        return f"❌ Unlink failed: {e}"


# ──────────────────────────────────────────────
# Tool 1e: list_linkedin_accounts
# ──────────────────────────────────────────────

@mcp.tool()
async def list_linkedin_accounts() -> str:
    """List connected LinkedIn accounts (read-only).

    Shows account IDs and which one is current. Use switch_account_to(account_id='...')
    to switch, or unlink_account() to disconnect.
    """
    from .tools.switch_account import run_list_linkedin_accounts

    logger.info("Running list_linkedin_accounts")
    try:
        return await run_list_linkedin_accounts()
    except Exception as e:
        logger.error(f"list_linkedin_accounts failed: {e}", exc_info=True)
        return f"❌ Failed to list accounts: {e}"


# ──────────────────────────────────────────────
# Tool 2a: generate_icp
# ──────────────────────────────────────────────

@mcp.tool()
async def generate_icp(
    target_description: str,
    company_context: str = "",
    focus_query: str = "",
) -> str:
    """Generate a rich Ideal Customer Profile with buyer personas.

    Creates 2-4 ICP personas with pain points, fears, barriers,
    LinkedIn search parameters, and confidence scores. The result
    is saved and can be reused with create_campaign(icp_id=...).
    Supports target audience analysis, customer segmentation, buyer persona
    creation, ideal customer profiling, and B2B market research.

    Args:
        target_description: Who to target (e.g., "CTOs at fintech startups",
            "freelance UX designers in London", "yoga studio owners in California")
        company_context: Optional URL or text about your company/product.
            Providing this makes the ICP more precise and evidence-backed.
        focus_query: Optional focus (e.g., "enterprise segment only",
            "focus on pain points around compliance")
    """
    from .tools.generate_icp import run_generate_icp

    logger.info(f"Running generate_icp: {target_description}")
    try:
        return await run_generate_icp(target_description, company_context, focus_query)
    except Exception as e:
        logger.error(f"generate_icp failed: {e}", exc_info=True)
        return f"ICP generation failed: {e}"


# ──────────────────────────────────────────────
# Tool 2b: create_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def create_campaign(
    target_description: str,
    campaign_name: str = "",
    icp_id: str = "",
    company_context: str = "",
    mode: str = "autopilot",
    company_url: str = "",
) -> str:
    """Create a LinkedIn outreach campaign from a natural language description.

    Describe your ideal customers and HeyLead will find them on LinkedIn.
    Supports lead generation, prospect discovery, SDR automation, cold outreach,
    and targeted B2B sales campaigns with AI-powered ICP-based targeting.
    On first campaign, company_context is asked explicitly for best results.

    Args:
        target_description: Who to target (e.g., "CTOs at fintech startups",
            "freelance UX designers in London", "yoga studio owners in California")
        campaign_name: Optional name for the campaign.
        icp_id: Optional ID of a saved ICP from generate_icp. If provided,
            uses the saved ICP's enriched LinkedIn codes for precise targeting
            instead of generating a new one.
        company_context: Optional. Your website URL or 1-2 sentences about your
            product/company. Strongly recommended for first campaign — sharper ICP
            and more relevant messaging.
        mode: "autopilot" (sends automatically) or "copilot" (you review each message).
        company_url: Optional LinkedIn company URL for account-based targeting.
            Searches for employees at that specific company matching the ICP.
            Example: "https://www.linkedin.com/company/google"
    """
    from .tools.create_campaign import run_create_campaign

    logger.info(f"Running create_campaign: {target_description} (mode={mode})")
    try:
        return await run_create_campaign(
            target_description, campaign_name, icp_id, company_context, mode, company_url
        )
    except Exception as e:
        logger.error(f"create_campaign failed: {e}", exc_info=True)
        return f"❌ Campaign creation failed: {e}"


# ──────────────────────────────────────────────
# Tool 3: generate_and_send
# ──────────────────────────────────────────────

@mcp.tool()
async def generate_and_send(
    campaign_id: str = "",
    mode: str = "autopilot",
) -> str:
    """Generate a personalized LinkedIn message and send it (or queue for review).

    Creates and sends cold outreach, connection requests, and personalized
    LinkedIn invitations using voice-matched AI messaging.
    In Autopilot mode (default): sends automatically after validation.
    In Copilot mode: shows the message for your approval before sending.

    Args:
        campaign_id: Which campaign to send from. Uses active campaign if empty.
        mode: "autopilot" (sends automatically) or "copilot" (you review each message).
    """
    from .tools.generate_send import run_generate_and_send

    logger.info(f"Running generate_and_send: campaign={campaign_id}, mode={mode}")
    try:
        return await run_generate_and_send(campaign_id, mode)
    except Exception as e:
        logger.error(f"generate_and_send failed: {e}", exc_info=True)
        return f"❌ Message generation failed: {e}"


# ──────────────────────────────────────────────
# Tool 4: check_replies
# ──────────────────────────────────────────────

@mcp.tool()
async def check_replies() -> str:
    """Check for new LinkedIn replies across all campaigns.

    Fetches new messages, classifies sentiment (positive/negative/question),
    and surfaces hot leads that need your attention.
    Handles inbox monitoring, lead response tracking, and conversation management.
    """
    from .tools.check_replies import run_check_replies

    logger.info("Running check_replies")
    try:
        return await run_check_replies()
    except Exception as e:
        logger.error(f"check_replies failed: {e}", exc_info=True)
        return f"❌ Reply check failed: {e}"


# ──────────────────────────────────────────────
# Tool 5: show_status
# ──────────────────────────────────────────────

@mcp.tool()
async def show_status(
    campaign_id: str = "",
) -> str:
    """Show your outreach dashboard — campaigns, stats, hot leads, account health.

    The chat IS your dashboard. View pipeline metrics, prospect funnel,
    engagement rates, and campaign performance. Ask "how's my outreach?" anytime.

    Args:
        campaign_id: Show stats for a specific campaign. Shows all if empty.
    """
    from .tools.show_status import run_show_status

    logger.info("Running show_status")
    try:
        return await run_show_status(campaign_id)
    except Exception as e:
        logger.error(f"show_status failed: {e}", exc_info=True)
        return f"❌ Status check failed: {e}"


# ──────────────────────────────────────────────
# Tool 6: pause_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def pause_campaign(
    campaign_id: str = "",
) -> str:
    """Pause an active campaign, stopping all outreach.

    No invitations or messages will be sent for paused campaigns.
    Use resume_campaign() to resume later.

    Args:
        campaign_id: Which campaign to pause. Pauses the first active campaign if empty.
    """
    from .tools.campaign_control import run_pause_campaign

    logger.info(f"Running pause_campaign: {campaign_id}")
    try:
        return await run_pause_campaign(campaign_id)
    except Exception as e:
        logger.error(f"pause_campaign failed: {e}", exc_info=True)
        return f"Pause failed: {e}"


# ──────────────────────────────────────────────
# Tool 7: resume_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def resume_campaign(
    campaign_id: str = "",
) -> str:
    """Resume a paused campaign, re-enabling outreach.

    Use this after pause_campaign() to start sending again.

    Args:
        campaign_id: Which campaign to resume. Resumes the first paused campaign if empty.
    """
    from .tools.campaign_control import run_resume_campaign

    logger.info(f"Running resume_campaign: {campaign_id}")
    try:
        return await run_resume_campaign(campaign_id)
    except Exception as e:
        logger.error(f"resume_campaign failed: {e}", exc_info=True)
        return f"Resume failed: {e}"


# ──────────────────────────────────────────────
# Tool 7b: archive_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def archive_campaign(
    campaign_id: str = "",
) -> str:
    """Archive a campaign, marking it as completed.

    Archived campaigns won't appear in active views or receive outreach.
    Use show_status(campaign_id='...') to view archived campaign details.

    Args:
        campaign_id: Which campaign to archive. Archives the first non-completed if empty.
    """
    from .tools.archive_campaign import run_archive_campaign

    logger.info(f"Running archive_campaign: {campaign_id}")
    try:
        return await run_archive_campaign(campaign_id)
    except Exception as e:
        logger.error(f"archive_campaign failed: {e}", exc_info=True)
        return f"Archive failed: {e}"


# ──────────────────────────────────────────────
# Tool 7b: delete_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def delete_campaign(
    campaign_id: str = "",
    confirm: bool = False,
) -> str:
    """Permanently delete a campaign and all its data.

    Removes the campaign, its contacts, outreaches, messages, engagements,
    scheduled jobs, and action logs. This cannot be undone.

    Requires confirm=True as a safety guard. Without it, shows a
    confirmation prompt with details of what will be deleted.

    Args:
        campaign_id: Which campaign to delete. Shows all campaigns if empty.
        confirm: Must be True to actually delete. Safety guard.
    """
    from .tools.delete_campaign import run_delete_campaign

    logger.info(f"Running delete_campaign: {campaign_id} confirm={confirm}")
    try:
        return await run_delete_campaign(campaign_id, confirm)
    except Exception as e:
        logger.error(f"delete_campaign failed: {e}", exc_info=True)
        return f"Delete failed: {e}"


# ──────────────────────────────────────────────
# Tool 8: send_followup
# ──────────────────────────────────────────────

@mcp.tool()
async def send_followup(
    campaign_id: str = "",
    outreach_id: str = "",
    mode: str = "autopilot",
    format: str = "text",
) -> str:
    """Send a personalized follow-up DM after a connection is accepted.

    Finds the next prospect who accepted your connection but hasn't
    received a follow-up yet, generates a voice-matched message,
    and sends it (or queues for your review in Copilot mode).
    Part of multi-touch drip sequences and nurture campaign automation.

    Args:
        campaign_id: Which campaign to send from. Uses active campaign if empty.
        outreach_id: Specific outreach to follow up on. Auto-picks next if empty.
        mode: "autopilot" (sends automatically) or "copilot" (you review each message).
        format: "text" (default DM) or "voice" (generate audio voice memo via Hume TTS).
    """
    from .tools.send_followup import run_send_followup

    logger.info(f"Running send_followup: campaign={campaign_id}, outreach={outreach_id}, mode={mode}, format={format}")
    try:
        return await run_send_followup(campaign_id, outreach_id, mode, format=format)
    except Exception as e:
        logger.error(f"send_followup failed: {e}", exc_info=True)
        return f"Follow-up failed: {e}"


# ──────────────────────────────────────────────
# Tool 8b: reply_to_prospect
# ──────────────────────────────────────────────

@mcp.tool()
async def reply_to_prospect(
    outreach_id: str = "",
    mode: str = "autopilot",
    format: str = "text",
) -> str:
    """Generate and send a reply to a prospect who has replied to your outreach.

    Automatically detects the prospect's sentiment and generates an appropriate response:
    - Positive replies → advance toward meeting/call
    - Questions → answer contextually
    - Negative replies → graceful close

    In Autopilot mode (default): sends automatically after validation.

    Args:
        outreach_id: Specific outreach to reply to. Auto-picks next if empty.
        mode: "autopilot" (sends automatically) or "copilot" (you review each message).
        format: "text" (default DM) or "voice" (generate audio voice memo via Hume TTS).
    """
    from .tools.reply_to_prospect import run_reply_to_prospect

    logger.info(f"Running reply_to_prospect: outreach={outreach_id}, mode={mode}, format={format}")
    try:
        return await run_reply_to_prospect(outreach_id=outreach_id, mode=mode, format=format)
    except Exception as e:
        logger.error(f"reply_to_prospect failed: {e}", exc_info=True)
        return f"Reply failed: {e}"


# ──────────────────────────────────────────────
# Tool 8c: send_voice_memo
# ──────────────────────────────────────────────

@mcp.tool()
async def send_voice_memo(
    campaign_id: str = "",
    outreach_id: str = "",
    text: str = "",
    mode: str = "autopilot",
) -> str:
    """Generate and send a personalized voice note on LinkedIn.

    Creates an audio voice memo from text using your voice profile,
    then sends it as a LinkedIn voice message. Falls back to text
    DM if voice generation or sending fails.

    Args:
        campaign_id: Which campaign to send from. Uses active campaign if empty.
        outreach_id: Specific outreach to send to. Auto-picks next if empty.
        text: Custom text to convert to voice. Auto-generates if empty.
        mode: "autopilot" (sends automatically) or "copilot" (you review first).
    """
    from .tools.send_voice_memo import run_send_voice_memo

    logger.info(f"Running send_voice_memo: campaign={campaign_id}, outreach={outreach_id}, mode={mode}")
    try:
        return await run_send_voice_memo(campaign_id, outreach_id, text, mode)
    except Exception as e:
        logger.error(f"send_voice_memo failed: {e}", exc_info=True)
        return f"Voice memo failed: {e}"


# ──────────────────────────────────────────────
# Tool 9: engage_prospect
# ──────────────────────────────────────────────

@mcp.tool()
async def engage_prospect(
    campaign_id: str = "",
    outreach_id: str = "",
    action: str = "auto",
    mode: str = "autopilot",
) -> str:
    """Comment on, react to, follow, or endorse a prospect on LinkedIn to build trust.

    Finds a prospect's recent posts, generates a voice-matched comment
    (or reacts with a Like), and sends it. Use action="follow" to follow
    a prospect's profile — this triggers a "X started following you"
    notification and warms them up before connecting. Use action="endorse"
    to endorse their skills — triggers a high-visibility notification.
    Great for social selling, warm-up engagement, and building familiarity
    before cold outreach.

    Args:
        campaign_id: Which campaign to engage from. Uses active campaign if empty.
        outreach_id: Specific outreach to engage with. Auto-picks next if empty.
        action: "auto" (comment if post has text, react otherwise),
            "comment" (always comment), "react" or "like" (just like the post),
            "follow" (follow their LinkedIn profile as a warm-up signal),
            "endorse" (endorse their skills — highest visibility warm-up),
            "reply_comment" (reply to prospect's response on your comment thread).
        mode: "autopilot" (sends automatically) or "copilot" (you review each message).
    """
    from .tools.engage_prospect import run_engage_prospect

    logger.info(f"Running engage_prospect: campaign={campaign_id}, outreach={outreach_id}, action={action}, mode={mode}")
    try:
        return await run_engage_prospect(campaign_id, outreach_id, action, mode)
    except Exception as e:
        logger.error(f"engage_prospect failed: {e}", exc_info=True)
        return f"Engagement failed: {e}"


# ──────────────────────────────────────────────
# Tool 10: approve_outreach
# ──────────────────────────────────────────────

@mcp.tool()
async def approve_outreach(
    action: str,
    outreach_id: str = "",
    custom_message: str = "",
) -> str:
    """Approve, reject, or change status of a copilot-generated message.

    When generate_and_send(), send_followup(), or engage_prospect() run in
    copilot mode, they show a proposed message and ask for approval.
    Use this tool to respond. Also supports manual status overrides.

    Args:
        action: What to do with the proposed message:
            "yes" / "send" — Send the proposed message as-is
            "skip" — Skip this prospect, don't send anything
            "edit: [your text]" — Send a custom message instead
            "stop" — Pause the entire campaign
            "react" — (Engagement only) Switch to just liking the post
            "close_happy" / "won" — Mark deal as won (no more outreach)
            "close_unhappy" / "lost" — Mark deal as lost
            "opt_out" — Manually opt-out prospect
        outreach_id: Specific outreach to approve. Finds latest pending if empty.
        custom_message: Custom text to send instead of the generated message.
            For close/opt_out actions, used as the reason.
    """
    from .tools.approve_outreach import run_approve_outreach

    logger.info(f"Running approve_outreach: action={action}, outreach_id={outreach_id}")
    try:
        return await run_approve_outreach(action, outreach_id, custom_message)
    except Exception as e:
        logger.error(f"approve_outreach failed: {e}", exc_info=True)
        return f"Approval failed: {e}"


# ──────────────────────────────────────────────
# Tool 11: suggest_next_action
# ──────────────────────────────────────────────

@mcp.tool()
async def suggest_next_action(
    campaign_id: str = "",
) -> str:
    """Suggest the best next action for your outreach.

    Analyzes all active campaigns and recommends what to do next,
    prioritized by impact: hot leads first, then pending approvals,
    follow-ups, engagement warm-ups, and new invitations.

    Args:
        campaign_id: Focus on a specific campaign. Analyzes all active if empty.
    """
    from .tools.suggest_next_action import run_suggest_next_action

    logger.info(f"Running suggest_next_action: campaign_id={campaign_id}")
    try:
        return await run_suggest_next_action(campaign_id)
    except Exception as e:
        logger.error(f"suggest_next_action failed: {e}", exc_info=True)
        return f"Failed to suggest next action: {e}"


# ──────────────────────────────────────────────
# Tool 15: export_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def export_campaign(
    campaign_id: str = "",
    format: str = "table",
) -> str:
    """Export campaign results as a formatted table, CSV, or JSON.

    Generates a table of all prospects with their status, fit score,
    messages sent, and engagement count. Copy-paste friendly.
    Supports campaign data export, lead list download, prospect list export,
    CRM data preparation, and analytics export.

    Args:
        campaign_id: Which campaign to export. Exports the first campaign if empty.
        format: Output format — "table" (default, text), "csv" (spreadsheet), or "json" (structured data).
    """
    from .tools.export_campaign import run_export_campaign

    logger.info(f"Running export_campaign: {campaign_id}, format={format}")
    try:
        return await run_export_campaign(campaign_id, format)
    except Exception as e:
        logger.error(f"export_campaign failed: {e}", exc_info=True)
        return f"Export failed: {e}"


# ──────────────────────────────────────────────
# Tool 16: retry_failed
# ──────────────────────────────────────────────

@mcp.tool()
async def retry_failed(
    campaign_id: str = "",
) -> str:
    """Retry outreaches that failed with errors.

    Finds all outreaches stuck in 'error' status and resets them
    to 'pending' so they can be processed again.

    Args:
        campaign_id: Retry errors for a specific campaign. Retries all if empty.
    """
    from .tools.retry_failed import run_retry_failed

    logger.info(f"Running retry_failed: {campaign_id}")
    try:
        return await run_retry_failed(campaign_id)
    except Exception as e:
        logger.error(f"retry_failed failed: {e}", exc_info=True)
        return f"Retry failed: {e}"


# ──────────────────────────────────────────────
# Tool 17: skip_prospect
# ──────────────────────────────────────────────

@mcp.tool()
async def skip_prospect(
    outreach_id: str = "",
    campaign_id: str = "",
) -> str:
    """Skip a prospect, removing them from the outreach queue.

    Marks a prospect as 'skipped' so they won't receive any more
    invitations, follow-ups, or engagements. Useful for bad-fit
    prospects found during copilot review.

    Args:
        outreach_id: Specific outreach to skip. Skips lowest-fit pending if empty.
        campaign_id: Which campaign to skip from. Uses active campaign if empty.
    """
    from .tools.skip_prospect import run_skip_prospect

    logger.info(f"Running skip_prospect: outreach={outreach_id}, campaign={campaign_id}")
    try:
        return await run_skip_prospect(outreach_id, campaign_id)
    except Exception as e:
        logger.error(f"skip_prospect failed: {e}", exc_info=True)
        return f"Skip failed: {e}"


# ──────────────────────────────────────────────
# Tool 18: show_conversation
# ──────────────────────────────────────────────

@mcp.tool()
async def show_conversation(
    outreach_id: str,
) -> str:
    """View the full conversation thread with a prospect.

    Shows engagements (comments, reactions) and DM messages
    in chronological order with timestamps and sentiment.

    Args:
        outreach_id: The outreach ID to show conversation for.
            Use export_campaign() or show_status(campaign_id='...') to find IDs.
    """
    from .tools.show_conversation import run_show_conversation

    logger.info(f"Running show_conversation: outreach={outreach_id}")
    try:
        return await run_show_conversation(outreach_id)
    except Exception as e:
        logger.error(f"show_conversation failed: {e}", exc_info=True)
        return f"Failed to show conversation: {e}"


# ──────────────────────────────────────────────
# Tool 19: edit_campaign
# ──────────────────────────────────────────────

@mcp.tool()
async def edit_campaign(
    campaign_id: str = "",
    name: str = "",
    mode: str = "",
    booking_link: str = "",
    offerings: str = "",
    case_studies: str = "",
    social_proofs: str = "",
    campaign_preferences: str = "",
    voice_mode: str = "",
) -> str:
    """Edit a campaign's name, mode, booking link, or context fields.

    Change the campaign name, switch between copilot and autopilot
    modes, set a booking link, or configure campaign context for
    better message personalization.

    Args:
        campaign_id: Which campaign to edit. Edits the first active campaign if empty.
        name: New campaign name. Leave empty to keep current name.
        mode: New mode: "copilot" or "autopilot". Leave empty to keep current mode.
        booking_link: Calendar/booking URL (e.g., "https://cal.com/you/15min").
            Used in reply_to_prospect() for positive replies to suggest meetings.
        offerings: What you offer (products, services, value props). Used in follow-up messages.
        case_studies: Brief case studies or success stories. Used for social proof in messages.
        social_proofs: Social proof (logos, metrics, testimonials). Used in follow-up messages.
        campaign_preferences: Custom messaging preferences (tone, topics to avoid, etc.).
        voice_mode: Voice memo mode: "text_only", "voice_only", "mixed", or "ab_test".
            Leave empty to keep current value.
    """
    from .tools.edit_campaign import run_edit_campaign

    logger.info(f"Running edit_campaign: campaign={campaign_id}, name={name}, mode={mode}")
    try:
        return await run_edit_campaign(
            campaign_id, name, mode, booking_link,
            offerings, case_studies, social_proofs, campaign_preferences,
            voice_mode,
        )
    except Exception as e:
        logger.error(f"edit_campaign failed: {e}", exc_info=True)
        return f"Edit failed: {e}"


# ──────────────────────────────────────────────
# Tool 20: close_outreach
# ──────────────────────────────────────────────

@mcp.tool()
async def close_outreach(
    outreach_id: str = "",
    outcome: str = "won",
    reason: str = "",
    booking_link: str = "",
) -> str:
    """Close an outreach and record the outcome (won/lost/opt_out).

    Mark a prospect as won (converted), lost (declined/cold), or opt_out
    (requested no further contact). Records outcome details for analytics.

    Args:
        outreach_id: Which outreach to close. Uses first hot_lead if empty.
        outcome: "won", "lost", or "opt_out".
        reason: Optional notes (e.g., "Booked demo call", "Not interested").
        booking_link: Meeting/calendar link if outcome is "won".
    """
    from .tools.close_outreach import run_close_outreach

    logger.info(f"Running close_outreach: outreach={outreach_id}, outcome={outcome}")
    try:
        return await run_close_outreach(
            outreach_id=outreach_id,
            outcome=outcome,
            reason=reason,
            booking_link=booking_link,
        )
    except Exception as e:
        logger.error(f"close_outreach failed: {e}", exc_info=True)
        return f"Close failed: {e}"


# ──────────────────────────────────────────────
# Tool 21: campaign_report
# ──────────────────────────────────────────────

@mcp.tool()
async def campaign_report(
    campaign_id: str = "",
) -> str:
    """Generate a detailed analytics report with outcomes, conversion rates, and stale lead warnings.

    Deeper than show_status — includes outcome breakdown (won/lost),
    conversion rate, won deal details, stale lead detection, and
    engagement ROI. Sales pipeline analytics and outreach performance reporting.

    Args:
        campaign_id: Which campaign to report on. Uses the active campaign if empty.
    """
    from .tools.campaign_report import run_campaign_report

    logger.info(f"Running campaign_report: campaign={campaign_id}")
    try:
        return await run_campaign_report(campaign_id=campaign_id)
    except Exception as e:
        logger.error(f"campaign_report failed: {e}", exc_info=True)
        return f"Report failed: {e}"


# ──────────────────────────────────────────────
# Tool 22: emergency_stop
# ──────────────────────────────────────────────

@mcp.tool()
async def emergency_stop() -> str:
    """Immediately pause all active campaigns — emergency kill switch.

    Stops ALL outreach across every active campaign. Use this when you
    need to halt everything immediately (e.g., messaging errors, wrong
    audience, LinkedIn warnings).

    Use resume_campaign() afterward to re-enable campaigns one by one.
    """
    from .tools.emergency_stop import run_emergency_stop

    logger.info("Running emergency_stop")
    try:
        return await run_emergency_stop()
    except Exception as e:
        logger.error(f"emergency_stop failed: {e}", exc_info=True)
        return f"Emergency stop failed: {e}"


# ──────────────────────────────────────────────
# Tool 23: compare_campaigns
# ──────────────────────────────────────────────

@mcp.tool()
async def compare_campaigns(
    campaign_ids: str = "",
) -> str:
    """Compare 2+ campaigns side by side with key metrics.

    Shows acceptance rate, reply rate, hot leads, outcomes, conversion
    rate, and engagement stats in a comparison table. Highlights the
    best performer for each metric.

    Args:
        campaign_ids: Comma-separated campaign IDs to compare.
            Compares all active/paused campaigns if empty.
    """
    from .tools.compare_campaigns import run_compare_campaigns

    logger.info(f"Running compare_campaigns: {campaign_ids}")
    try:
        return await run_compare_campaigns(campaign_ids)
    except Exception as e:
        logger.error(f"compare_campaigns failed: {e}", exc_info=True)
        return f"Comparison failed: {e}"


# ──────────────────────────────────────────────
# Tool 26: scheduler_status
# ──────────────────────────────────────────────

@mcp.tool()
async def scheduler_status() -> str:
    """Show scheduler status, pending jobs, recent activity.

    The autonomous scheduler runs in the background and handles
    outreach for autopilot campaigns: invitations, follow-ups,
    reply checks, and engagement warm-ups.

    Shows both local and cloud scheduler status (if cloud scheduling is enabled).

    Use toggle_scheduler(enabled=True) to enable local scheduling.
    Use toggle_scheduler(enabled=True, cloud=True) for 24/7 cloud scheduling.
    """
    from .tools.scheduler_status import run_scheduler_status

    logger.info("Running scheduler_status")
    try:
        return await run_scheduler_status()
    except Exception as e:
        logger.error(f"scheduler_status failed: {e}", exc_info=True)
        return f"Scheduler status check failed: {e}"


# ──────────────────────────────────────────────
# Tool 27: toggle_scheduler
# ──────────────────────────────────────────────

@mcp.tool()
async def toggle_scheduler(
    enabled: bool = True,
    cloud: bool = False,
) -> str:
    """Enable or disable the autonomous scheduler.

    When enabled, the scheduler automatically processes outreach
    for autopilot campaigns: sending invitations, follow-ups,
    checking replies, and engaging with prospect posts.
    Supports 24/7 autonomous LinkedIn outreach automation.

    All actions respect rate limits and daily caps.
    Copilot campaigns still require manual approval.

    Args:
        enabled: True to enable, False to disable the scheduler.
        cloud: If True, toggle the cloud (backend) scheduler for 24/7 operation
            that continues even when your laptop is off. Requires backend mode.
    """
    from .tools.scheduler_status import run_toggle_scheduler

    logger.info(f"Running toggle_scheduler: enabled={enabled}, cloud={cloud}")
    try:
        return await run_toggle_scheduler(enabled, cloud=cloud)
    except Exception as e:
        logger.error(f"toggle_scheduler failed: {e}", exc_info=True)
        return f"Toggle scheduler failed: {e}"


# ──────────────────────────────────────────────
# Tool 29: create_post
# ──────────────────────────────────────────────

@mcp.tool()
async def create_post(
    topic: str = "",
    tone: str = "professional",
    mode: str = "copilot",
) -> str:
    """Generate and publish a voice-matched LinkedIn post.

    Creates LinkedIn posts using your voice signature for social selling.
    Builds authority and drives inbound connections. Supports content
    creation for thought leadership and prospect engagement.

    Args:
        topic: What to post about (e.g., "share a tip about cold outreach",
            "comment on AI in sales", "share a success story").
        tone: Post tone: "professional", "casual", "thought-leader", "storytelling".
        mode: "autopilot" (publishes immediately) or "copilot" (review first).
    """
    from .tools.create_post import run_create_post

    logger.info(f"Running create_post: topic={topic}, tone={tone}, mode={mode}")
    try:
        return await run_create_post(topic, tone, mode)
    except Exception as e:
        logger.error(f"create_post failed: {e}", exc_info=True)
        return f"Post creation failed: {e}"


# ──────────────────────────────────────────────
# Tool 30: brand_strategy
# ──────────────────────────────────────────────

@mcp.tool()
async def brand_strategy(
    action: str = "analyze",
    focus: str = "",
) -> str:
    """Analyze and improve your LinkedIn personal brand to drive more leads.

    Audits your profile, generates a personal brand strategy, executes
    actions (post topics, headline rewrites, engagement targets), and
    tracks improvement over time.

    Args:
        action: What to do:
            "analyze" — Full profile audit with scored areas and issues
            "plan"    — Generate a 4-week brand strategy with content calendar
            "execute" — Execute the next recommended action from your plan
            "progress" — Show before/after metrics and completed actions
        focus: Focus area: "headline", "summary", "content", "engagement", or "" for all.
    """
    from .tools.brand_strategy import run_brand_strategy

    logger.info("Running brand_strategy: action=%s, focus=%s", action, focus)
    try:
        return await run_brand_strategy(action, focus)
    except Exception as e:
        logger.error("brand_strategy failed: %s", e, exc_info=True)
        return f"Brand strategy failed: {e}"


# ──────────────────────────────────────────────
# Tool 31: import_prospects
# ──────────────────────────────────────────────

@mcp.tool()
async def import_prospects(
    campaign_id: str = "",
    csv_data: str = "",
    linkedin_enrich: bool = False,
) -> str:
    """Import prospects from CSV data into a campaign.

    Paste CSV text with prospect information and HeyLead will parse it,
    deduplicate against existing contacts and LinkedIn connections, score
    each prospect, and add them to the campaign for outreach.

    Supports CSV import, bulk prospect upload, lead list import,
    and contact list management for LinkedIn outreach campaigns.

    Args:
        campaign_id: Campaign to import into. Leave empty for the most recent.
        csv_data: CSV text with headers. Auto-detects columns:
            Name, Title, Company, LinkedIn URL, Email, Location.
            Must have Name + at least one of: Title, Company, or LinkedIn URL.
        linkedin_enrich: If true, fetch full LinkedIn profiles for imported
            prospects (slower but better personalization). Default: false.
    """
    from .tools.import_prospects import run_import_prospects

    logger.info("Running import_prospects: %d chars CSV", len(csv_data))
    try:
        return await run_import_prospects(campaign_id, csv_data, linkedin_enrich)
    except Exception as e:
        logger.error("import_prospects failed: %s", e, exc_info=True)
        return f"Import failed: {e}"


# ──────────────────────────────────────────────
# Tool 32: crm_sync
# ──────────────────────────────────────────────

@mcp.tool()
async def crm_sync(
    campaign_id: str = "",
    filter: str = "won",
    hubspot_api_key: str = "",
) -> str:
    """Sync campaign contacts and deals to HubSpot CRM.

    Pushes won deals, hot leads, or all contacts to HubSpot as contacts + deals.
    Tracks sync status to avoid duplicates. Includes conversation history as notes.
    Supports CRM integration, deal pipeline sync, and lead handoff to sales teams.

    First-time setup: create a HubSpot Private App with CRM scopes
    (contacts, deals, notes), then pass the access token here.
    The key is saved for future syncs.

    Args:
        campaign_id: Campaign to sync. Uses the most recent if empty.
        filter: Which contacts to sync: "won" (default), "hot_leads", or "all".
        hubspot_api_key: Optional — your HubSpot Private App access token.
            Only needed on first use; saved for future syncs.
    """
    from .tools.crm_sync import run_crm_sync

    logger.info("Running crm_sync: campaign=%s, filter=%s", campaign_id, filter)
    try:
        return await run_crm_sync(campaign_id, filter, hubspot_api_key)
    except Exception as e:
        logger.error("crm_sync failed: %s", e, exc_info=True)
        return f"CRM sync failed: {e}"


# ──────────────────────────────────────────────
# Tool 33: show_strategy
# ──────────────────────────────────────────────


@mcp.tool()
async def show_strategy(
    campaign_id: str = "",
) -> str:
    """Show the strategy engine's insights, learned patterns, and autonomous actions.

    Displays revenue pipeline, discovered cross-campaign patterns,
    recent strategy actions (skip, reorder, spawn), and playbook stats.
    The strategy engine runs every 4 hours and autonomously optimizes
    campaigns for maximum estimated deal revenue.

    Args:
        campaign_id: Focus on a specific campaign. Shows all if empty.
    """
    from .tools.show_strategy import run_show_strategy

    logger.info("Running show_strategy")
    try:
        return await run_show_strategy(campaign_id)
    except Exception as e:
        logger.error("show_strategy failed: %s", e, exc_info=True)
        return f"Strategy insights failed: {e}"


# ──────────────────────────────────────────────
# Signal-Based Selling (v1.0)
# ──────────────────────────────────────────────


@mcp.tool()
async def manage_watchlist(
    action: str = "list",
    name: str = "",
    watch_type: str = "keyword",
    keywords: str = "",
    watchlist_id: str = "",
    campaign_id: str = "",
) -> str:
    """Add, remove, and list signal keyword watchlists.

    Watchlists define keywords that HeyLead monitors on LinkedIn
    to detect buying signals from prospect posts. Watchlists are
    also auto-created when you generate an ICP.

    Args:
        action: What to do: 'list', 'add', 'remove', 'pause', 'resume'.
        name: Watchlist name (for 'add').
        watch_type: 'keyword' or 'competitor' (for 'add').
        keywords: Comma-separated keywords (for 'add'). E.g., "cold outreach, SDR automation".
        watchlist_id: Watchlist ID (for 'remove', 'pause', 'resume').
        campaign_id: Optional campaign to link the watchlist to.
    """
    from .tools.manage_watchlist import run_manage_watchlist

    logger.info("Running manage_watchlist: action=%s", action)
    try:
        return await run_manage_watchlist(action, name, watch_type, keywords, watchlist_id, campaign_id)
    except Exception as e:
        logger.error("manage_watchlist failed: %s", e, exc_info=True)
        return f"Watchlist management failed: {e}"


@mcp.tool()
async def show_signals(
    signal_type: str = "",
    campaign_id: str = "",
    status: str = "",
    limit: int = 20,
) -> str:
    """Show the signal feed — detected buying signals from LinkedIn.

    Displays keyword mentions, prospect post activity, job changes,
    competitor mentions, and other buying signals detected by watchlists.

    Args:
        signal_type: Filter by signal type (e.g., 'keyword_mention', 'job_change').
        campaign_id: Filter by campaign.
        status: Filter by status: 'new', 'classified', 'actioned'.
        limit: Max signals to show (default 20).
    """
    from .tools.show_signals import run_show_signals

    logger.info("Running show_signals: type=%s", signal_type)
    try:
        return await run_show_signals(signal_type, campaign_id, status, limit)
    except Exception as e:
        logger.error("show_signals failed: %s", e, exc_info=True)
        return f"Show signals failed: {e}"


@mcp.tool()
async def signal_report(
    days: int = 30,
    campaign_id: str = "",
) -> str:
    """Generate a detailed analytics report with outcomes, conversion rates, and stale lead warnings.

    Deeper than show_status — includes outcome breakdown (won/lost),
    conversion rate, won deal details, stale lead detection, and
    engagement ROI. Sales pipeline analytics and outreach performance reporting.

    Args:
        days: Lookback window in days (default 30).
        campaign_id: Which campaign to report on. Uses all campaigns if empty.
    """
    from .tools.signal_report import run_signal_report

    logger.info("Running signal_report: days=%d", days)
    try:
        return await run_signal_report(days, campaign_id)
    except Exception as e:
        logger.error("signal_report failed: %s", e, exc_info=True)
        return f"Signal report failed: {e}"


# ──────────────────────────────────────────────
# Entry point
# ──────────────────────────────────────────────

def main(transport: str = "stdio", host: str = "0.0.0.0", port: int = 8080) -> None:
    """Run the HeyLead MCP server.

    Args:
        transport: Transport type — "stdio", "sse", or "streamable-http".
        host: Host to bind to for HTTP transports. Default: 0.0.0.0.
        port: Port to bind to for HTTP transports. Default: 8080.
    """
    logger.info(f"Starting HeyLead MCP server v{__version__} (transport={transport})")

    # Ensure directories and DB exist on first run
    config.ensure_dirs()

    # Initialize the database
    from .db.schema import get_db
    db = get_db()
    db.close()

    # Configure HTTP transport settings if needed
    if transport in ("sse", "streamable-http"):
        mcp.settings.host = host
        mcp.settings.port = port
        logger.info(f"HTTP server binding to {host}:{port}")

    # Run MCP server
    mcp.run(transport=transport)
