"""Tool 3: generate_and_send — Generate a personalized message and send (or queue for review).

Core outreach loop:
1. Pick next prospect from campaign queue
2. Generate personalized invitation message (voice-matched)
3. Run 5-stage validation
4. In Copilot mode: show for approval. In Autopilot: send immediately.
5. Track rate limits and scheduling
"""

from __future__ import annotations

import json
import logging
import time
from typing import Any

from ..ai.message_generator import generate_message
from ..ai.message_validator import validate_message
from ..config import get_tier
from ..constants import (
    COPILOT_APPROVAL_THRESHOLD,
    FREE_MONTHLY_INVITATIONS,
    INVITATION_NOTE_MAX_CHARS,
    TIER_PRO,
)
from ..db.queries import (
    get_contacts_for_campaign,
    get_monthly_usage,
    get_setting,
    increment_sent,
    increment_usage,
    list_campaigns,
    log_action,
    save_message,
    update_campaign,
    update_outreach,
)
from ..formatter import stars
from ..linkedin.rate_limiter import can_send_now, get_next_delay, update_limits_after_send
from ..linkedin import (
    UnipileAuthError,
    UnipileError,
    get_account_id,
    get_linkedin_client,
)

logger = logging.getLogger(__name__)


async def run_generate_and_send(
    campaign_id: str = "",
    mode: str = "copilot",
) -> str:
    """Generate and send (or queue) a personalized LinkedIn message.

    Flow:
    1. Find the active campaign and next pending prospect
    2. Generate a personalized message using voice signature
    3. Validate the message (5-stage pipeline)
    4. Copilot: show for review. Autopilot: send directly.
    """

    # ── Step 0: Pre-checks ──
    setup_done = get_setting("setup_complete", False)
    if not setup_done:
        return (
            "❌ Please run setup_profile first!\n"
            "I need your voice signature to generate personalized messages."
        )

    account_id = get_account_id()
    if not account_id:
        return "❌ No LinkedIn account connected. Run setup_profile first."

    try:
        client = get_linkedin_client()
    except UnipileError as e:
        return f"❌ {e}"

    # ── Step 1: Find campaign + next prospect ──
    if campaign_id:
        from ..db.queries import get_campaign
        campaign = get_campaign(campaign_id)
        if not campaign:
            return f"❌ Campaign not found: {campaign_id}"
    else:
        # Find the active campaign
        campaigns = list_campaigns(status="active")
        if not campaigns:
            return (
                "❌ No active campaigns.\n\n"
                "Create one first: create_campaign(\"your target description\")"
            )
        campaign = campaigns[0]
        campaign_id = campaign["id"]

    # Find next pending outreach
    from ..db.schema import get_db
    db = get_db()
    row = db.execute(
        """SELECT o.id as outreach_id, o.contact_id,
                  c.id as contact_db_id, c.campaign_id as contact_campaign_id,
                  c.name, c.title, c.company, c.linkedin_url, c.linkedin_id,
                  c.profile_json, c.analysis_json, c.fit_score, c.status as contact_status
           FROM outreaches o
           JOIN contacts c ON o.contact_id = c.id
           WHERE o.campaign_id = ? AND o.status = 'pending'
           ORDER BY c.fit_score DESC
           LIMIT 1""",
        (campaign_id,),
    ).fetchone()
    db.close()

    if not row:
        return (
            f"✅ All prospects in this campaign have been reached!\n\n"
            f"Campaign: {campaign['name']}\n"
            "Use show_status to see results, or create_campaign for a new target."
        )

    prospect = dict(row)
    outreach_id = prospect["outreach_id"]
    contact_id = prospect["contact_id"]

    # ── Step 2: Check rate limits ──
    can_send, reason = can_send_now()
    if not can_send and mode == "autopilot":
        return f"⏸️ Sending paused: {reason}\n\nThe queue is ready — will resume automatically."

    # Check free tier monthly limit
    tier = get_tier()
    if tier != TIER_PRO:
        usage = get_monthly_usage()
        if usage.get("invitations_sent", 0) >= FREE_MONTHLY_INVITATIONS:
            return (
                f"⚠️ Free tier limit reached: {FREE_MONTHLY_INVITATIONS} invitations/month.\n\n"
                "Upgrade to Pro ($29/mo) for unlimited outreach.\n"
                "Your campaign will resume next month if you stay on Free."
            )

    # ── Step 3: Generate message ──
    sender_profile = get_setting("profile", {})
    voice_signature = get_setting("voice_signature", {})
    campaign_config = json.loads(campaign.get("config_json", "{}"))
    icp_data = json.loads(campaign.get("icp_json", "{}"))

    campaign_context = {
        "target_description": campaign_config.get("target_description", ""),
        "relevance_hook": icp_data.get("relevance_hook", ""),
    }

    prospect_data = json.loads(prospect.get("profile_json", "{}")) if prospect.get("profile_json") else {
        "name": prospect.get("name", ""),
        "title": prospect.get("title", ""),
        "company": prospect.get("company", ""),
        "headline": f"{prospect.get('title', '')} at {prospect.get('company', '')}",
    }

    # Generate with up to 3 retries if validation fails
    max_attempts = 3
    message = ""
    validation = None

    for attempt in range(max_attempts):
        try:
            message = await generate_message(
                prospect=prospect_data,
                sender_profile=sender_profile,
                voice_signature=voice_signature,
                campaign_context=campaign_context,
            )
        except Exception as e:
            logger.error(f"Message generation failed (attempt {attempt+1}): {e}")
            if attempt == max_attempts - 1:
                return f"❌ Failed to generate message after {max_attempts} attempts: {e}"
            continue

        # Validate
        validation = validate_message(message, voice_signature, INVITATION_NOTE_MAX_CHARS)
        if validation.is_valid:
            break
        else:
            logger.info(f"Validation failed (attempt {attempt+1}): {validation.issues}")

    if not validation or not validation.is_valid:
        # Use the last generated message anyway, but warn
        issues_text = "\n".join(f"  ⚠️ {issue}" for issue in (validation.issues if validation else []))
        logger.warning(f"Message sent despite validation warnings: {issues_text}")

    # ── Step 4: Copilot vs Autopilot ──
    prospect_name = prospect.get("name", "Unknown")
    prospect_title = prospect.get("title", "")
    prospect_company = prospect.get("company", "")
    prospect_url = prospect.get("linkedin_url", "")
    fit_score = prospect.get("fit_score", 0)

    role_str = prospect_title
    if prospect_company:
        role_str += f" at {prospect_company}" if role_str else prospect_company

    if mode == "copilot":
        # Show message for review — don't send yet
        # Store the message in the outreach record so we can send it on approval
        update_outreach(outreach_id, status="review_pending", next_action=message)

        output = [
            f"📝 Message for **{prospect_name}** ({role_str}):",
            f"   Fit: {stars(fit_score)}",
            f"   {prospect_url}" if prospect_url else "",
            "",
            f'   "{message}"',
            f"   ({len(message)}/200 chars)",
            "",
        ]

        # Show validation warnings if any
        if validation and validation.warnings:
            for w in validation.warnings:
                output.append(f"   ⚠️ {w}")
            output.append("")

        output.extend([
            "Send this? Reply with:",
            "  → 'yes' / 'send' — send this message",
            "  → 'skip' — skip this prospect",
            "  → 'edit: [your text]' — send a custom message instead",
            "  → 'stop' — pause the campaign",
        ])

        # Track approvals for Autopilot prompt
        approval_count = get_setting("copilot_approvals", 0)
        if approval_count >= COPILOT_APPROVAL_THRESHOLD - 1:
            output.extend([
                "",
                f"💡 You've approved {approval_count + 1} messages in a row.",
                "   Ready to switch to Autopilot? Say 'autopilot' and I'll handle the rest.",
            ])

        return "\n".join(line for line in output if line is not None)

    else:
        # Autopilot — send immediately
        if not can_send:
            update_outreach(outreach_id, next_action=message)
            return f"⏸️ Queued for later: {reason}"

        # Get the provider_id for Unipile invitation
        provider_id = (
            prospect_data.get("provider_id", "")
            or prospect.get("linkedin_id", "")
            or prospect_data.get("public_id", "")
        )
        if not provider_id:
            update_outreach(outreach_id, status="error")
            await client.close()
            return f"❌ No LinkedIn ID for {prospect_name}. Skipping."

        # Send the invitation via Unipile
        result = await client.send_invitation(
            account_id=account_id,
            provider_id=provider_id,
            message=message,
        )
        await client.close()

        # Update rate limits
        increment_sent()
        new_limit = update_limits_after_send(blocked=result.get("blocked", False))

        if result["success"]:
            # Update DB
            update_outreach(outreach_id, status="invited")
            save_message(outreach_id, role="sdr", text=message)
            increment_usage("invitations_sent")
            log_action("invitation_sent", outreach_id=outreach_id, result="success",
                       details={"prospect": prospect_name, "message_length": len(message)})

            delay = get_next_delay()
            delay_minutes = delay // 60

            return (
                f"✅ Sent to {prospect_name} ({role_str})\n"
                f'   "{message}"\n\n'
                f"⏱️ Next send in ~{delay_minutes} minutes\n"
                f"📊 Daily limit: {new_limit}/day"
            )
        else:
            error = result.get("error", "Unknown error")
            if result.get("auth_error"):
                # Account disconnected — don't change outreach status, just alert user
                update_outreach(outreach_id, status="pending")
                log_action("auth_error", outreach_id=outreach_id, result="blocked",
                           details={"error": error})
                return (
                    "🔑 LinkedIn account disconnected.\n\n"
                    "Run setup_profile() again to reconnect your LinkedIn account."
                )
            elif result.get("blocked"):
                update_outreach(outreach_id, status="pending")
                log_action("invitation_blocked", outreach_id=outreach_id, result="blocked",
                           details={"error": error})
                return (
                    f"⚠️ LinkedIn blocked the send: {error}\n\n"
                    f"Daily limit reduced to {new_limit}. Will retry later."
                )
            else:
                update_outreach(outreach_id, status="error")
                log_action("invitation_failed", outreach_id=outreach_id, result="error",
                           details={"error": error})
                return f"❌ Send failed for {prospect_name}: {error}"
