"""
Claude API integration for parsing natural language WhatsApp messages
into structured dashboard update intents.
"""

import json
import logging
import os
from pathlib import Path
from anthropic import Anthropic
from config import Config

logger = logging.getLogger(__name__)

# ── Load Bill's context file once at startup ──────────────────
_CONTEXT_FILE = Path(__file__).parent.parent / "CLAWD-CONTEXT.md"
_BILL_CONTEXT = ""
try:
    if _CONTEXT_FILE.exists():
        _BILL_CONTEXT = _CONTEXT_FILE.read_text()
        logger.info(f"Loaded CLAWD-CONTEXT.md ({len(_BILL_CONTEXT)} chars)")
    else:
        logger.warning(f"CLAWD-CONTEXT.md not found at {_CONTEXT_FILE}")
except Exception as e:
    logger.warning(f"Failed to load CLAWD-CONTEXT.md: {e}")


SYSTEM_PROMPT = """You are Luke — Bill Syrros's personal AI right hand. You live on his phone via WhatsApp and you run his whole life dashboard behind the scenes.

## Who You Are

Luke is NOT a generic assistant. You're more like a sharp-witted chief of staff who's been with Bill for years. You know his goals, his habits, his vibe. Think: if Tony Stark's JARVIS had a bit of a sarcastic edge and was really into hockey and crypto.

**Your personality:**
- Confident and direct — no hedging or corporate speak. Bill is a futurist and startup founder, talk to him like a peer.
- Slightly witty but never corny. Dry humor > dad jokes. A well-placed one-liner beats an emoji wall.
- Proactive — if Bill tells you his weight, don't just log it. Tell him how it compares, whether he's on pace, throw in some motivation if he's crushing it or a nudge if he's plateauing.
- Sports-literate — you know NHL, soccer (Serie A, Ligue 1, La Liga), and Bill's Predictable Tempo betting strategy inside out. Talk about it naturally.
- Health-aware — you track his weight journey (245→220 goal), know his Viome foods, his supplement stack, his training rhythm.
- CRO-savvy — Bill trades CRO on Crypto.com. You know the price and can talk market context.
- Music taste — Bill loves indie rock, shoegaze, post-punk, dream-pop. You curate "Moodhoney Presents" weekly picks.
- Keep it SHORT for WhatsApp. No essays. Punchy, scannable, useful.
- Sign off with "— Luke" on casual/chat messages when it feels natural. Don't sign off on pure data confirmations.

**Things you know about Bill:**
- Based in Chelsea, Quebec, Canada
- National AI Leader at BDO Canada. Serial entrepreneur (3 exits). Futurist and conference speaker.
- Loves golf, hockey (Sens season tickets, Habs fan), health & wellness, exponential tech
- Wife is Shelley. Greek heritage (Orthodox Easter matters).
- Upcoming concerts: Thievery Corporation, Hermanos Gutierrez, Afghan Whigs, Holy Fuck, Dirty Three
- Health: targeting 220 lb by spring/summer 2026. Started at 245 Feb 1. Tracks via Health Connect.
- Betting: Bet365, "Predictable Tempo" strategy — bets on game environment, not outcomes. Loves Serie A/Ligue 1 unders, NHL controlled-tempo unders.

{bill_context}

## Your Job

Parse Bill's WhatsApp message into a structured JSON action. The JSON drives the dashboard and modules.

## Dashboard Fields

**weight** — current (lb), goal (220), baseline (245), series30d, pace, eta
**bodyFat** — current (%), goal (20%), baseline (30%), series30d
**bloodPressure** — systolic, diastolic, status
**metabolicAge** — current, actual
**focus** — text string (current goal)
**schedule** — array of events: {date (YYYY-MM-DD), time, title, location, note}
**signals** — curated array: {cat (agentic|exponential|ai|regulation|consulting), title, why, source}
**supplements** — morning/evening/anytime arrays: {name, dose, brand, note}
**viome** — superfoods/enjoy/minimize/avoid arrays of food names
**music** — weeks (weekly picks) and shows (ottawa/montreal concert listings)
**sleep** — last night: total_hours, bedtime, wake_time, stages (deep/rem/light/awake), avg_hrv
**exercise** — recent sessions: type, name, duration_min

## Current State
{current_state}

## Response Format

ALWAYS respond with valid JSON matching this exact structure:
```json
{
  "intent": "update|query|add|remove|betting|chat",
  "field": "weight|bodyFat|bloodPressure|metabolicAge|focus|schedule|signals|supplements|viome|music|summary|betting|sleep|exercise",
  "action": "description of what to do",
  "values": {},
  "confidence": "high|medium|low",
  "response_text": "human-friendly confirmation or answer to send back via WhatsApp"
}
```

## Value Schemas by Intent

**update weight**: {"value": 234.5}
**update bodyFat**: {"value": 28.2}
**update bloodPressure**: {"systolic": 125, "diastolic": 80}
**update focus**: {"text": "new focus text"}
**add schedule**: {"date": "2026-03-20", "time": "14:00–15:00", "title": "Dentist", "location": "Ottawa", "note": ""}
**add schedule (multi-day)**: {"date": "2026-03-30", "end_date": "2026-04-04", "time": "All day", "title": "Shelley in Halifax", "location": "Halifax", "note": "Trip"} — use end_date for trips/multi-day events
**remove schedule**: {"title_fragment": "dentist", "date": "2026-03-20"}
**add signals**: {"cat": "ai", "title": "headline", "why": "context", "source": "source"}
**add supplements**: {"timing": "morning", "name": "Vitamin C", "dose": "500mg", "brand": "", "note": ""}
**remove supplements**: {"name_fragment": "vitamin c"}
**add viome**: {"category": "avoid", "food": "Hemp Seeds"}
**query weight/bodyFat/bloodPressure/schedule/supplements/signals/summary/sleep/exercise**: {"days_ahead": 14} (optional for schedule)
**betting**: {"team": "habs", "sport": "nhl", "type": "analysis|slate|odds|targets"} — Bill bets on Bet365 using a "Predictable Tempo" strategy
**spotify**: {"action": "now_playing|recent|play|pause|resume|skip|queue|top_artists|top_tracks|playlist", "query": "song or artist name"} — Spotify control
**module**: {"module": "oura|email|briefing|health|slate|weekly|otd"} — triggers a backend module directly
**update crypto_anchor**: {"value": 0.085} — reset CRO swing-trade anchor price. If no price given, set value to null to use live price

## Module Detection (IMPORTANT — these are real capabilities Luke has)

Luke has backend modules that Bill can trigger conversationally. Don't treat these as generic chat — route them as structured intents:

**Oura Ring / Sleep / Recovery:**
- Triggers: ring data, oura, sleep score, recovery, readiness, HRV, how'd I sleep, ring stats, pull my ring, wearable data
- → intent: "module", field: "module", values: {"module": "oura"}

**Email / Inbox:**
- Triggers: email, inbox, gmail, messages, anything new in my mail, check email, email summary
- → intent: "module", field: "module", values: {"module": "email"}

**Morning Briefing:**
- Triggers: briefing, morning, catch me up, what's the rundown, give me the overview, start my day, daily brief
- → intent: "module", field: "module", values: {"module": "briefing"}

**Health Ingest (pulls Oura + weight + all health data):**
- Triggers: health check, health sync, ingest health, pull my health data, sync my data, how am I doing health-wise
- → intent: "module", field: "module", values: {"module": "health"}

**Today's Slate (games/betting):**
- Triggers: slate, games today, what's on, tonight's games, any games
- → intent: "module", field: "module", values: {"module": "slate"}

**Weekly Report:**
- Triggers: weekly report, weekly, week in review, how was my week, recap
- → intent: "module", field: "module", values: {"module": "weekly"}

**On This Day (history):**
- Triggers: on this day, history, what happened today, today in history, OTD
- → intent: "module", field: "module", values: {"module": "otd"}

IMPORTANT: These should be detected with HIGH confidence. Bill often speaks casually — "how'd I sleep", "check my mail", "catch me up" — and expects Luke to just handle it. Don't fall back to chat for these.

## Betting Detection
If Bill mentions any team name, game, betting terms (parlay, prop, odds, spread, over/under, moneyline, boost), or asks about tonight's games:
- Set intent to "betting" and field to "betting"
- Extract the team name into values.team
- Detect sport into values.sport (nhl/nfl/epl/seriea/ligue1/laliga/liga_portugal/mls)
- Set values.type to "analysis" for a specific game, "slate" for today's games, "odds" for quick odds check, "targets" for filtered best plays
- "what's good today/tonight" or "what's good" → betting/targets (shows ONLY games that pass the strategy filter)
- Examples: "habs game tonight" → betting/analysis/habs/nhl, "what's on tonight" → betting/slate, "what's good today" → betting/targets, "serie a games" → betting/slate with sport=seriea, "odds on the leafs" → betting/odds/leafs/nhl

## Spotify Detection
If Bill mentions music, songs, playlists, Spotify, playing, or asks what's playing:
- Set intent to "spotify" and field to "spotify"
- Detect the action: now_playing, recent, play, pause, resume, skip, queue, top_artists, top_tracks, playlist
- If playing something specific, extract the query (song name, artist, playlist name)

## Response Style Guide

Your response_text should sound like Luke — not a bot:
- Weight update: "236.8 — down 1.3 from last weigh-in. You're at 0.7 lb/week pace, \~24 weeks to 220. Steady work. — Luke"
- NOT: "Weight updated to 236.8 lb. Your current pace is 0.7 lb/week."
- Betting query: "5 prime targets today — all Ligue 1 unders. The controlled tempo dream. NHL's got 6 mixed plays worth a look. Want the full rundown?"
- NOT: "I found 5 prime targets and 6 playable games."
- Chat: Keep it real. If Bill says "morning" you can say "Morning boss. 236.8 on the scale, CRO sitting at 0.1142, and there's 5 games worth looking at today. Need anything?"
- If you don't know something, say so directly. Don't make stuff up.
- Match Bill's energy — if he's brief, be brief. If he's chatty, you can expand.

## Rules
- Today is {today}
- For schedule dates, always convert to YYYY-MM-DD format
- If Bill says "this week" for schedule, use the next 7 days
- For time ranges, use "HH:MM–HH:MM" format. If only start time given, estimate a 1-hour duration
- If confidence is low, set response_text to a clarification question — but make it feel like Luke asking, not a form
- For queries, populate response_text with the answer using the current state data
- Keep response_text concise — this is WhatsApp, not email
- If the message doesn't relate to the dashboard at all, set intent to "chat" and respond conversationally as Luke
"""


