import uuid
from datetime import datetime, timedelta
from pathlib import Path

import requests

from backend.browser_runtime import (
    _browser_click,
    _browser_eval,
    _browser_get_text,
    _browser_navigate,
    _browser_screenshot,
    _browser_type,
)
from backend.calendar_runtime import (
    _google_create_calendar_event,
    _google_delete_calendar_event,
    _google_read_calendar,
    _google_update_calendar_event,
    _outlook_create_calendar_event,
    _outlook_delete_calendar_event,
    _outlook_get_tasks,
    _outlook_read_calendar,
    _outlook_update_calendar_event,
)
from backend.config import (
    OUTPUT_DIR,
    _load_schedules,
    _load_settings,
    _normalize_tool_enabled_map,
    _normalize_tool_name,
    _save_schedules,
    _to_bool,
)
from backend.drive_runtime import (
    _google_drive_list,
    _google_drive_read,
    _google_drive_search,
    _google_drive_upload,
    _onedrive_list,
    _onedrive_read,
    _onedrive_search,
    _onedrive_upload,
)
from backend.mail_actions_runtime import (
    _gmail_reply_email,
    _gmail_save_draft_email,
    _gmail_send_email,
    _outlook_reply_email,
    _outlook_save_draft_email,
    _outlook_send_email,
)
from backend.mail_mutation_runtime import (
    _gmail_delete_email,
    _gmail_update_email,
    _outlook_delete_email,
    _outlook_update_email,
)
from backend.mail_read_runtime import (
    _gmail_get_email,
    _gmail_get_inbox,
    _gmail_get_thread,
    _outlook_get_email,
    _outlook_get_inbox,
    _outlook_get_thread,
)
from backend.mail_search_runtime import _gmail_search_emails, _outlook_search_emails
from backend.mailbox_ops_runtime import (
    _gmail_bulk_action,
    _gmail_get_attachments,
    _gmail_manage_labels,
    _outlook_bulk_action,
    _outlook_get_attachments,
    _outlook_move_email,
)
from backend.mcp_runtime import _mcp_call, _mcp_list_tools
from backend.scheduling_runtime import _find_free_slots
from backend.tool_general_runtime import (
    _extract_source,
    _fetch_url,
    _fetch_webhook,
    _http_request,
    _package_web_app,
    _preview_web_app,
    _read_workspace_file,
    _run_python,
    _run_shell,
    _search_workspace,
    _web_search,
    _write_web_files,
)
from backend.tool_registry_runtime import (
    _create_or_update_skill,
    _dynamic_tools,
    _execute_dynamic_tool,
)


def _slack_post_message(channel: str, text: str, thread_ts: str = "") -> str:
    channel = str(channel or "").strip()
    text = str(text or "").strip()
    thread_ts = str(thread_ts or "").strip()
    if not channel:
        return "Slack channel is required."
    if not text:
        return "Slack text is required."

    settings = _load_settings()
    token = str(settings.get("slack_bot_token", "") or "").strip()
    if not token:
        return "Slack bot token is not configured. Go to Settings and connect Slack."

    payload = {"channel": channel, "text": text}
    if thread_ts:
        payload["thread_ts"] = thread_ts

    try:
        resp = requests.post(
            "https://slack.com/api/chat.postMessage",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json; charset=utf-8",
            },
            json=payload,
            timeout=20,
        )
        data = resp.json()
    except Exception as e:
        return f"Slack post failed: {e}"

    if not resp.ok:
        return f"Slack API HTTP error: {resp.status_code}"
    if not data.get("ok"):
        return f"Slack API error: {data.get('error', 'unknown_error')}"

    sent_ts = str(data.get("ts", "") or "").strip()
    if sent_ts:
        return f"Slack message sent to {channel} (ts: {sent_ts})"
    return f"Slack message sent to {channel}"


