"""
doit-fm AI engine.
Uses stdlib urllib for HTTP — no aiohttp required.
Calls any OpenAI-compatible provider + Anthropic.
"""
from __future__ import annotations
import asyncio, json, logging, re
from datetime import datetime
from typing import Optional

from doit_fm.config import AI_PROVIDERS, HTTP_TIMEOUT

logger = logging.getLogger("doit.ai")


def _system_prompt() -> str:
    now      = datetime.now().strftime("%A %B %d %Y, %I:%M %p")
    platform = __import__("platform").system()
    from doit_fm.tools import tool_list_for_prompt
    tools = tool_list_for_prompt()
    return f"""You are Doit — a friendly AI assistant that controls a real computer for the user.
Today: {now}. OS: {platform}.

PERSONALITY
• Warm, jargon-free. Many users are non-technical.
• Prefer doing over asking. Infer reasonable defaults.
• Only use "clarify" when truly ambiguous.
• Users may misspell or use casual language — always infer intent.

AVAILABLE TOOLS
{tools}

RESPONSE — output ONLY valid JSON, zero prose outside the JSON.

Single tool:
{{"tool": "tool_name", "args": {{}}, "description": "Plain English what I'm doing", "confirm": false}}

Dangerous tool (fs_delete, sys_kill, shell_exec) — MUST have confirm:true:
{{"tool": "tool_name", "args": {{}}, "description": "Plain English what I'm doing", "confirm": true}}

Need clarification:
{{"tool": "clarify", "args": {{"message": "Short friendly question"}}, "description": "Need info", "confirm": false}}

RULES
1. Only use tools from the list. Never invent tool names.
2. Dangerous tools (marked ⚠️) must always have "confirm": true.
3. Typos: "wether"=weather, "caclulate"=calc, "searh"=web_search, "shwo files"=fs_list, etc.
4. "show files"/"list"/"ls" → fs_list
5. "find"/"search" → fs_search
6. "how's my computer" → sys_monitor
7. "weather in X" → weather city=X
8. "calculate X" → calc expression=X
9. "remember X" → memory_store
10. "what do you remember" → memory_recall
11. "note about X" → note_create
12. "remind me in X minutes" → reminder_set minutes=X
13. "screenshot" → screenshot_take
14. "run X" or explicit shell → shell_exec confirm=true
15. "search web for X" → web_search
"""


class ConversationMemory:
    def __init__(self, max_turns=10):
        self._turns = []
        self._max   = max_turns * 2

    def add(self, role, content):
        self._turns.append({"role": role, "content": content})
        if len(self._turns) > self._max:
            self._turns = self._turns[-self._max:]

    def messages(self, user_msg):
        return list(self._turns) + [{"role": "user", "content": user_msg}]

    def clear(self):
        self._turns = []