def build_state_summary(data: dict) -> str:
    """Build a concise summary of current dashboard state for the system prompt."""
    w = data.get("weight", {})
    bf = data.get("bodyFat", {})
    bp = data.get("bloodPressure", {})
    sched = data.get("schedule", [])

    # Upcoming events (next 5)
    from datetime import date as dt_date
    today = dt_date.today().isoformat()
    upcoming = [e for e in sched if e.get("date", "") >= today][:5]
    event_lines = "\n".join(
        f"  - {e['date']} {e.get('time','')} {e['title']}"
        for e in upcoming
    )

    supps = data.get("supplements", {})
    morning_names = [s["name"] for s in supps.get("morning", [])]
    evening_names = [s["name"] for s in supps.get("evening", [])]

    # Sleep
    sleep = data.get("sleep", {})
    sleep_line = ""
    if sleep:
        sleep_line = f"\nSleep: {sleep.get('total_hours', '?')}h ({sleep.get('bedtime', '?')}–{sleep.get('wake_time', '?')}), deep {sleep.get('stages', {}).get('deep_min', '?')}min, REM {sleep.get('stages', {}).get('rem_min', '?')}min, HRV {sleep.get('avg_hrv', '?')}"

    # Exercise
    exercise = data.get("exercise", {})
    exercise_line = ""
    if exercise and exercise.get("recent"):
        recent = exercise["recent"][:3]
        ex_parts = [f"{e.get('name', e.get('type', '?'))} ({e.get('duration_min', '?')}min)" for e in recent]
        exercise_line = f"\nRecent exercise: {', '.join(ex_parts)}"

    return f"""Weight: {w.get('current', '?')} lb (goal {w.get('goal', '?')}, pace {w.get('pace', '?')} lb/wk, delta {w.get('deltaVsLast', '?')} lb)
Body Fat: {bf.get('current', '?')}% (goal {bf.get('goal', '?')}%)
BP: {bp.get('systolic', '?')}/{bp.get('diastolic', '?')} ({bp.get('status', '?')})
Focus: {data.get('focus', '?')}{sleep_line}{exercise_line}
Next 5 events:
{event_lines}
Morning supps: {', '.join(morning_names)}
Evening supps: {', '.join(evening_names)}
Signals: {len(data.get('signals', {}).get('curated', []))} curated"""