def _dispatch_general_tools(tool_name: str, args: dict) -> str | None:
    if tool_name == "web_search":
        return _web_search(args.get("query", ""), max_results=6)
    if tool_name == "fetch_url":
        return _fetch_url(args.get("url", ""), max_chars=12000)
    if tool_name == "extract_source":
        return _extract_source(args.get("source", ""), max_chars=int(args.get("max_chars", 24000)))
    if tool_name == "run_python":
        return _run_python(args.get("code", ""))
    if tool_name == "write_file":
        fname = Path(args.get("filename", "output.txt")).name
        dest = OUTPUT_DIR / fname
        dest.write_text(args.get("content", ""), encoding="utf-8")
        return f"File saved: output/{fname}"
    if tool_name == "write_web_files":
        return _write_web_files(
            project=args.get("project", ""),
            files=args.get("files"),
            overwrite=bool(args.get("overwrite", True)),
        )
    if tool_name == "preview_web_app":
        return _preview_web_app(
            project=args.get("project", ""),
            html_path=str(args.get("html_path", "index.html") or "index.html"),
            selector=str(args.get("selector", "body") or "body"),
            max_chars=int(args.get("max_chars", 2000)),
        )
    if tool_name == "package_web_app":
        return _package_web_app(
            project=args.get("project", ""),
            zip_name=str(args.get("zip_name", "") or ""),
        )
    if tool_name == "read_file":
        fname = Path(args.get("filename", "")).name
        src = OUTPUT_DIR / fname
        if not src.exists():
            return f"File not found: {fname}"
        return src.read_text(encoding="utf-8")
    if tool_name == "list_files":
        OUTPUT_DIR.mkdir(exist_ok=True)
        files = sorted(OUTPUT_DIR.iterdir())
        if not files:
            return "output/ is empty."
        count = int(args.get("count", 250))
        if count < 1:
            count = 1
        lines = []
        for path in files[:count]:
            size = path.stat().st_size
            lines.append(f"{path.name}  ({size:,} bytes)")
        omitted = max(0, len(files) - len(lines))
        if omitted:
            lines.append(f"... ({omitted} more files omitted)")
        return "Files in output/:\n" + "\n".join(lines)
    if tool_name == "run_shell":
        return _run_shell(args.get("command", ""), timeout=int(args.get("timeout", 30)))
    if tool_name == "http_request":
        return _http_request(
            method=args.get("method", "GET"),
            url=args.get("url", ""),
            headers=args.get("headers"),
            body=args.get("body"),
            timeout=int(args.get("timeout", 20)),
        )
    if tool_name == "fetch_webhook":
        return _fetch_webhook(webhook_name=args.get("webhook_name", ""), params=args.get("params"))
    if tool_name == "read_workspace_file":
        return _read_workspace_file(args.get("path", ""))
    if tool_name == "search_workspace":
        return _search_workspace(
            pattern=args.get("pattern", ""),
            glob=args.get("glob", "**/*"),
            max_results=int(args.get("max_results", 250)),
            regex=bool(args.get("regex", False)),
            mode=str(args.get("mode", "auto") or "auto"),
        )
    if tool_name in {"describe_image", "analyze_image", "image_describe"}:
        from backend.tool_general_runtime import _describe_image

        return _describe_image(
            source=str(args.get("source") or args.get("path") or args.get("url") or args.get("image") or ""),
            prompt=str(args.get("prompt") or args.get("question") or ""),
            model=str(args.get("model") or ""),
        )
    if tool_name == "recall_memory":
        from backend.memory import _recall_memory_deep

        return _recall_memory_deep(session_id=args.get("session_id", ""), query=args.get("query", ""))
    return None


