"""Work planner — determines WHAT to schedule and WHEN.

Analyzes active autopilot campaigns and creates scheduler jobs for:
1. Invitations: pending outreaches that need to be sent
2. Follow-ups: connected outreaches that are due based on schedule
3. Engagements: warm-up comments/reactions before inviting

All decisions respect:
- Rate limits (can_send_now, daily/weekly caps)
- Follow-up schedule (PRO_FOLLOWUP_SCHEDULE_DAYS: 1, 7, 14, 21, 28 days)
- Deduplication (no duplicate pending jobs for same outreach)
- Randomized delays (20-40 min invites, 20-35 min follow-ups, 25-40 min engagements)
"""

from __future__ import annotations

import logging
import random
import time
from typing import Any

from ..config import get_tier
from ..constants import (
    DAILY_ENGAGEMENT_LIMIT,
    ENGAGEMENT_DELAY_MAX,
    ENGAGEMENT_DELAY_MIN,
    FOLLOWUP_DELAY_MAX,
    FOLLOWUP_DELAY_MIN,
    FREE_MAX_FOLLOWUPS,
    INVITE_DELAY_MAX,
    INVITE_DELAY_MIN,
    JOB_ENGAGE,
    JOB_FOLLOWUP,
    JOB_INVITE,
    PRO_FOLLOWUP_SCHEDULE_DAYS,
    PRO_MAX_FOLLOWUPS,
    TIER_PRO,
)
from ..db.queries import (
    create_scheduler_job,
    get_daily_engagement_count,
    get_engagement_candidates,
    get_followup_candidates,
    get_messages_for_outreach,
    get_outreach,
    get_pending_job_count,
    get_pending_outreach_job,
)
from ..linkedin.rate_limiter import can_send_now

logger = logging.getLogger(__name__)


async def plan_campaign_work(campaign_id: str) -> None:
    """Analyze an autopilot campaign and schedule invite + follow-up jobs.

    Called by the scheduler engine every tick for each active autopilot campaign.
    Creates jobs in the scheduler_jobs table with randomized future scheduled_at times.
    """
    now = int(time.time())
    tier = get_tier()

    # ── 1. Invitations ──
    await _plan_invitations(campaign_id, now)

    # ── 2. Follow-ups ──
    await _plan_followups(campaign_id, now, tier)


async def plan_engagements(campaign_id: str) -> None:
    """Schedule engagement warm-up jobs for an autopilot campaign.

    Called less frequently (every 30 min) since engagements are lower priority.
    """
    now = int(time.time())

    # Check daily engagement limit
    daily_count = get_daily_engagement_count()
    if daily_count >= DAILY_ENGAGEMENT_LIMIT:
        logger.debug("Daily engagement limit reached (%d/%d)", daily_count, DAILY_ENGAGEMENT_LIMIT)
        return

    # Check for existing pending engagement jobs for this campaign
    pending_count = get_pending_job_count(campaign_id, JOB_ENGAGE)
    if pending_count >= 2:
        logger.debug("Already %d pending engagement jobs for campaign %s", pending_count, campaign_id)
        return

    # Find engagement candidates
    candidates = get_engagement_candidates(campaign_id, max_per_outreach=3)
    if not candidates:
        return

    # Schedule up to 2 engagement jobs with staggered delays
    scheduled = 0
    for candidate in candidates[:3]:
        outreach_id = candidate["outreach_id"]

        # Skip if this outreach already has a pending engagement job
        if get_pending_outreach_job(outreach_id, JOB_ENGAGE):
            continue

        delay = random.randint(ENGAGEMENT_DELAY_MIN, ENGAGEMENT_DELAY_MAX)
        scheduled_at = now + delay + (scheduled * random.randint(300, 600))

        create_scheduler_job(
            campaign_id=campaign_id,
            job_type=JOB_ENGAGE,
            scheduled_at=scheduled_at,
            outreach_id=outreach_id,
        )
        logger.debug(
            "Scheduled engagement for outreach %s in %d min",
            outreach_id, (scheduled_at - now) // 60,
        )
        scheduled += 1
        if scheduled >= 2:
            break


