"""Prompt loader — loads v63 production prompts from JSON files.

Provides a simple interface for loading prompt templates and rendering them
with HeyLead data structures mapped to v63 variable names.

Usage:
    from .prompt_loader import render_prompt, build_context_block, get_prompt_temperature

    ctx = build_context_block(sender, prospect, campaign, voice, ...)
    prompt = render_prompt("outreach_invitation", ctx)
    temp = get_prompt_temperature("outreach_invitation")
"""

from __future__ import annotations

import json
import logging
import time
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)

# Prompt files directory (sibling to ai/)
_PROMPTS_DIR = Path(__file__).parent.parent / "prompts"

# Cache loaded prompts in memory
_prompt_cache: dict[str, dict] = {}


class SafeDict(dict):
    """Dict subclass that returns '{key}' for missing keys in str.format_map().

    This prevents KeyError when a prompt template has variables that aren't
    provided — they're left as-is in the output.
    """

    def __missing__(self, key: str) -> str:
        return "{" + key + "}"


def load_prompt(name: str) -> dict:
    """Load a prompt template from JSON file.

    Args:
        name: Prompt name (e.g., "outreach_invitation", "followup_reasoning")

    Returns:
        Dict with keys: name, description, version, content, variables,
        temperature, response_limit

    Raises:
        FileNotFoundError: If prompt file doesn't exist
    """
    if name in _prompt_cache:
        return _prompt_cache[name]

    path = _PROMPTS_DIR / f"{name}.json"
    if not path.exists():
        raise FileNotFoundError(f"Prompt file not found: {path}")

    with open(path, encoding="utf-8") as f:
        data = json.load(f)

    _prompt_cache[name] = data
    return data


def render_prompt(name: str, variables: dict[str, Any]) -> str:
    """Load a prompt and render it with variables.

    Uses SafeDict so missing variables are left as '{var}' placeholders
    rather than raising KeyError.

    Args:
        name: Prompt name (e.g., "outreach_invitation")
        variables: Dict of variable values to substitute

    Returns:
        Rendered prompt string

    Raises:
        FileNotFoundError: If prompt file doesn't exist
    """
    prompt_data = load_prompt(name)
    content = prompt_data["content"]

    # Use SafeDict for graceful handling of missing variables
    safe_vars = SafeDict(variables)
    rendered = content.format_map(safe_vars)

    return rendered


def get_prompt_temperature(name: str) -> float:
    """Get the recommended temperature for a prompt.

    Args:
        name: Prompt name

    Returns:
        Temperature float (e.g., 0.7, 0.95)
    """
    try:
        data = load_prompt(name)
        return float(data.get("temperature", 0.7))
    except FileNotFoundError:
        return 0.7


def get_prompt_response_limit(name: str) -> int:
    """Get the response symbol limit for a prompt.

    Args:
        name: Prompt name

    Returns:
        Response limit in characters
    """
    try:
        data = load_prompt(name)
        return int(data.get("response_limit", 200))
    except FileNotFoundError:
        return 200


def has_prompt(name: str) -> bool:
    """Check if a prompt file exists.

    Args:
        name: Prompt name

    Returns:
        True if the prompt JSON file exists
    """
    path = _PROMPTS_DIR / f"{name}.json"
    return path.exists()


# ──────────────────────────────────────────────
# Context Builder — maps HeyLead data → v63 variables
# ──────────────────────────────────────────────


def _format_contact_info(prospect: dict[str, Any]) -> str:
    """Format prospect data into v63 {{contact_info}} block."""
    parts = []
    name = prospect.get("name", "")
    if name:
        parts.append(f"Name: {name}")
    title = prospect.get("title", "")
    if title:
        parts.append(f"Title: {title}")
    company = prospect.get("company", "")
    if company:
        parts.append(f"Company: {company}")
    headline = prospect.get("headline", "")
    if headline:
        parts.append(f"Headline: {headline}")
    location = prospect.get("location", "")
    if location:
        parts.append(f"Location: {location}")
    industry = prospect.get("industry", "")
    if industry:
        parts.append(f"Industry: {industry}")
    return "\n".join(parts) if parts else "No contact information available."


def _format_history(messages: list[dict[str, Any]] | None) -> str:
    """Format conversation history for v63 {{history}} variable."""
    if not messages:
        return "No previous messages."
    lines = []
    for msg in messages:
        role = msg.get("role", "unknown")
        text = msg.get("text", "")
        label = "SDR" if role == "sdr" else "PROSPECT"
        lines.append(f"[{label}]: {text}")
    return "\n".join(lines)


