import hashlib
import logging
from collections.abc import Awaitable, Callable, Coroutine
from contextlib import suppress
from datetime import UTC, datetime

from pydantic_ai import Agent
from pydantic_ai_backends import LocalBackend

from app.agent import run_agent_interaction
from app.agent.deps import Deps
from app.channels.whatsapp.ask_user import (
    make_ask_user_callback,
    resolve_pending_text_response,
)
from app.channels.whatsapp.streaming import MessageStreamer
from app.context import Context
from app.context.state import State
from app.settings import Contacts, WhatsAppContact

logger = logging.getLogger(__name__)

SendJson = Callable[[dict[str, object]], Awaitable[None]]
MessageHandler = Callable[[dict[str, object], SendJson], Coroutine[object, object, None]]


def _user_id_for_sender(sender: str) -> str:
    digest = hashlib.sha256(sender.encode("utf-8")).hexdigest()
    return f"wa_{digest[:16]}"


def _upsert_whatsapp_contact(
    ctx: Context,
    jid: str,
    authorized: bool,
    pending_message: str | None = None,
) -> None:
    try:
        number = jid.split("@")[0] if "@" in jid else jid
        last_seen = datetime.now(tz=UTC).isoformat()

        def update(contacts: Contacts) -> Contacts:
            entry = contacts.whatsapp.get(jid) or WhatsAppContact(jid=jid, authorized=authorized)
            entry.jid = jid
            entry.number = number
            entry.last_seen = last_seen
            if not authorized and pending_message is not None:
                entry.pending_messages.append(pending_message)
            return contacts.model_copy(update={"whatsapp": {**contacts.whatsapp, jid: entry}})

        ctx.contacts.set(update)
    except Exception:
        logger.exception("Failed to upsert whatsapp contact %s", jid)


async def _resolve_input(
    ctx: Context,
    payload: dict[str, object],
    user_id: str,
) -> str | None:
    config = ctx.settings.get()
    text = str(payload.get("text", "")).strip()
    media_path_raw = str(payload.get("media_path", "")).strip()
    if not media_path_raw:
        return text if text else None

    if not config.messages.accept_files:
        disabled_notice = (
            "[Usuário tentou enviar um arquivo,"
            " mas o recebimento de arquivos está desabilitado neste canal.]"
        )
        if text:
            return f"{disabled_notice}\n\nMensagem do usuário: {text}"
        return disabled_notice

    from pathlib import Path

    media_path = Path(media_path_raw)
    if not media_path.exists():
        return text if text else None

    data = media_path.read_bytes()
    filename = media_path.name

    workspace_dir = ctx.paths.user_workspace(user_id)
    all_dirs = [str(workspace_dir)]
    backend = LocalBackend(
        root_dir=workspace_dir,
        allowed_directories=all_dirs,
        enable_execute=False,
        ask_fallback="deny",
    )
    backend.write(f"uploads/{filename}", data)

    with suppress(Exception):
        media_path.unlink(missing_ok=True)

    message = f"[Arquivo recebido: {filename} ({len(data)} bytes) — salvo em uploads/{filename}]"
    if text:
        message += f"\n\nMensagem do usuário: {text}"

    return message


async def _process_message(
    ctx: Context,
    agent: Agent[Deps, str],
    user_input: str,
    user_id: str,
    sender: str,
    send_json: SendJson,
) -> None:
    streamer = MessageStreamer(send_json, sender, messages=ctx.messages.get)
    await streamer.start()

    try:
        await run_agent_interaction(
            ctx=ctx,
            agent=agent,
            user_input=user_input,
            user_id=user_id,
            event_stream_handler=streamer.event_handler,
            ask_user=make_ask_user_callback(send_json, sender),
            send_file=streamer.send_file,
        )
        await streamer.finalize()
    except Exception as error:
        logger.exception("Error processing WhatsApp message")
        await send_json(
            {
                "action": "send_text",
                "to": sender,
                "text": f"Desculpe, ocorreu um erro: {str(error)[:100]}",
            }
        )


def create_message_handler(ctx: Context, agent_state: State[Agent[Deps, str]]) -> MessageHandler:
    async def handle_message(payload: dict[str, object], send_json: SendJson) -> None:
        if str(payload.get("type", "")) != "message":
            return

        sender = str(payload.get("sender", "")).strip()
        if not sender:
            logger.warning("Mensagem WhatsApp recebida sem sender: %s", payload)
            return

        message_id = str(payload.get("message_id", "")).strip() or "(sem-id)"
        is_allowed = ctx.contacts.get().is_authorized_for_whatsapp(sender)
        logger.info(
            "[whatsapp] contato=%s autorizado=%s message_id=%s",
            sender,
            is_allowed,
            message_id,
        )

        user_id = _user_id_for_sender(sender)
        user_input = await _resolve_input(ctx, payload, user_id)
        if user_input is None:
            return

        _upsert_whatsapp_contact(
            ctx,
            sender,
            is_allowed,
            pending_message=user_input if not is_allowed else None,
        )

        if not is_allowed:
            logger.info("[whatsapp] contato não autorizado enfileirado: %s", sender)
            return

        text_input = str(payload.get("text", "")).strip()
        is_button_reply = bool(payload.get("is_button_reply", False))
        if text_input and resolve_pending_text_response(sender, text_input, is_button_reply):
            return

        await _process_message(ctx, agent_state.get(), user_input, user_id, sender, send_json)

    return handle_message