def _dispatch_outlook_tools(tool_name: str, args: dict) -> str | None:
    if tool_name == "outlook_get_inbox":
        today_only = _to_bool(args.get("today_only", False))
        since = str(args.get("since", "") or "")
        until = str(args.get("until", "") or "")
        if not today_only and not since and not until:
            all_str = " ".join(str(v) for v in args.values()).lower()
            if any(keyword in all_str for keyword in ["today", "vandaag", "heute", "aujourd"]):
                today_only = True
        return _outlook_get_inbox(
            count=int(args.get("count", 250)),
            unread_only=_to_bool(args.get("unread_only", False)),
            folder=args.get("folder", "Inbox"),
            mailbox=args.get("mailbox", "ALL"),
            today_only=today_only,
            since=since,
            until=until,
            body_length=int(args.get("body_length", 300)),
        )
    if tool_name == "outlook_send_email":
        return _outlook_send_email(
            to=args.get("to", ""),
            subject=args.get("subject", ""),
            body=args.get("body", ""),
            cc=args.get("cc", ""),
            attachments=args.get("attachments", ""),
        )
    if tool_name == "outlook_update_email":
        return _outlook_update_email(
            message_id=args.get("message_id", ""),
            subject=args.get("subject", ""),
            body=args.get("body", ""),
            to=args.get("to", ""),
            cc=args.get("cc", ""),
        )
    if tool_name == "outlook_delete_email":
        return _outlook_delete_email(message_id=args.get("message_id", ""))
    if tool_name == "outlook_reply_email":
        return _outlook_reply_email(message_id=args.get("message_id", ""), body=args.get("body", ""))
    if tool_name == "outlook_save_draft_email":
        return _outlook_save_draft_email(message_id=args.get("message_id", ""), body=args.get("body", ""))
    if tool_name == "outlook_read_calendar":
        return _outlook_read_calendar(
            days_ahead=int(args.get("days_ahead", 7)),
            since=str(args.get("since", "") or ""),
            until=str(args.get("until", "") or ""),
        )
    if tool_name == "outlook_get_tasks":
        return _outlook_get_tasks(count=int(args.get("count", 20)), include_completed=_to_bool(args.get("include_completed", False)))
    if tool_name == "outlook_create_calendar_event":
        return _outlook_create_calendar_event(
            summary=args.get("summary", ""),
            start=args.get("start", ""),
            end=args.get("end", ""),
            description=args.get("description", ""),
            location=args.get("location", ""),
            attendees=args.get("attendees", ""),
        )
    if tool_name == "outlook_update_calendar_event":
        return _outlook_update_calendar_event(
            event_id=args.get("event_id", ""),
            summary=args.get("summary", ""),
            start=args.get("start", ""),
            end=args.get("end", ""),
            description=args.get("description", ""),
            location=args.get("location", ""),
        )
    if tool_name == "outlook_delete_calendar_event":
        return _outlook_delete_calendar_event(event_id=args.get("event_id", ""))
    if tool_name == "outlook_search_emails":
        return _outlook_search_emails(
            query=args.get("query", ""),
            count=int(args.get("count", 250)),
            mailbox=args.get("mailbox", "ALL"),
            folder=args.get("folder", "Inbox"),
            today_only=_to_bool(args.get("today_only", False)),
            since=str(args.get("since", "") or ""),
            until=str(args.get("until", "") or ""),
        )
    if tool_name == "outlook_get_thread":
        return _outlook_get_thread(
            conversation_id=args.get("conversation_id", args.get("thread_id", "")),
            body_length=int(args.get("body_length", 500)),
            max_messages=int(args.get("max_messages", 10)),
        )
    if tool_name == "outlook_get_email":
        return _outlook_get_email(
            message_id=args.get("message_id", ""),
            include_attachments=_to_bool(args.get("include_attachments", False)),
            max_attachments=int(args.get("max_attachments", 4)),
        )
    if tool_name == "outlook_move_email":
        return _outlook_move_email(message_id=args.get("message_id", ""), folder=args.get("folder", ""))
    if tool_name == "outlook_bulk_action":
        return _outlook_bulk_action(action=args.get("action", ""), query=args.get("query", ""), max_count=int(args.get("max_count", 50)))
    if tool_name == "outlook_get_attachments":
        return _outlook_get_attachments(message_id=args.get("message_id", ""), download=_to_bool(args.get("download", False)))
    return None


