"""Reactive rate limiting — no proactive caps.

All hardcoded daily/weekly/hourly limits have been removed.
Actions are attempted freely; when Unipile returns 429 or 422
temporary_provider_limit the cloud scheduler applies exponential backoff.

This module now only provides:
- Pacing helpers (delay between sends)
- Pending invitation cache + withdrawal utility
- Block type constants (for backwards compat)
"""

from __future__ import annotations

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

from .. import constants
from ..db import aio as db
from ..db.async_bridge import run_db

logger = logging.getLogger(__name__)

# Block type constants — kept for backwards compatibility with callers
BLOCK_NONE = ""        # Not blocked
BLOCK_TIME = "time"    # Outside working hours/days
BLOCK_DAILY = "daily"  # Unused — no daily cap
BLOCK_WEEKLY = "weekly"  # Unused — no weekly cap
BLOCK_PENDING = "pending"  # Unused — no pending cap


async def can_send_now(
    *, skip_time_check: bool = False
) -> tuple[bool, str, str]:
    """Always returns True — no proactive rate limits.

    Kept for backwards compatibility with callers.
    """
    return True, "", BLOCK_NONE


async def can_send_email_now() -> tuple[bool, str]:
    """Always returns True — no proactive email limits."""
    return True, ""


async def increment_email_sent() -> None:
    """Track an email send in the email rate limits table."""
    from ..db.queries import increment_email_sent as _inc
    await run_db(_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)


async def update_limits_after_send(blocked: bool = False) -> int:
    """No-op — adaptive limits removed. Returns 0."""
    return 0


async def check_engagement_budget(
    engagement_type: str = "",
) -> tuple[bool, int, int]:
    """Always returns has_budget=True — no proactive engagement limits."""
    return True, 0, 999


async def check_pending_limit(
    client: Any, account_id: str
) -> tuple[bool, str, str]:
    """Always returns can_send=True — no proactive pending limit."""
    return True, "", BLOCK_NONE


async def estimate_weekly_limit_reset() -> tuple[bool, int, str]:
    """Always returns not-at-limit — no weekly cap."""
    return False, 0, ""


async def _get_effective_caps() -> tuple[int, int]:
    """No proactive caps — return high defaults for health score calculation.

    Returns (weekly_cap, daily_max).  Kept as a stub so callers that
    reference the old adaptive-limit function don't crash.
    """
    return 200, 30


# ──────────────────────────────────────────────
# Pending invitation cache + withdrawal (utility, not rate limiting)
# ──────────────────────────────────────────────

_pending_cache: dict[str, Any] = {}


async def get_cached_pending_invitations(
    client: Any, account_id: str
) -> tuple[int, list[dict]]:
    """Return (count, invitation_list) with a 5-minute TTL cache.

    On cache miss, fetches from LinkedIn via client.get_pending_invitations().
    """
    now = time.time()
    fetched_at = _pending_cache.get("fetched_at", 0)
    if (
        _pending_cache.get("invitations") is not None
        and (now - fetched_at) < constants.PENDING_CACHE_TTL_SECONDS
    ):
        invitations = _pending_cache["invitations"]
        return len(invitations), invitations

    try:
        invitations = await client.get_pending_invitations(account_id)
    except Exception as e:
        logger.warning("Failed to fetch pending invitations: %s", e)
        if _pending_cache.get("invitations") is not None:
            invitations = _pending_cache["invitations"]
            return len(invitations), invitations
        return 0, []

    _pending_cache["invitations"] = invitations
    _pending_cache["fetched_at"] = now
    return len(invitations), invitations


def invalidate_pending_cache() -> None:
    """Clear the pending invitation cache. Call after send or withdrawal."""
    _pending_cache.clear()


async def withdraw_oldest_to_free_spot(
    client: Any, account_id: str
) -> dict[str, Any]:
    """Withdraw the oldest pending invitation to free a spot.

    Returns {success, invitation_id, days_old, name}.
    """
    count, invitations = await get_cached_pending_invitations(
        client, account_id
    )
    if not invitations:
        return {"success": False, "error": "No pending invitations to withdraw"}

    # Parse timestamps and sort oldest-first
    now_ts = int(time.time())
    candidates = []
    for inv in invitations:
        inv_time = (
            inv.get("parsed_datetime")
            or inv.get("timestamp")
            or inv.get("created_at")
            or 0
        )
        if isinstance(inv_time, str):
            try:
                inv_time = int(
                    datetime.fromisoformat(
                        inv_time.replace("Z", "+00:00")
                    ).timestamp()
                )
            except (ValueError, TypeError):
                inv_time = 0
        inv_id = inv.get("id") or inv.get("invitation_id") or ""
        if inv_id and inv_time:
            candidates.append((inv_time, inv_id, inv))

    if not candidates:
        return {"success": False, "error": "No valid invitation candidates"}

    candidates.sort(key=lambda x: x[0])  # oldest first
    oldest_time, oldest_id, oldest_inv = candidates[0]
    days_old = (now_ts - oldest_time) // 86400
    name = oldest_inv.get("invited_user") or oldest_inv.get("name") or ""

    try:
        result = await client.withdraw_invitation(account_id, oldest_id)
    except Exception as e:
        logger.warning("Failed to withdraw invitation %s: %s", oldest_id, e)
        return {"success": False, "error": str(e)}

    if result.get("success"):
        invalidate_pending_cache()
        await db.log_action(
            "invite_withdrawn_to_free_spot",
            result="success",
            details={
                "invitation_id": oldest_id,
                "days_old": days_old,
                "name": name,
                "pending_count_before": count,
            },
        )
        logger.info(
            "Withdrew oldest invitation %s (%d days old, %s) to free spot",
            oldest_id,
            days_old,
            name,
        )
        return {
            "success": True,
            "invitation_id": oldest_id,
            "days_old": days_old,
            "name": name,
        }

    error = result.get("error", "Unknown error")
    logger.warning("Failed to withdraw invitation %s: %s", oldest_id, error)
    return {"success": False, "error": error}