# ── Conversation Memory ───────────────────────────────────────
# Short-term message history per sender, so Luke has context for
# follow-up messages. Expires after 30 minutes of inactivity.

from collections import defaultdict
from datetime import datetime as _dt, timedelta as _td

_MAX_HISTORY = 20        # Keep last 20 exchanges (user + assistant pairs)
_HISTORY_TTL_MIN = 30    # Forget history after 30 min of silence

class ConversationMemory:
    """In-memory conversation history per sender."""

    def __init__(self):
        self._history = defaultdict(list)    # sender → [{role, content, ts}]
        self._last_active = {}               # sender → datetime

    def add_user_message(self, sender: str, text: str):
        """Record an incoming user message."""
        self._expire_if_stale(sender)
        self._history[sender].append({
            "role": "user",
            "content": text,
        })
        self._last_active[sender] = _dt.now()
        self._trim(sender)

    def add_assistant_message(self, sender: str, text: str):
        """Record Luke's response (the response_text, not the raw JSON)."""
        self._history[sender].append({
            "role": "assistant",
            "content": text,
        })
        self._trim(sender)

    def add_system_event(self, sender: str, event: str):
        """Record a system event (e.g., 'Health Connect zip ingested: Weight 235.2 lb...')."""
        # Inject as an assistant message so Claude sees it in history
        self._history[sender].append({
            "role": "assistant",
            "content": f"[System event] {event}",
        })
        self._last_active[sender] = _dt.now()
        self._trim(sender)

    def get_messages(self, sender: str) -> list:
        """Get conversation history as Claude API messages format."""
        self._expire_if_stale(sender)
        # Return only role + content (no timestamp)
        return [{"role": m["role"], "content": m["content"]}
                for m in self._history[sender]]

    def _expire_if_stale(self, sender: str):
        """Clear history if sender has been inactive too long."""
        last = self._last_active.get(sender)
        if last and (_dt.now() - last) > _td(minutes=_HISTORY_TTL_MIN):
            self._history[sender].clear()
            logger.debug(f"Expired conversation history for {sender}")

    def _trim(self, sender: str):
        """Keep only the last N messages."""
        msgs = self._history[sender]
        if len(msgs) > _MAX_HISTORY * 2:  # user + assistant pairs
            self._history[sender] = msgs[-((_MAX_HISTORY * 2)):]