def _dispatch_gmail_tools(tool_name: str, args: dict) -> str | None:
    if tool_name == "gmail_get_inbox":
        return _gmail_get_inbox(
            count=int(args.get("count", 20)),
            unread_only=_to_bool(args.get("unread_only", False)),
            today_only=_to_bool(args.get("today_only", False)),
            since=str(args.get("since", "") or ""),
            until=str(args.get("until", "") or ""),
            folder=args.get("folder", "Inbox"),
            body_length=int(args.get("body_length", 300)),
        )
    if tool_name == "gmail_send_email":
        return _gmail_send_email(
            to=args.get("to", ""),
            subject=args.get("subject", ""),
            body=args.get("body", ""),
            cc=args.get("cc", ""),
            attachments=args.get("attachments", ""),
        )
    if tool_name == "gmail_update_email":
        return _gmail_update_email(
            message_id=args.get("message_id", ""),
            subject=args.get("subject", ""),
            body=args.get("body", ""),
            to=args.get("to", ""),
            cc=args.get("cc", ""),
        )
    if tool_name == "gmail_delete_email":
        return _gmail_delete_email(message_id=args.get("message_id", ""))
    if tool_name == "gmail_reply_email":
        return _gmail_reply_email(message_id=args.get("message_id", ""), body=args.get("body", ""))
    if tool_name == "gmail_save_draft_email":
        return _gmail_save_draft_email(message_id=args.get("message_id", ""), body=args.get("body", ""))
    if tool_name == "gmail_search_emails":
        return _gmail_search_emails(query=args.get("query", ""), count=int(args.get("count", 20)), folder=args.get("folder", "Inbox"))
    if tool_name == "gmail_get_thread":
        return _gmail_get_thread(thread_id=args.get("thread_id", ""), body_length=int(args.get("body_length", 500)), max_messages=int(args.get("max_messages", 10)))
    if tool_name == "gmail_get_email":
        return _gmail_get_email(
            message_id=args.get("message_id", ""),
            include_attachments=_to_bool(args.get("include_attachments", False)),
            max_attachments=int(args.get("max_attachments", 4)),
        )
    if tool_name == "gmail_manage_labels":
        return _gmail_manage_labels(action=args.get("action", "list"), label=args.get("label", ""), message_ids=args.get("message_ids", ""))
    if tool_name == "gmail_bulk_action":
        return _gmail_bulk_action(action=args.get("action", ""), query=args.get("query", ""), max_count=int(args.get("max_count", 50)))
    if tool_name == "gmail_get_attachments":
        return _gmail_get_attachments(message_id=args.get("message_id", ""), download=_to_bool(args.get("download", False)))
    return None


def _dispatch_calendar_and_storage_tools(tool_name: str, args: dict) -> str | None:
    if tool_name == "google_read_calendar":
        return _google_read_calendar(days_ahead=int(args.get("days_ahead", 7)), since=str(args.get("since", "") or ""), until=str(args.get("until", "") or ""))
    if tool_name == "google_create_calendar_event":
        return _google_create_calendar_event(
            summary=args.get("summary", ""),
            start=args.get("start", ""),
            end=args.get("end", ""),
            description=args.get("description", ""),
            location=args.get("location", ""),
            attendees=args.get("attendees", ""),
        )
    if tool_name == "google_update_calendar_event":
        return _google_update_calendar_event(
            event_id=args.get("event_id", ""),
            summary=args.get("summary", ""),
            start=args.get("start", ""),
            end=args.get("end", ""),
            description=args.get("description", ""),
            location=args.get("location", ""),
        )
    if tool_name == "google_delete_calendar_event":
        return _google_delete_calendar_event(event_id=args.get("event_id", ""))
    if tool_name == "google_drive_list":
        return _google_drive_list(folder_id=args.get("folder_id", "root"), count=int(args.get("count", 30)))
    if tool_name == "google_drive_search":
        return _google_drive_search(query=args.get("query", ""), count=int(args.get("count", 20)))
    if tool_name == "google_drive_read":
        return _google_drive_read(file_id=args.get("file_id", ""))
    if tool_name == "google_drive_upload":
        return _google_drive_upload(
            name=args.get("name", ""),
            content=args.get("content", ""),
            folder_id=args.get("folder_id", "root"),
            mime_type=args.get("mime_type", "text/plain"),
        )
    if tool_name == "onedrive_list":
        return _onedrive_list(folder_path=args.get("folder_path", ""), count=int(args.get("count", 30)))
    if tool_name == "onedrive_search":
        return _onedrive_search(query=args.get("query", ""), count=int(args.get("count", 20)))
    if tool_name == "onedrive_read":
        return _onedrive_read(file_id=args.get("file_id", ""))
    if tool_name == "onedrive_upload":
        return _onedrive_upload(name=args.get("name", ""), content=args.get("content", ""), folder_path=args.get("folder_path", ""))
    if tool_name == "find_free_slots":
        return _find_free_slots(
            date=args.get("date", ""),
            days=int(args.get("days", 1)),
            duration_minutes=int(args.get("duration_minutes", 30)),
            working_start=int(args.get("working_start", 9)),
            working_end=int(args.get("working_end", 17)),
        )
    return None