async def _plan_invitations(campaign_id: str, now: int) -> None:
    """Schedule invitation jobs for pending outreaches."""
    # Check rate limits first
    can_send, reason = can_send_now()
    if not can_send:
        logger.debug("Cannot send invitations: %s", reason)
        return

    # Check for existing pending invite jobs (dedup)
    pending_invites = get_pending_job_count(campaign_id, JOB_INVITE)
    if pending_invites >= 2:
        logger.debug("Already %d pending invite jobs for campaign %s", pending_invites, campaign_id)
        return

    # Check if there are pending outreaches to invite
    # (the generate_and_send tool internally picks by fit_score)
    from ..db.queries import count_outreaches_by_status
    pending_count = count_outreaches_by_status(campaign_id, "pending")

    if pending_count == 0:
        return

    # Schedule one invitation with randomized delay
    delay = random.randint(INVITE_DELAY_MIN, INVITE_DELAY_MAX)
    scheduled_at = now + delay

    create_scheduler_job(
        campaign_id=campaign_id,
        job_type=JOB_INVITE,
        scheduled_at=scheduled_at,
    )
    logger.debug(
        "Scheduled invitation for campaign %s in %d min",
        campaign_id, delay // 60,
    )


async def _plan_followups(campaign_id: str, now: int, tier: str) -> None:
    """Schedule follow-up jobs for connected outreaches that are due."""
    max_followups = PRO_MAX_FOLLOWUPS if tier == TIER_PRO else FREE_MAX_FOLLOWUPS

    # Check for existing pending followup jobs (dedup)
    pending_followups = get_pending_job_count(campaign_id, JOB_FOLLOWUP)
    if pending_followups >= 3:
        logger.debug("Already %d pending followup jobs for campaign %s", pending_followups, campaign_id)
        return

    # Find connected outreaches ready for follow-up
    candidates = get_followup_candidates(campaign_id, max_followups)
    if not candidates:
        return

    scheduled = 0
    for candidate in candidates:
        outreach_id = candidate["outreach_id"]

        # Skip if this outreach already has a pending followup job
        if get_pending_outreach_job(outreach_id, JOB_FOLLOWUP):
            continue

        # Check if follow-up is due based on schedule
        if not _is_followup_due(candidate, tier):
            continue

        # Schedule with randomized delay + stagger
        delay = random.randint(FOLLOWUP_DELAY_MIN, FOLLOWUP_DELAY_MAX)
        scheduled_at = now + delay + (scheduled * random.randint(300, 600))

        create_scheduler_job(
            campaign_id=campaign_id,
            job_type=JOB_FOLLOWUP,
            scheduled_at=scheduled_at,
            outreach_id=outreach_id,
        )
        logger.debug(
            "Scheduled follow-up #%d for outreach %s in %d min",
            candidate.get("followup_count", 0) + 1,
            outreach_id,
            (scheduled_at - now) // 60,
        )
        scheduled += 1
        if scheduled >= 2:
            break


def _is_followup_due(candidate: dict[str, Any], tier: str) -> bool:
    """Check if a follow-up is due based on the schedule.

    Uses PRO_FOLLOWUP_SCHEDULE_DAYS for Pro tier: [1, 7, 14, 21, 28]
    Free tier: simpler check (just needs to have been connected for >= 1 day)

    Args:
        candidate: Outreach candidate from get_followup_candidates()
        tier: User's tier ('free' or 'pro')

    Returns:
        True if the follow-up is due now.
    """
    followup_count = candidate.get("followup_count", 0)
    outreach_updated = candidate.get("outreach_updated_at", 0)

    if not outreach_updated:
        return False

    now = int(time.time())
    days_since = (now - outreach_updated) // 86400

    if tier == TIER_PRO and followup_count > 0:
        # Pro tier: use schedule
        schedule_idx = min(followup_count, len(PRO_FOLLOWUP_SCHEDULE_DAYS) - 1)
        required_days = PRO_FOLLOWUP_SCHEDULE_DAYS[schedule_idx]
        return days_since >= required_days
    else:
        # First follow-up (any tier) or free tier: wait at least 1 day
        return days_since >= 1