def _format_analysis_section(
    analysis: dict[str, Any] | None, section: str
) -> str:
    """Format optional analysis sections for v63 conditional blocks."""
    if not analysis:
        return ""
    data = analysis.get(section, {})
    if not data:
        return ""

    if section == "tone":
        parts = []
        if data.get("recommended_approach"):
            parts.append(f"Recommended approach: {data['recommended_approach']}")
        if data.get("formality_level"):
            parts.append(f"Formality: {data['formality_level']}/10")
        if data.get("industry_jargon"):
            jargon = data["industry_jargon"]
            if isinstance(jargon, list):
                parts.append(f"Jargon: {', '.join(jargon[:5])}")
        return f"Tone Analysis: {'; '.join(parts)}" if parts else ""

    if section == "pain_points":
        if isinstance(data, list):
            return f"Pain Point Analysis: {'; '.join(str(p) for p in data[:3])}"
        return ""

    return ""


def build_context_block(
    sender: dict[str, Any],
    prospect: dict[str, Any],
    campaign_config: dict[str, Any],
    voice: dict[str, Any],
    campaign_context: dict[str, Any] | None = None,
    history: list[dict[str, Any]] | None = None,
    analysis: dict[str, Any] | None = None,
    max_chars: int = 200,
    news_context: str | None = None,
    previously_used: str | None = None,
) -> dict[str, str]:
    """Build the complete variable dict mapping HeyLead data → v63 variable names.

    Args:
        sender: User's profile data (name, title, company)
        prospect: Prospect profile data
        campaign_config: Campaign config_json (parsed)
        voice: Voice signature dict
        campaign_context: Optional campaign context_json (offerings, case_studies, etc.)
        history: Optional conversation history (list of {role, text})
        analysis: Optional prospect analysis (from prospect_analyzer)
        max_chars: Character limit for the message
        news_context: Optional formatted news text from SERPER API
        previously_used: Optional formatted memory block for anti-repetition

    Returns:
        Dict ready to pass to render_prompt()
    """
    ctx = campaign_context or {}

    # Core v63 variables
    variables: dict[str, str] = {
        # Prospect info
        "contact_info": _format_contact_info(prospect),
        # Trigger / relevance
        "trigger_info": campaign_config.get("relevance_hook", "")
            or campaign_config.get("target_description", ""),
        # Campaign context (new fields from context_json)
        "offerings": ctx.get("offerings", "Not specified"),
        "case_studies": ctx.get("case_studies", "Not specified"),
        "social_proofs": ctx.get("social_proofs", "Not specified"),
        "campaign_preferences": ctx.get("campaign_preferences", ""),
        "custom_params": ctx.get("custom_params", ""),
        # Calendar / booking
        "calendar_link": campaign_config.get("booking_link", "Not configured"),
        # Sender
        "company": sender.get("company", "Our company"),
        # Conversation
        "history": _format_history(history),
        # Timestamp
        "current_date_time": time.strftime("%Y-%m-%d %H:%M %Z"),
        # Limits
        "symbols_limit": f"Response symbols limit: {max_chars} characters maximum.",
        # Analysis sections (v63 uses conditional blocks)
        "tone_analysis_section": _format_analysis_section(analysis, "tone")
            if analysis else "",
        "pain_point_section": _format_analysis_section(analysis, "pain_points")
            if analysis else "",
        # News context (from SERPER API, Sprint 15)
        "news_context": news_context or "",
        # Conversation memory (from memory_json, Sprint 16)
        "previously_used": previously_used or "",
    }

    # Voice-related variables (used by HeyLead prompts, not v63 — but included for compatibility)
    voice_vocab = voice.get("vocabulary_preferences", [])
    if isinstance(voice_vocab, list):
        voice_vocab = ", ".join(voice_vocab)

    variables.update({
        "voice_tone": voice.get("tone", "Professional, direct"),
        "voice_sentence": voice.get("sentence_length", "Medium"),
        "voice_vocab": voice_vocab or "None specified",
        "voice_nogo": voice.get("no_go", "Generic sales phrases"),
        # Prospect shorthand
        "prospect_name": prospect.get("name", "").split()[0] if prospect.get("name") else "there",
        "prospect_title": prospect.get("title", ""),
        "prospect_company": prospect.get("company", ""),
        "prospect_headline": prospect.get("headline", ""),
        "prospect_location": prospect.get("location", ""),
        # Sender shorthand
        "sender_name": sender.get("name", ""),
        "sender_title": sender.get("title", ""),
        "sender_company": sender.get("company", ""),
    })

    return variables


def clear_cache() -> None:
    """Clear the prompt cache (useful for testing or hot-reloading)."""
    _prompt_cache.clear()
