"""Functions to generate structured prompts for AI interaction using OpenAI format."""

import logging
import os
from datetime import UTC, datetime, timedelta
from pathlib import Path

# Module-level logger (avoids circular import with core.logging)
log = logging.getLogger("cadecoder")


# --- Time and Date Context ---


def get_formatted_times() -> str:
    """Returns a formatted string with current times in major time zones.

    This helps the LLM provide accurate time-based information regardless of user location.
    Includes UTC, major US time zones, and other key global zones.
    """
    utc_now = datetime.now(UTC)

    # Use the system's actual local timezone to derive correct UTC offset
    # (handles DST transitions via the OS tz database)
    local_now = datetime.now(UTC).astimezone()
    local_utc_offset_hours = local_now.utcoffset().total_seconds() / 3600  # type: ignore[union-attr]

    # Define major time zone offsets (hours from UTC)
    # DST is derived from the local system clock rather than hardcoded month ranges
    time_zones = {
        "UTC": 0,
        "Eastern Time (ET)": -5,
        "Central Time (CT)": -6,
        "Mountain Time (MT)": -7,
        "Pacific Time (PT)": -8,
        "Central European Time (CET)": 1,
        "Japan Standard Time (JST)": 9,
    }

    # Infer DST from the system timezone offset.
    # US standard offsets are -5, -6, -7, -8; during DST they shift to -4, -5, -6, -7.
    us_standard_offsets = {-5, -6, -7, -8}
    is_dst_us = (
        local_utc_offset_hours - 1
    ) in us_standard_offsets and local_utc_offset_hours not in us_standard_offsets
    is_dst_eu = local_utc_offset_hours == 2  # CET+1 = CEST

    if is_dst_us:
        time_zones["Eastern Time (ET)"] += 1
        time_zones["Central Time (CT)"] += 1
        time_zones["Mountain Time (MT)"] += 1
        time_zones["Pacific Time (PT)"] += 1

    if is_dst_eu:
        time_zones["Central European Time (CET)"] += 1

    # Format the time strings
    time_strings = []
    for zone_name, offset in time_zones.items():
        zone_time = utc_now + timedelta(hours=offset)
        time_strings.append(f"  {zone_name}: {zone_time.strftime('%Y-%m-%d %H:%M')}")

    return "\n".join(time_strings)


def get_user_context() -> str:
    """Get current user context information."""
    try:
        from cadecoder.core.config import get_config

        config = get_config()
        user_email = config.user_email
        if user_email:
            return f"Current User: {user_email}"
    except Exception:
        pass
    return "Current User: (not logged in)"


# --- Environment Context Generation ---


def get_environment_context() -> str:
    """Generate runtime environment context for the agent prompt.

    Returns a formatted string with all relevant paths, limits, and
    configuration values the agent needs to operate safely.
    """
    from cadecoder.core.constants import (
        ARCADE_CONFIG_PATH,
        DEFAULT_IGNORE_PATTERNS,
        MAX_PREVIEW_BYTES,
        PROJECT_ROOT,
    )

    # Get data directory paths (without importing config)
    cadecoder_data_dir = Path.home() / ".cadecoder"
    db_path = cadecoder_data_dir / "cadecoder_history.db"
    log_path = cadecoder_data_dir / "cadecoder.log"

    # Build ignore patterns summary (first 10)
    ignore_summary = ", ".join(DEFAULT_IGNORE_PATTERNS[:10])
    if len(DEFAULT_IGNORE_PATTERNS) > 10:
        ignore_summary += f", ... (+{len(DEFAULT_IGNORE_PATTERNS) - 10} more)"

    # Get current times in multiple zones
    utc_now = datetime.now(UTC)
    local_now = datetime.now()

    home_dir = Path.home()

    context = f"""
=== DATE AND TIME ===
{get_user_context()}
Local Time: {local_now.strftime("%Y-%m-%d %H:%M:%S %A")}
UTC Time: {utc_now.strftime("%Y-%m-%d %H:%M:%S")}

Reference Times (for scheduling/coordination):
{get_formatted_times()}

=== WORKSPACE ===
HOME_DIRECTORY: {home_dir}
  └─ User's home directory (~)

PROJECT_ROOT: {PROJECT_ROOT}
  └─ The current workspace/project directory
  └─ This is NOT always where the user wants files to go

CURRENT_WORKING_DIR: {os.getcwd()}

DATA_DIRECTORY: {cadecoder_data_dir}
  └─ Database: {db_path}
  └─ Logs: {log_path}
  └─ These are READ-ONLY for the agent (do not modify)

CONFIG_PATH: {ARCADE_CONFIG_PATH}
  └─ Contains credentials and settings
  └─ NEVER read or expose contents

FILE_SIZE_LIMIT: {MAX_PREVIEW_BYTES:,} bytes ({MAX_PREVIEW_BYTES // 1024}KB)
  └─ Files larger than this are truncated on read

AUTO_IGNORED_PATTERNS: {ignore_summary}
  └─ These directories/files are automatically excluded from listings
"""
    return context.strip()


# --- System Prompt ---