class AIEngine:
    def __init__(self, config: dict):
        self.provider  = config.get("ai_provider", "openai")
        self.model     = config.get("ai_model", "gpt-4o-mini")
        self.api_key   = config.get("ai_api_key", "")
        self.base_url  = config.get("ai_base_url", "")
        self._memory   = ConversationMemory()
        self._available = True

    def clear_memory(self):
        self._memory.clear()

    def _headers(self):
        if self.provider == "anthropic":
            return {"x-api-key": self.api_key,
                    "anthropic-version": "2023-06-01",
                    "Content-Type": "application/json"}
        return {"Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"}

    def _endpoint(self):
        if self.provider == "anthropic":
            return f"{self.base_url}/messages"
        return f"{self.base_url}/chat/completions"

    def _payload(self, messages):
        sys = _system_prompt()
        if self.provider == "anthropic":
            return {"model": self.model, "max_tokens": 1024,
                    "system": sys, "messages": messages}
        full = [{"role": "system", "content": sys}] + messages
        p = {"model": self.model, "messages": full,
             "max_tokens": 1024, "temperature": 0.1}
        if self.provider == "openai":
            p["response_format"] = {"type": "json_object"}
        return p

    def _extract(self, data):
        if self.provider == "anthropic":
            for b in data.get("content", []):
                if isinstance(b, dict) and b.get("type") == "text":
                    return b["text"]
            raise ValueError("No text in Anthropic response")
        choices = data.get("choices", [])
        if not choices:
            raise ValueError("Empty choices in response")
        return choices[0]["message"]["content"]

    def _parse(self, raw: str) -> dict:
        text = raw.strip()
        text = re.sub(r"^```(?:json)?\s*\n?", "", text, flags=re.MULTILINE)
        text = re.sub(r"\n?```\s*$", "", text, flags=re.MULTILINE)
        text = text.strip()
        m = re.search(r"\{.*\}", text, re.DOTALL)
        if m: text = m.group()
        try:
            a = json.loads(text)
            if not isinstance(a, dict): raise ValueError("not a dict")
            if "tool" not in a:
                for alt in ("action", "command", "function"):
                    if alt in a:
                        a["tool"] = a.pop(alt); break
            if "tool" not in a and "steps" not in a:
                raise ValueError("no tool key")
            a.setdefault("args", {})
            a.setdefault("description", a.get("tool", ""))
            a.setdefault("confirm", False)
            return a
        except Exception as e:
            logger.warning("AI parse failed: %s | raw: %.200s", e, raw)
            return {"tool": "clarify",
                    "args": {"message": "I didn't quite understand — could you rephrase?"},
                    "description": "clarify", "confirm": False}

    def _http_call(self, endpoint, headers, payload):
        """Blocking HTTP POST using stdlib urllib."""
        import urllib.request, urllib.error
        data = json.dumps(payload).encode()
        req  = urllib.request.Request(endpoint, data=data, headers=headers)
        try:
            with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT) as r:
                return json.loads(r.read())
        except urllib.error.HTTPError as e:
            body = {}
            try: body = json.loads(e.read())
            except Exception: pass
            code = e.code
            desc = body.get("error", {}).get("message") or body.get("error") or str(e)
            if code == 401: raise _InvalidKey(str(desc))
            if code == 429: raise _Quota(str(desc))
            raise _AIError(f"HTTP {code}: {desc}")
        except Exception as e:
            raise _AIError(str(e))

    async def parse_intent(self, user_message: str) -> dict:
        messages = self._memory.messages(user_message)
        loop     = asyncio.get_event_loop()
        try:
            endpoint = self._endpoint()
            headers  = self._headers()
            payload  = self._payload(messages)
            data = await loop.run_in_executor(
                None, lambda: self._http_call(endpoint, headers, payload))
            text   = self._extract(data)
            action = self._parse(text)
            self._available = True
            self._memory.add("user", user_message)
            self._memory.add("assistant", json.dumps(action))
            return action
        except _InvalidKey as e:
            self._available = False
            return _err("Invalid AI API key. Run `doitfm init --reconfigure` to fix it.")
        except _Quota as e:
            return _err("AI rate limit exceeded — please wait a moment and try again.")
        except Exception as e:
            logger.error("AI call failed: %s", e)
            self._available = False
            return _err("Couldn't reach the AI right now. Check your internet and API key.")


class _AIError(Exception): pass
class _InvalidKey(_AIError): pass
class _Quota(_AIError): pass

def _err(msg):
    return {"tool": "error", "args": {"message": msg},
            "description": "error", "confirm": False}


async def validate_api_key(provider, model, api_key, base_url):
    """Quick live test. Returns (ok, message)."""
    engine = AIEngine({"ai_provider": provider, "ai_model": model,
                       "ai_api_key": api_key, "ai_base_url": base_url})
    loop = asyncio.get_event_loop()
    try:
        payload = engine._payload([
            {"role": "user",
             "content": 'Reply ONLY with: {"tool":"ok","args":{},"description":"ok","confirm":false}'}
        ])
        data = await loop.run_in_executor(
            None, lambda: engine._http_call(engine._endpoint(), engine._headers(), payload))
        text   = engine._extract(data)
        action = engine._parse(text)
        if action.get("tool") in ("ok", "clarify", "error", "fs_list"):
            return True, f"Connected: {provider}/{model}"
        return True, f"Connected (resp: {text[:60]})"
    except _InvalidKey:
        return False, "Invalid API key"
    except _Quota:
        return False, "Quota exceeded"
    except Exception as e:
        return False, str(e)