def _dispatch_browser_and_admin_tools(tool_name: str, args: dict) -> str | None:
    if tool_name == "slack_post_message":
        return _slack_post_message(channel=args.get("channel", ""), text=args.get("text", ""), thread_ts=args.get("thread_ts", ""))
    if tool_name == "whatsapp_send_message":
        from backend.integrations import _wa_send_tool

        return _wa_send_tool(recipient=args.get("recipient", ""), text=args.get("text", ""))
    if tool_name == "browser_navigate":
        return _browser_navigate(url=args.get("url", ""), wait_until=args.get("wait_until", "domcontentloaded"))
    if tool_name == "browser_click":
        return _browser_click(selector=args.get("selector", ""))
    if tool_name == "browser_type":
        return _browser_type(selector=args.get("selector", ""), text=args.get("text", ""), press_enter=_to_bool(args.get("press_enter", False)))
    if tool_name == "browser_screenshot":
        return _browser_screenshot()
    if tool_name == "browser_get_text":
        return _browser_get_text(selector=args.get("selector", "body"), max_chars=int(args.get("max_chars", 12000)))
    if tool_name == "browser_eval":
        return _browser_eval(js_code=args.get("js_code", ""))
    if tool_name == "mcp_call":
        return _mcp_call(server=args.get("server", ""), tool=args.get("tool", ""), arguments=args.get("arguments"))
    if tool_name == "mcp_list_tools":
        return _mcp_list_tools(server=args.get("server", ""))
    if tool_name == "create_tool":
        return _create_or_update_skill(
            raw_name=args.get("name", ""),
            description=args.get("description", ""),
            args_schema=args.get("args_schema", "{}"),
            code=args.get("code", ""),
            overwrite=str(args.get("overwrite", "false")).lower() in ("true", "1", "yes"),
            approved=str(args.get("approved", "false")).lower() in ("true", "1", "yes"),
            require_existing=False,
            op_label="create_skill",
        )
    if tool_name == "update_tool":
        return _create_or_update_skill(
            raw_name=args.get("name", ""),
            description=args.get("description", ""),
            args_schema=args.get("args_schema", ""),
            code=args.get("code", ""),
            overwrite=True,
            approved=str(args.get("approved", "false")).lower() in ("true", "1", "yes"),
            require_existing=True,
            op_label="update_skill",
        )
    return None


def _execute_tool(tool_name: str, args: dict) -> str:
    """Dispatcher for all agent tools."""
    try:
        tool_name = _normalize_tool_name(tool_name)
        tool_map = _normalize_tool_enabled_map(_load_settings().get("tool_enabled_map"))
        if not bool(tool_map.get(tool_name, True)):
            return f"TOOL_DISABLED: Skill '{tool_name}' is disabled in Settings > Skills."

        for handler in (
            _dispatch_general_tools,
            _dispatch_outlook_tools,
            _dispatch_gmail_tools,
            _dispatch_calendar_and_storage_tools,
            _dispatch_browser_and_admin_tools,
        ):
            result = handler(tool_name, args)
            if result is not None:
                return result

        if tool_name == "schedule_goal":
            goal = (args.get("goal") or "").strip()
            delay_minutes = int(args.get("delay_minutes", 5))
            if not goal:
                return "schedule_goal: 'goal' is required."
            if delay_minutes < 1:
                delay_minutes = 1
            entry = {
                "id": str(uuid.uuid4())[:8],
                "goal": goal,
                "interval_seconds": 0,
                "interval_label": "once",
                "next_run": (datetime.now() + timedelta(minutes=delay_minutes)).isoformat(),
                "created_at": datetime.now().isoformat(),
                "source": "agent",
            }
            schedules = _load_schedules()
            schedules.append(entry)
            _save_schedules(schedules)
            return f"Scheduled '{goal}' to run in {delay_minutes} minute(s) (id: {entry['id']})"
        if tool_name in _dynamic_tools:
            return _execute_dynamic_tool(tool_name, args)
        return f"Unknown skill: {tool_name}"
    except Exception as e:
        return f"Skill error ({tool_name}): {e}"