
import os
import json
import logging
import time
import platform
import requests
from datetime import datetime, timedelta
from typing import Optional
from collections import defaultdict
from livekit.agents import function_tool, RunContext
import psutil
from langchain_community.tools import DuckDuckGoSearchRun

# ==============================
# DATA FILES
# ==============================
APP_LOG_FILE = "app_usage_log.json"
MEMORY_FILE = "jarvis_memory.json"
CONTACTS_FILE = "contacts.json"

# ==============================
# APP TRACKING
# ==============================
def _load_app_log():
    if os.path.exists(APP_LOG_FILE):
        with open(APP_LOG_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return {}

def _save_app_log(data):
    with open(APP_LOG_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2)

@function_tool()
async def track_active_application(context: RunContext) -> str:
    import win32gui
    import win32process
    try:
        hwnd = win32gui.GetForegroundWindow()
        _, pid = win32process.GetWindowThreadProcessId(hwnd)
        app = psutil.Process(pid).name()

        log = _load_app_log()
        today = datetime.today().date().isoformat()
        log.setdefault(today, {})
        log[today][app] = log[today].get(app, 0) + 1
        _save_app_log(log)
        return f"Tracked usage for {app}"
    except Exception as e:
        return f"Tracking failed: {e}"

@function_tool()
async def weekly_app_usage_report(context: RunContext) -> str:
    log = _load_app_log()
    cutoff = datetime.today().date() - timedelta(days=7)
    summary = defaultdict(int)

    for day, apps in log.items():
        if datetime.fromisoformat(day).date() >= cutoff:
            for app, mins in apps.items():
                summary[app] += mins

    if not summary:
        return "No usage data available."

    report = ["📊 Weekly Application Usage:"]
    for app, mins in sorted(summary.items(), key=lambda x: -x[1]):
        report.append(f"{app}: {mins} min")
    return "\n".join(report)

# ==============================
# MEMORY
# ==============================
def _load_memory():
    return json.load(open(MEMORY_FILE)) if os.path.exists(MEMORY_FILE) else {}

def _save_memory(data):
    json.dump(data, open(MEMORY_FILE, "w"), indent=2)

@function_tool()
async def remember(context: RunContext, key: str, value: str) -> str:
    mem = _load_memory()
    mem[key.lower()] = value
    _save_memory(mem)
    return f"Saved: {key}"

@function_tool()
async def recall(context: RunContext, key: str) -> str:
    return _load_memory().get(key.lower(), "Not found")

# ==============================
# CONTACTS
# ==============================
def _load_contacts():
    if os.path.exists(CONTACTS_FILE):
        with open(CONTACTS_FILE, "r") as f:
            return json.load(f)
    return {}

def _save_contacts(contacts):
    with open(CONTACTS_FILE, "w") as f:
        json.dump(contacts, f, indent=2)

@function_tool()
async def save_contact(context: RunContext, name: str, phone: str, notes: str = "") -> str:
    """Saves a contact with their phone number."""
    try:
        contacts = _load_contacts()
        name_key = name.lower().strip()
        phone_clean = phone.strip().replace("+", "").replace("-", "").replace(" ", "")
        if not phone_clean.isdigit():
            return f"❌ Invalid phone number format. Use digits only (e.g., 919876543210)"
        
        if name_key in contacts:
            old_phone = contacts[name_key]["phone"]
            contacts[name_key] = {
                "name": name, "phone": phone_clean, "notes": notes, 
                "updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            }
            _save_contacts(contacts)
            return f"✅ Updated contact '{name}'\n📱 Old: {old_phone}\n📱 New: {phone_clean}"
        else:
            contacts[name_key] = {
                "name": name, "phone": phone_clean, "notes": notes, 
                "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            }
            _save_contacts(contacts)
            return f"✅ Saved contact '{name}'\n📱 Phone: {phone_clean}"
    except Exception as e:
        return f"❌ Failed to save contact: {e}"

@function_tool()
async def get_contact_phone(context: RunContext, name: str) -> str:
    """Retrieves a phone number for a contact by name."""
    try:
        contacts = _load_contacts()
        name_lower = name.lower().strip()
        
        if name_lower in contacts:
            return contacts[name_lower]["phone"]
        
        matches = [c for k, c in contacts.items() if name_lower in k or name_lower in c["name"].lower()]
        
        if len(matches) == 1:
            return matches[0]["phone"]
        elif len(matches) > 1:
            return "Found potential matches:\n" + "\n".join([f"{c['name']}: {c['phone']}" for c in matches])
        else:
            return f"❌ No contact found for '{name}'"
    except Exception as e:
        return f"❌ Failed to retrieve contact: {e}"

@function_tool()
async def list_all_contacts(context: RunContext) -> str:
    """Lists all saved contacts."""
    try:
        contacts = _load_contacts()
        if not contacts: return "📱 No contacts saved yet."
        
        sorted_contacts = sorted(contacts.values(), key=lambda x: x["name"].lower())
        result = [f"📱 Saved Contacts ({len(contacts)} total):"]
        for i, c in enumerate(sorted_contacts, 1):
            result.append(f"{i}. {c['name']}: {c['phone']} {f'({c.get(chr(34) + 'notes' + chr(34), '')})' if c.get('notes') else ''}")
        return "\n".join(result)
    except Exception as e:
        return f"❌ Failed to list contacts: {e}"

@function_tool()
async def delete_contact(context: RunContext, name: str) -> str:
    """Deletes a contact."""
    try:
        contacts = _load_contacts()
        name_lower = name.lower().strip()
        if name_lower in contacts:
            del contacts[name_lower]
            _save_contacts(contacts)
            return f"✅ Deleted contact '{name}'"
        return f"❌ Contact '{name}' not found"
    except Exception as e:
        return f"❌ Failed to delete: {e}"

@function_tool()
async def search_contacts(context: RunContext, query: str) -> str:
    """Searches contacts."""
    try:
        contacts = _load_contacts()
        q = query.lower().strip()
        matches = [c for c in contacts.values() if q in c["name"].lower() or q in c["phone"] or q in c.get("notes", "").lower()]
        if not matches: return f"❌ No matches for '{query}'"
        return "🔍 Matches:\n" + "\n".join([f"{c['name']}: {c['phone']}" for c in matches])
    except Exception as e:
        return f"❌ Search failed: {e}"

# ==============================
# SYSTEM & WEATHER
# ==============================
@function_tool()
async def get_weather(context: RunContext, city: str) -> str:
    try:
        return requests.get(f"https://wttr.in/{city}?format=3", timeout=5).text
    except:
        return "Weather unavailable"

@function_tool()
async def search_web(context: RunContext, query: str) -> str:
    return DuckDuckGoSearchRun().run(query)

@function_tool()
async def current_time(context: RunContext) -> str:
    return datetime.now().strftime("%H:%M:%S")

@function_tool()
async def system_status(context: RunContext) -> str:
    return platform.platform()

@function_tool()
async def system_uptime(context: RunContext) -> str:
    return str(timedelta(seconds=int(time.time() - psutil.boot_time())))

@function_tool()
async def battery_status(context: RunContext) -> str:
    b = psutil.sensors_battery()
    return f"{b.percent}% {'Charging' if b.power_plugged else 'Not Charging'}"

@function_tool()
async def check_internet(context: RunContext) -> str:
    import socket
    import http.client as httplib
    
    # Method 1: DNS
    targets = [("8.8.8.8", 53), ("1.1.1.1", 53)]
    for host, port in targets:
        try:
            socket.create_connection((host, port), timeout=2)
            return "✅ Internet Connection: Active (DNS verifying)."
        except: pass
            
    # Method 2: HTTP
    try:
        conn = httplib.HTTPSConnection("www.google.com", timeout=3)
        conn.request("HEAD", "/")
        conn.close()
        return "✅ Internet Connection: Active (HTTP verifying)."
    except: pass
    
    return "⚠️ Limited Connectivity Detected."

@function_tool()
async def system_health_report(context: RunContext) -> str:
    return f"CPU: {psutil.cpu_percent()}% | RAM: {psutil.virtual_memory().percent}%"
