"""iMessage channel via AppleScript bridge (macOS only).

Sends messages via `osascript` calls to Messages.app.
Receives messages by polling a SQLite database (~/Library/Messages/chat.db)
or via a lightweight AppleScript polling loop.

Requirements:
- macOS only
- Full Disk Access for the Python process (to read chat.db)
- Messages.app signed in with Apple ID
"""

from __future__ import annotations

import asyncio
import logging
import platform
import subprocess
from collections.abc import Callable
from typing import Any

from agntspace.channels.base import Channel, ChannelMessage

log = logging.getLogger(__name__)


class IMessageChannel(Channel):
    """iMessage channel via macOS Messages.app."""

    name = "imessage"

    def __init__(self) -> None:
        self._connected = False
        self._message_callback: Callable | None = None
        self._chat_id: str | None = None
        self._poll_task: asyncio.Task | None = None
        self._last_rowid: int = 0

    # ── lifecycle ────────────────────────────────────────────────────

    async def connect(self) -> None:
        if platform.system() != "Darwin":
            raise ConnectionError("iMessage is only available on macOS")

        # Verify we can run osascript
        try:
            result = subprocess.run(
                ["osascript", "-e", 'tell application "Messages" to get name'],
                capture_output=True, text=True, timeout=5,
            )
            if result.returncode != 0:
                raise ConnectionError(f"Cannot access Messages.app: {result.stderr.strip()}")
        except FileNotFoundError as e:
            raise ConnectionError("osascript not found — macOS required") from e

        self._connected = True
        log.info("iMessage connected via Messages.app")

        # Get latest rowid to avoid processing old messages
        self._last_rowid = await self._get_latest_rowid()

        # Start polling
        self._poll_task = asyncio.create_task(self._poll_loop())

    async def disconnect(self) -> None:
        self._connected = False
        if self._poll_task and not self._poll_task.done():
            self._poll_task.cancel()
            try:
                await self._poll_task
            except asyncio.CancelledError:
                pass
        log.info("iMessage disconnected")

    @property
    def is_connected(self) -> bool:
        return self._connected

    # ── sending ──────────────────────────────────────────────────────

    async def send_message(self, chat_id: str, text: str) -> None:
        """Send via AppleScript to Messages.app."""
        # Escape for AppleScript
        escaped = text.replace("\\", "\\\\").replace('"', '\\"')
        script = f'''
            tell application "Messages"
                set targetService to 1st account whose service type = iMessage
                set targetBuddy to participant "{chat_id}" of targetService
                send "{escaped}" to targetBuddy
            end tell
        '''
        try:
            await asyncio.get_event_loop().run_in_executor(
                None,
                lambda: subprocess.run(
                    ["osascript", "-e", script],
                    capture_output=True, text=True, timeout=10,
                ),
            )
        except Exception as e:
            log.warning("iMessage send failed: %s", e)

    async def send_notification(self, chat_id: str, text: str) -> None:
        await self.send_message(chat_id, f"🔔 {text}")

    # ── polling chat.db ──────────────────────────────────────────────

    async def _get_latest_rowid(self) -> int:
        """Get the latest message ROWID from chat.db."""
        try:
            import sqlite3
            db_path = _chat_db_path()
            conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
            cur = conn.execute("SELECT MAX(ROWID) FROM message")
            row = cur.fetchone()
            conn.close()
            return row[0] or 0 if row else 0
        except Exception as e:
            log.warning("Cannot read chat.db: %s", e)
            return 0

    async def _poll_loop(self) -> None:
        """Poll ~/Library/Messages/chat.db for new incoming messages."""
        while self._connected:
            try:
                new_messages = await asyncio.get_event_loop().run_in_executor(
                    None, self._read_new_messages,
                )
                for msg in new_messages:
                    await self._handle_incoming(msg)
                await asyncio.sleep(3)  # poll every 3 seconds
            except asyncio.CancelledError:
                break
            except Exception as e:
                log.warning("iMessage poll error: %s", e)
                await asyncio.sleep(10)

    def _read_new_messages(self) -> list[dict]:
        """Read new messages from chat.db (runs in thread pool)."""
        import sqlite3

        try:
            db_path = _chat_db_path()
            conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
            cur = conn.execute(
                """
                SELECT m.ROWID, m.text, m.is_from_me,
                       h.id as sender,
                       datetime(m.date/1000000000 + 978307200, 'unixepoch') as ts
                FROM message m
                LEFT JOIN handle h ON m.handle_id = h.ROWID
                WHERE m.ROWID > ? AND m.text IS NOT NULL AND m.is_from_me = 0
                ORDER BY m.ROWID ASC
                LIMIT 20
                """,
                (self._last_rowid,),
            )
            rows = cur.fetchall()
            conn.close()

            messages = []
            for row in rows:
                self._last_rowid = max(self._last_rowid, row[0])
                messages.append({
                    "rowid": row[0],
                    "text": row[1],
                    "sender": row[3] or "unknown",
                    "timestamp": row[4],
                })
            return messages
        except Exception as e:
            log.debug("chat.db read error: %s", e)
            return []

    async def _handle_incoming(self, msg: dict) -> None:
        sender = msg["sender"]
        self._chat_id = sender

        channel_msg = ChannelMessage(
            channel="imessage",
            sender=sender,
            text=msg["text"],
            metadata={"chat_id": sender, "sender_id": sender},
        )

        if self._message_callback:
            try:
                response = await self._message_callback(channel_msg)
                if response:
                    await self.send_message(sender, response)
            except Exception as e:
                log.error("iMessage handler error: %s", e)

    async def handle_webhook(self, payload: dict[str, Any]) -> str | None:
        """Not used — iMessage uses polling."""
        return None


def _chat_db_path() -> str:
    from pathlib import Path
    return str(Path.home() / "Library" / "Messages" / "chat.db")
