"""Simple adaptive rate limiting — ~15 lines of core logic.

From the spec: "That's it. Not 2-3 weeks. ~15 lines. Ships Day 1."

Logic:
- Start at 15 invitations/day
- If acceptance rate >30%: increase by 5 (cap 50)
- If acceptance rate <15%: decrease by 5 (floor 10)
- If blocked: halve (floor 5)
- Hard weekly cap: 100 (200 for Sales Navigator)

Runs 24/7 — autonomous bot has no working hours restriction.
Randomized delays: 15-45 minutes between sends.
"""

from __future__ import annotations

import logging
import random
from datetime import date, datetime, timedelta
from typing import Any

from .. import constants
from ..config import load_config
from ..db.queries import get_rate_limit_today, update_daily_limit

logger = logging.getLogger(__name__)

# Block type constants — classify WHY sends are blocked
BLOCK_NONE = ""        # Not blocked
BLOCK_TIME = "time"    # Outside working hours/days — both channels wait
BLOCK_DAILY = "daily"  # Daily LinkedIn limit hit — email can take over
BLOCK_WEEKLY = "weekly" # Weekly LinkedIn cap hit — email can take over


def _get_effective_caps() -> tuple[int, int]:
    """Return (weekly_cap, daily_max) based on account type (Sales Nav or regular)."""
    from ..db.queries import get_setting
    if get_setting("has_sales_navigator", False):
        return constants.WEEKLY_INVITATION_CAP_SALES_NAV, constants.MAX_DAILY_INVITATIONS_SALES_NAV
    return constants.WEEKLY_INVITATION_CAP, constants.MAX_DAILY_INVITATIONS