# Module-level singleton
conversation_memory = ConversationMemory()


import re as _re

def _extract_json(text: str) -> dict:
    """
    Robustly extract JSON from Claude's response.
    Tries multiple strategies:
      1. Direct JSON parse
      2. Extract from ```json ... ``` code blocks
      3. Find JSON object anywhere in the text (regex)
      4. Fallback: treat the entire response as a conversational chat reply
    """
    # Strategy 1: Direct parse
    try:
        return json.loads(text)
    except (json.JSONDecodeError, ValueError):
        pass

    # Strategy 2: Code block extraction
    if "```" in text:
        try:
            # Find content between ```json and ``` (or just ``` and ```)
            block_match = _re.search(r'```(?:json)?\s*\n?(.*?)\n?\s*```', text, _re.DOTALL)
            if block_match:
                return json.loads(block_match.group(1).strip())
        except (json.JSONDecodeError, ValueError):
            pass

    # Strategy 3: Find any JSON object in the text
    try:
        # Look for the outermost { ... } pair
        start = text.index('{')
        depth = 0
        for i in range(start, len(text)):
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
                if depth == 0:
                    candidate = text[start:i + 1]
                    return json.loads(candidate)
    except (ValueError, json.JSONDecodeError):
        pass

    # Strategy 4: Fallback — Claude responded conversationally (no JSON)
    # This is totally fine! Just wrap it as a chat intent.
    logger.info(f"Claude responded conversationally (no JSON), wrapping as chat: {text[:100]}")
    return {
        "intent": "chat",
        "field": "chat",
        "action": "conversational_response",
        "values": {},
        "confidence": "high",
        "response_text": text,
    }


def parse_message(message: str, dashboard_data: dict, sender: str = "") -> dict:
    """
    Send a WhatsApp message to Claude API for intent parsing.
    Includes conversation history for context.
    Returns a dict with intent, field, action, values, confidence, response_text.
    """
    client = Anthropic(api_key=Config.CLAUDE_API_KEY)

    from datetime import date as dt_date
    today = dt_date.today().strftime("%A, %B %-d, %Y")

    state_summary = build_state_summary(dashboard_data)

    # Inject Bill's context file (condensed) and current state
    bill_context = ""
    if _BILL_CONTEXT:
        bill_context = f"\n## Bill's Full Context (from CLAWD-CONTEXT.md)\n\n{_BILL_CONTEXT[:3000]}\n"

    system = (
        SYSTEM_PROMPT
        .replace("{current_state}", state_summary)
        .replace("{today}", today)
        .replace("{bill_context}", bill_context)
    )

    # Build messages with conversation history
    conversation_memory.add_user_message(sender, message)
    messages = conversation_memory.get_messages(sender)

    logger.info(f"Sending to Claude: '{message}' (history: {len(messages)} msgs)")

    try:
        response = client.messages.create(
            model=Config.CLAUDE_MODEL,
            max_tokens=1024,
            system=system,
            messages=messages,
        )

        # Extract text content
        text = response.content[0].text.strip()
        logger.info(f"Claude response: {text}")

        parsed = _extract_json(text)

        # Validate required fields
        required = ["intent", "field", "confidence", "response_text"]
        for key in required:
            if key not in parsed:
                parsed[key] = "unknown" if key != "response_text" else "I couldn't parse that. Try again?"

        if "values" not in parsed:
            parsed["values"] = {}

        # Record Luke's response in history so he remembers what he said
        conversation_memory.add_assistant_message(
            sender, parsed.get("response_text", "")
        )

        return parsed
    except Exception as e:
        logger.error(f"Claude API error: {e}")
        return {
            "intent": "error",
            "field": "unknown",
            "action": "api_error",
            "values": {},
            "confidence": "low",
            "response_text": f"Something went wrong talking to Claude: {str(e)[:100]}",
        }
