from __future__ import annotations

import re

from asgiref.sync import sync_to_async

from core.gateway.commands import (
    GatewayCommandContext,
    GatewayCommandRoute,
    dispatch_gateway_command,
)
from core.models import (
    DerivedTask,
    Person,
    TaskProject,
)
from core.tasks.engine import create_task_record_and_sync, mark_task_completed_and_sync

TASK_COMMAND_MATCH_RE = re.compile(r"^\s*(?:\.tasks\b|\.l\b|\.list\b)", re.IGNORECASE)


def gateway_help_lines() -> list[str]:
    return [
        "Gateway commands:",
        "  .contacts — list contacts",
        "  .whoami — show current user",
        "  .help — show this help",
        "Task commands:",
        "  .l — shortcut for open task list",
        "  .tasks list [status] [limit] — list tasks",
        "  .tasks add <project> :: <title> — create task in project",
        "  .tasks show #<ref> — show task details",
        "  .tasks complete #<ref> — mark task complete",
        "  .tasks undo #<ref> — remove task",
    ]


def _parse_task_create(rest: str) -> tuple[str, str]:
    text = str(rest or "").strip()
    if not text.lower().startswith("add "):
        return "", ""
    payload = text[4:].strip()
    if "::" in payload:
        project_name, title = payload.split("::", 1)
        return str(project_name or "").strip(), str(title or "").strip()
    return "", ""


async def handle_tasks_command(
    user,
    body,
    emit,
    *,
    service: str = "",
    channel_identifier: str = "",
    sender_identifier: str = "",
):
    command = str(body or "").strip()
    lower_command = command.lower()
    if not TASK_COMMAND_MATCH_RE.match(command):
        return False
    if lower_command.startswith(".tasks"):
        rest = command[len(".tasks") :].strip()
    elif lower_command.startswith(".list") or lower_command.startswith(".l"):
        rest = "list"
    else:
        rest = "list " + command[2:].strip() if len(command) > 2 else "list"

    if rest.startswith("list"):
        parts = rest.split()
        status_filter = parts[1] if len(parts) > 1 else "open"
        limit = int(parts[2]) if len(parts) > 2 and parts[2].isdigit() else 10
        tasks = await sync_to_async(list)(
            DerivedTask.objects.filter(
                user=user, status_snapshot=status_filter
            ).order_by("-id")[:limit]
        )
        if not tasks:
            emit(f"no {status_filter} tasks")
        else:
            for task in tasks:
                emit(f"#{task.reference_code} [{task.status_snapshot}] {task.title}")
        return True

    project_name, title = _parse_task_create(rest)
    if project_name or rest.startswith("add "):
        if not project_name or not title:
            emit("usage: .tasks add <project> :: <title>")
            return True
        project = await sync_to_async(
            lambda: TaskProject.objects.filter(user=user, name__iexact=project_name)
            .order_by("name")
            .first()
        )()
        if project is None:
            emit(f"project_not_found:{project_name}")
            return True
        task, _event = await create_task_record_and_sync(
            user=user,
            project=project,
            title=title,
            source_service=str(service or "web").strip().lower() or "web",
            source_channel=str(channel_identifier or "").strip(),
            actor_identifier=str(sender_identifier or "").strip(),
            immutable_payload={
                "origin": "gateway.tasks.add",
                "channel_service": str(service or "").strip().lower(),
                "channel_identifier": str(channel_identifier or "").strip(),
            },
            event_payload={
                "command": ".tasks add",
                "via": "gateway_builtin",
            },
        )
        emit(f"created #{task.reference_code} [{project.name}] {task.title}")
        return True

    if rest.startswith("show "):
        ref = rest[len("show ") :].strip().lstrip("#")
        try:
            task = await sync_to_async(DerivedTask.objects.get)(
                user=user, reference_code=ref
            )
            emit(f"#{task.reference_code} {task.title}")
            emit(f"status: {task.status_snapshot}")
        except DerivedTask.DoesNotExist:
            emit(f"task_not_found:#{ref}")
        return True

    if rest.startswith("complete "):
        ref = rest[len("complete ") :].strip().lstrip("#")
        try:
            task = await sync_to_async(DerivedTask.objects.select_related("project").get)(
                user=user, reference_code=ref
            )
            await mark_task_completed_and_sync(
                task=task,
                actor_identifier=str(sender_identifier or "").strip(),
                payload={
                    "marker": ref,
                    "command": ".tasks complete",
                    "via": "gateway_builtin",
                },
            )
            emit(f"completed #{ref}")
        except DerivedTask.DoesNotExist:
            emit(f"task_not_found:#{ref}")
        return True

    if rest.startswith("undo "):
        ref = rest[len("undo ") :].strip().lstrip("#")
        try:
            task = await sync_to_async(DerivedTask.objects.get)(
                user=user, reference_code=ref
            )
            await sync_to_async(task.delete)()
            emit(f"removed #{ref}")
        except DerivedTask.DoesNotExist:
            emit(f"task_not_found:#{ref}")
        return True

    emit(
        "tasks: .l | .tasks list [status] [limit] | "
        ".tasks add <project> :: <title> | "
        ".tasks show #<ref> | "
        ".tasks complete #<ref> | "
        ".tasks undo #<ref>"
    )
    return True


async def dispatch_builtin_gateway_command(
    *,
    user,
    command_text: str,
    service: str,
    channel_identifier: str,
    sender_identifier: str,
    source_message,
    message_meta: dict,
    payload: dict,
    emit,
) -> bool:
    text = str(command_text or "").strip()

    async def _contacts_handler(_ctx, out):
        persons = await sync_to_async(list)(Person.objects.filter(user=user).order_by("name"))
        if not persons:
            out("No contacts found.")
            return True
        out("Contacts: " + ", ".join([p.name for p in persons]))
        return True

    async def _help_handler(_ctx, out):
        for line in gateway_help_lines():
            out(line)
        return True

    async def _whoami_handler(_ctx, out):
        out(str(user.__dict__))
        return True

    async def _tasks_handler(_ctx, out):
        return await handle_tasks_command(
            user,
            text,
            out,
            service=service,
            channel_identifier=channel_identifier,
            sender_identifier=sender_identifier,
        )

    routes = [
        GatewayCommandRoute(
            name="contacts",
            scope_key="gateway.contacts",
            matcher=lambda value: str(value or "").strip().lower() == ".contacts",
            handler=_contacts_handler,
        ),
        GatewayCommandRoute(
            name="help",
            scope_key="gateway.help",
            matcher=lambda value: str(value or "").strip().lower() == ".help",
            handler=_help_handler,
        ),
        GatewayCommandRoute(
            name="whoami",
            scope_key="gateway.whoami",
            matcher=lambda value: str(value or "").strip().lower() == ".whoami",
            handler=_whoami_handler,
        ),
        GatewayCommandRoute(
            name="tasks",
            scope_key="gateway.tasks",
            matcher=lambda value: bool(TASK_COMMAND_MATCH_RE.match(str(value or ""))),
            handler=_tasks_handler,
        ),
    ]

    handled = await dispatch_gateway_command(
        context=GatewayCommandContext(
            user=user,
            source_message=source_message,
            service=str(service or "xmpp"),
            channel_identifier=str(channel_identifier or ""),
            sender_identifier=str(sender_identifier or ""),
            message_text=text,
            message_meta=dict(message_meta or {}),
            payload=dict(payload or {}),
        ),
        routes=routes,
        emit=emit,
    )
    if not handled and text.startswith("."):
        emit("No such command")
    return handled