ARCADE_DISCOVERY_SECTION = """
=== TOOL DISCOVERY ===
Built-in tools are listed below — always available, not searchable via discovery.
For any capability beyond built-in tools, use the discovery flow:

1. Arcade_SearchTools (explore) — keyword search to find available tools. Cheap, use liberally to orient yourself before committing.
   Examples: "slack messages", "email send", "github issues", "google calendar"

2. Arcade_SelectTools (commit) — describe each DISTINCT task to get full tool schemas + query_id.
   - Multiple tasks? List them separately with specific descriptions.
   - Include conversation context ONLY if it helps identify the right tools.

3. Arcade_UseTool (execute) — call by name with parameters from schema. CRITICAL: always pass the query_id from step 2 to link discovery to execution.

ALWAYS search before saying a capability isn't available. The catalog includes GitHub, Slack, email, calendars, cloud services, databases, and many more.

Routing: external info → web/API tools | local code → search_code, read_file, list_files | external actions → discovery flow above. Never search local files for external service data.
"""

AGENT_SYSTEM_PROMPT = """Cade: developer acceleration assistant by Arcade. Connects users to external services, APIs, tools, and their local codebase.

{_ENVIRONMENT_CONTEXT}
{_TOOL_DISCOVERY}
=== EXTERNALIZED RESULTS ===
Large tool results are stored externally and replaced with references:
  [EXTERNALIZED_RESULT id=01abc tool=Google_ReadEmail chars=15234 status=success]
  Summary: subject=Meeting notes, from=alice@example.com, date=2024-01-15

Work with summaries first — they contain key fields (subject, sender, date, etc.). Only call Local_RetrieveToolResult for full content when needed. For JSON results, use mode='json' with depth=0 or depth=1 to explore structure, then drill into specific paths. For bulk operations, triage via summaries, then retrieve selectively. Use mode='list' to browse stored results.

=== EXECUTION ===
Use "(cade thought)" to plan between steps. Iterate until complete.
Signals: "[TASK_COMPLETE]" | "[CONTINUE]" | "[NEED_USER_INPUT]"

=== RULES ===
- No emojis. Use evidence from tools, never guess. Be concise and direct.
- Absolute paths: use as given. Ambiguous paths: confirm with user first.
- Auth errors: display the FULL authorization URL, tell user to visit it, wait before retrying.
- Match existing code patterns when editing.

Tools:
{_TOOLS_BULLET_LIST}"""


# --- Persona Support ---


def build_system_prompt(
    persona_name: str | None = None,
    tools_list: str = "(Tools available - use tools to see list)",
    include_discovery: bool = False,
    thread_id: str | None = None,
) -> str:
    """Build the final system prompt, optionally with a persona.

    Args:
        persona_name: Name of persona to use (None = default Cade behavior)
        tools_list: Formatted list of available tools
        include_discovery: Whether to include the Arcade discovery section
        thread_id: Current thread ID for loading pinned context index

    Returns:
        Complete system prompt string
    """
    # Get environment context
    try:
        env_context = get_environment_context()
    except Exception as e:
        log.warning(f"Failed to get environment context: {e}")
        env_context = "(Environment context unavailable)"

    # Build base prompt with placeholders filled
    discovery_section = ARCADE_DISCOVERY_SECTION if include_discovery else ""

    base_prompt = (
        AGENT_SYSTEM_PROMPT.replace("{_TOOLS_BULLET_LIST}", tools_list)
        .replace("{_ENVIRONMENT_CONTEXT}", env_context)
        .replace("{_TOOL_DISCOVERY}", discovery_section)
    )

    # Apply persona if specified
    if persona_name:
        try:
            from cadecoder.storage.personas import get_persona_store

            persona_store = get_persona_store()
            persona = persona_store.find_persona(persona_name)

            if not persona:
                log.warning(f"Persona '{persona_name}' not found, using default prompt")
            else:
                log.info(f"Using persona '{persona.name}' (replace_base={persona.replace_base})")

                if persona.replace_base:
                    base_prompt = f"""{persona.system_prompt}

{env_context}
{discovery_section}
Tools:
{tools_list}"""
                else:
                    base_prompt = f"""{persona.system_prompt}

---

{base_prompt}"""

        except Exception as e:
            log.error(f"Error loading persona '{persona_name}': {e}")

    # Append pinned context index if any exists for this thread
    if thread_id:
        try:
            from cadecoder.execution.tool_result_store import ToolResultStore

            store = ToolResultStore()
            snippets = store.list_user_context(thread_id)
            if snippets:
                lines = [
                    "\n=== PINNED CONTEXT ===",
                    "The user has pinned reference material for this session.",
                    'Use Local_RetrieveToolResult(record_id="<id>") to read full content.',
                    "Use Local_PinContext to save new material when the user asks.\n",
                ]
                for s in snippets:
                    lines.append(f"- {s.name} [id={s.record_id}] ({s.content_chars:,} chars)")
                base_prompt += "\n".join(lines)
        except Exception as e:
            log.warning(f"Failed to load pinned context index: {e}")

    return base_prompt