def adjust_daily_limit(current: int, stats: dict[str, Any]) -> int:
    """The entire adaptive system — ~15 lines.

    Args:
        current: Current daily limit
        stats: Dict with 'sent', 'accepted', 'blocked' keys

    Returns:
        New daily limit
    """
    if stats.get("blocked"):
        return max(constants.BLOCK_HALVE_FLOOR, current // 2)

    sent = stats.get("sent", 0)
    accepted = stats.get("accepted", 0)

    if sent == 0:
        return current

    rate = accepted / sent

    _, daily_max = _get_effective_caps()
    if rate > constants.ACCEPTANCE_RATE_RAMP_UP and current < daily_max:
        return current + constants.RAMP_STEP

    if rate < constants.ACCEPTANCE_RATE_PULL_BACK:
        return max(constants.MIN_DAILY_LIMIT, current - constants.RAMP_STEP)

    return current


def can_send_now() -> tuple[bool, str, str]:
    """Check if we can send a LinkedIn invitation right now.

    Returns (can_send, reason_if_not, block_type).
    block_type is one of: BLOCK_NONE, BLOCK_TIME, BLOCK_DAILY, BLOCK_WEEKLY.
    """
    now = datetime.now()
    cfg = load_config()
    working_hours = cfg.get("working_hours", {})

    start_hour = working_hours.get("start", constants.DEFAULT_START_HOUR)
    end_hour = working_hours.get("end", constants.DEFAULT_END_HOUR)
    active_days = working_hours.get("days", constants.DEFAULT_ACTIVE_DAYS)

    # Check day of week (0=Monday)
    if now.weekday() not in active_days:
        return False, f"Outside working days. Active days: {active_days}", BLOCK_TIME

    # Check working hours
    if now.hour < start_hour:
        return False, f"Before working hours ({start_hour}:00). Queued for later.", BLOCK_TIME
    if now.hour >= end_hour:
        return False, f"After working hours ({end_hour}:00). Queued for tomorrow.", BLOCK_TIME

    # Check daily limit
    rate_data = get_rate_limit_today()
    sent_today = rate_data.get("sent", 0)
    daily_limit = rate_data.get("daily_limit", constants.INITIAL_DAILY_INVITATIONS)

    if sent_today >= daily_limit:
        return False, f"Daily limit reached ({sent_today}/{daily_limit}). Resumes tomorrow.", BLOCK_DAILY

    # Check weekly cap (subscription-aware)
    from ..db.queries import get_weekly_invitation_sum
    weekly_sent = get_weekly_invitation_sum()
    weekly_cap, _ = _get_effective_caps()
    if weekly_sent >= weekly_cap:
        return False, f"Weekly limit reached ({weekly_sent}/{weekly_cap}). Resumes next week.", BLOCK_WEEKLY

    return True, "", BLOCK_NONE


def can_send_email_now() -> tuple[bool, str]:
    """Check if we can send a cold email right now.

    Respects the same working hours as LinkedIn but uses separate
    email-specific daily/weekly limits.

    Returns (can_send, reason_if_not).
    """
    now = datetime.now()
    cfg = load_config()
    working_hours = cfg.get("working_hours", {})

    start_hour = working_hours.get("start", constants.DEFAULT_START_HOUR)
    end_hour = working_hours.get("end", constants.DEFAULT_END_HOUR)
    active_days = working_hours.get("days", constants.DEFAULT_ACTIVE_DAYS)

    # Check day of week
    if now.weekday() not in active_days:
        return False, f"Outside working days. Active days: {active_days}"

    # Check working hours
    if now.hour < start_hour:
        return False, f"Before working hours ({start_hour}:00)."
    if now.hour >= end_hour:
        return False, f"After working hours ({end_hour}:00)."

    # Check daily email limit
    from ..db.queries import get_email_rate_limit_today, get_weekly_email_sum
    rate_data = get_email_rate_limit_today()
    sent_today = rate_data.get("sent", 0)
    daily_limit = constants.DAILY_EMAIL_OUTREACH_LIMIT

    if sent_today >= daily_limit:
        return False, f"Daily email limit reached ({sent_today}/{daily_limit}). Resumes tomorrow."

    # Check weekly email cap
    weekly_sent = get_weekly_email_sum()
    weekly_cap = constants.WEEKLY_EMAIL_OUTREACH_CAP
    if weekly_sent >= weekly_cap:
        return False, f"Weekly email limit reached ({weekly_sent}/{weekly_cap}). Resumes next week."

    return True, ""


def increment_email_sent() -> None:
    """Track an email send in the email rate limits table."""
    from ..db.queries import increment_email_sent as _inc
    _inc()


def get_next_delay() -> int:
    """Get randomized delay in seconds for the next send.

    Returns a random delay between MIN_DELAY_MINUTES and MAX_DELAY_MINUTES.
    """
    min_secs = constants.MIN_DELAY_MINUTES * 60
    max_secs = constants.MAX_DELAY_MINUTES * 60
    return random.randint(min_secs, max_secs)


def estimate_weekly_limit_reset() -> tuple[bool, int, str]:
    """Estimate when the weekly invitation limit will free up capacity.

    The weekly limit is a rolling 7-day window. Capacity frees up when
    the oldest day's invitations drop out of the window.

    Returns:
        (is_at_limit, seconds_until_capacity, human_readable_message)
    """
    from ..db.queries import get_weekly_invitation_sum
    from ..db.schema import get_db

    weekly_cap, _ = _get_effective_caps()
    weekly_sent = get_weekly_invitation_sum()

    if weekly_sent < weekly_cap:
        return False, 0, ""

    # Find per-day sent counts in the 7-day window
    today = date.today()
    week_ago = (today - timedelta(days=6)).isoformat()
    db = get_db()
    rows = db.execute(
        """SELECT date, sent FROM rate_limits
           WHERE date >= ? AND sent > 0
           ORDER BY date ASC""",
        (week_ago,),
    ).fetchall()
    db.close()

    if not rows:
        return True, 86400, "~1 day"

    # Walk days oldest-first: find when enough invites drop to go under cap
    overshoot = weekly_sent - weekly_cap
    now = datetime.now()
    for row in rows:
        row_date = date.fromisoformat(row["date"])
        row_sent = row["sent"]
        overshoot -= row_sent
        if overshoot < 0:
            # This day dropping out frees enough capacity
            drop_date = row_date + timedelta(days=7)
            drop_dt = datetime.combine(drop_date, datetime.min.time())
            secs = max(0, int((drop_dt - now).total_seconds()))
            hours = secs // 3600
            if hours >= 24:
                days = hours // 24
                msg = f"~{days} day{'s' if days != 1 else ''}"
            elif hours > 0:
                msg = f"~{hours} hour{'s' if hours != 1 else ''}"
            else:
                msg = "< 1 hour"
            return True, secs, msg

    # All days needed to drop — capacity frees when last day drops
    oldest_date = date.fromisoformat(rows[-1]["date"])
    drop_date = oldest_date + timedelta(days=7)
    drop_dt = datetime.combine(drop_date, datetime.min.time())
    secs = max(0, int((drop_dt - now).total_seconds()))
    days = max(1, secs // 86400)
    return True, secs, f"~{days} day{'s' if days != 1 else ''}"


def update_limits_after_send(blocked: bool = False) -> int:
    """Update rate limits after a send attempt. Returns new daily limit."""
    rate_data = get_rate_limit_today()

    stats = {
        "sent": rate_data.get("sent", 0),
        "accepted": rate_data.get("accepted", 0),
        "blocked": blocked,
    }

    new_limit = adjust_daily_limit(rate_data.get("daily_limit", 15), stats)

    if new_limit != rate_data.get("daily_limit"):
        update_daily_limit(new_limit)
        logger.info(f"Daily limit adjusted: {rate_data.get('daily_limit')} → {new_limit}")

    return new_limit
