"""Parse Planka webhook payloads into ServiceEvent objects."""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any

from notify_bridge_core.models.events import EventType, ServiceEvent
from notify_bridge_core.providers.base import ServiceProviderType

from .models import (
    PlankaAttachment,
    PlankaBoard,
    PlankaCard,
    PlankaComment,
    PlankaLabel,
    PlankaList,
    PlankaTask,
    PlankaUser,
)

_LOGGER = logging.getLogger(__name__)

# Map Planka event types to our EventType.
# cardUpdate is refined by beforeUpdate/afterUpdate.
_PLANKA_EVENT_MAP: dict[str, EventType | None] = {
    "cardCreate": EventType.CARD_CREATED,
    "cardUpdate": None,  # refined: CARD_MOVED, CARD_UPDATED
    "cardDelete": EventType.CARD_DELETED,
    "commentCreate": EventType.CARD_COMMENTED,
    "commentUpdate": EventType.COMMENT_UPDATED,
    "boardCreate": EventType.BOARD_CREATED,
    "boardUpdate": EventType.BOARD_UPDATED,
    "boardDelete": EventType.BOARD_DELETED,
    "listCreate": EventType.LIST_CREATED,
    "listUpdate": EventType.LIST_UPDATED,
    "listDelete": EventType.LIST_DELETED,
    "attachmentCreate": EventType.ATTACHMENT_CREATED,
    "cardMembershipCreate": EventType.CARD_LABEL_ADDED,
    "taskUpdate": None,  # refined: TASK_COMPLETED only
}


def parse_webhook(
    event_type: str,
    payload: dict[str, Any],
    provider_name: str,
    base_url: str = "",
) -> ServiceEvent | None:
    """Parse a Planka webhook payload into a ServiceEvent.

    Args:
        event_type: The event type string from the webhook payload.
        payload: Parsed JSON body of the webhook.
        provider_name: Display name of the ServiceProvider instance.
        base_url: Base URL for building card/board links.

    Returns:
        A ServiceEvent, or None if the event is not tracked.
    """
    if event_type not in _PLANKA_EVENT_MAP:
        _LOGGER.debug("Ignoring untracked Planka event: %s", event_type)
        return None

    resolved_type = _resolve_event_type(event_type, payload)
    if resolved_type is None:
        _LOGGER.debug("Ignoring Planka event %s (not mapped)", event_type)
        return None

    user_data = payload.get("user", {})
    user = PlankaUser.from_payload(user_data) if user_data else None

    extra = _build_extra(event_type, resolved_type, payload, user, base_url)

    # Determine collection (board) info
    board_id = extra.get("board_id", "")
    board_name = extra.get("board_name", "")

    return ServiceEvent(
        event_type=resolved_type,
        provider_type=ServiceProviderType.PLANKA,
        provider_name=provider_name,
        collection_id=board_id,
        collection_name=board_name,
        timestamp=datetime.now(timezone.utc),
        extra=extra,
    )


def _resolve_event_type(
    event_type: str, payload: dict[str, Any],
) -> EventType | None:
    """Determine the EventType from event string + payload details."""
    direct = _PLANKA_EVENT_MAP.get(event_type)
    if direct is not None:
        return direct

    if event_type == "cardUpdate":
        return _resolve_card_update(payload)

    if event_type == "taskUpdate":
        return _resolve_task_update(payload)

    return None


def _resolve_card_update(payload: dict[str, Any]) -> EventType | None:
    """Determine if a cardUpdate is a move, completion, or general update."""
    before = payload.get("beforeUpdate", {})
    after = payload.get("afterUpdate", payload.get("item", {}))

    # List change = card moved
    old_list_id = before.get("listId")
    new_list_id = after.get("listId")
    if old_list_id and new_list_id and old_list_id != new_list_id:
        return EventType.CARD_MOVED

    return EventType.CARD_UPDATED


def _resolve_task_update(payload: dict[str, Any]) -> EventType | None:
    """Only emit TASK_COMPLETED when a task becomes completed."""
    item = payload.get("item", {})
    before = payload.get("beforeUpdate", {})

    if item.get("isCompleted") and not before.get("isCompleted", True):
        return EventType.TASK_COMPLETED

    return None


def _build_extra(
    event_type: str,
    resolved_type: EventType,
    payload: dict[str, Any],
    user: PlankaUser | None,
    base_url: str,
) -> dict[str, Any]:
    """Build the provider-specific extra dict for template rendering."""
    extra: dict[str, Any] = {}

    if user:
        extra["sender"] = user.username
        extra["sender_name"] = user.name or user.username

    item = payload.get("item", {})

    if resolved_type in (
        EventType.CARD_CREATED, EventType.CARD_UPDATED,
        EventType.CARD_MOVED, EventType.CARD_DELETED,
    ):
        _enrich_card(extra, item, payload, base_url)

    elif resolved_type in (EventType.CARD_COMMENTED, EventType.COMMENT_UPDATED):
        _enrich_comment(extra, item, payload, base_url)

    elif resolved_type in (
        EventType.BOARD_CREATED, EventType.BOARD_UPDATED, EventType.BOARD_DELETED,
    ):
        _enrich_board(extra, item, base_url)

    elif resolved_type in (
        EventType.LIST_CREATED, EventType.LIST_UPDATED, EventType.LIST_DELETED,
    ):
        _enrich_list(extra, item, payload, base_url)

    elif resolved_type == EventType.ATTACHMENT_CREATED:
        _enrich_attachment(extra, item, payload, base_url)

    elif resolved_type == EventType.CARD_LABEL_ADDED:
        _enrich_label(extra, item, payload, base_url)

    elif resolved_type == EventType.TASK_COMPLETED:
        _enrich_task(extra, item, payload, base_url)

    return extra


def _enrich_card(
    extra: dict[str, Any], item: dict[str, Any],
    payload: dict[str, Any], base_url: str,
) -> None:
    card = PlankaCard.from_payload(item)
    extra["card_name"] = card.name
    extra["card_id"] = card.id
    extra["card_description"] = card.description
    extra["card_due_date"] = card.due_date
    extra["board_id"] = card.board_id
    extra["list_id"] = card.list_id

    if base_url:
        extra["card_url"] = f"{base_url}/cards/{card.id}"

    # Resolve board and list names from included data
    included = payload.get("included", {})
    _resolve_board_name(extra, card.board_id, included)
    _resolve_list_name(extra, card.list_id, included)

    # For card moves, resolve old/new list names
    before = payload.get("beforeUpdate", {})
    old_list_id = before.get("listId")
    new_list_id = item.get("listId")
    if old_list_id and new_list_id and old_list_id != new_list_id:
        extra["old_list_id"] = str(old_list_id)
        extra["new_list_id"] = str(new_list_id)
        _resolve_old_new_list_names(extra, old_list_id, new_list_id, included)


def _enrich_comment(
    extra: dict[str, Any], item: dict[str, Any],
    payload: dict[str, Any], base_url: str,
) -> None:
    comment = PlankaComment.from_payload(item)
    extra["comment_text"] = comment.text
    extra["comment_id"] = comment.id

    # Resolve card info from included
    included = payload.get("included", {})
    card_data = included.get("card", item)
    card_id = str(comment.card_id or card_data.get("id", ""))
    if card_id:
        extra["card_id"] = card_id
        extra["card_name"] = card_data.get("name", "")
        if base_url:
            extra["card_url"] = f"{base_url}/cards/{card_id}"

    board_id = str(card_data.get("boardId", ""))
    extra["board_id"] = board_id
    _resolve_board_name(extra, board_id, included)


def _enrich_board(
    extra: dict[str, Any], item: dict[str, Any], base_url: str,
) -> None:
    board = PlankaBoard.from_payload(item)
    extra["board_name"] = board.name
    extra["board_id"] = board.id
    if base_url:
        extra["board_url"] = f"{base_url}/boards/{board.id}"


def _enrich_list(
    extra: dict[str, Any], item: dict[str, Any],
    payload: dict[str, Any], base_url: str,
) -> None:
    lst = PlankaList.from_payload(item)
    extra["list_name"] = lst.name
    extra["list_id"] = lst.id
    extra["board_id"] = lst.board_id

    included = payload.get("included", {})
    _resolve_board_name(extra, lst.board_id, included)


def _enrich_attachment(
    extra: dict[str, Any], item: dict[str, Any],
    payload: dict[str, Any], base_url: str,
) -> None:
    attachment = PlankaAttachment.from_payload(item)
    extra["attachment_name"] = attachment.name
    extra["attachment_id"] = attachment.id
    extra["card_id"] = attachment.card_id

    included = payload.get("included", {})
    card_data = included.get("card", {})
    extra["card_name"] = card_data.get("name", "")
    board_id = str(card_data.get("boardId", ""))
    extra["board_id"] = board_id
    _resolve_board_name(extra, board_id, included)

    if base_url and attachment.card_id:
        extra["card_url"] = f"{base_url}/cards/{attachment.card_id}"


def _enrich_label(
    extra: dict[str, Any], item: dict[str, Any],
    payload: dict[str, Any], base_url: str,
) -> None:
    # cardMembershipCreate: item is the membership; label info in included
    included = payload.get("included", {})
    label_data = included.get("label", item)
    label = PlankaLabel.from_payload(label_data)
    extra["label_name"] = label.name
    extra["label_color"] = label.color

    card_data = included.get("card", {})
    extra["card_id"] = str(card_data.get("id", item.get("cardId", "")))
    extra["card_name"] = card_data.get("name", "")

    board_id = str(card_data.get("boardId", label.board_id))
    extra["board_id"] = board_id
    _resolve_board_name(extra, board_id, included)

    if base_url and extra.get("card_id"):
        extra["card_url"] = f"{base_url}/cards/{extra['card_id']}"


def _enrich_task(
    extra: dict[str, Any], item: dict[str, Any],
    payload: dict[str, Any], base_url: str,
) -> None:
    task = PlankaTask.from_payload(item)
    extra["task_name"] = task.name
    extra["task_id"] = task.id
    extra["task_completed"] = task.is_completed
    extra["card_id"] = task.card_id

    included = payload.get("included", {})
    card_data = included.get("card", {})
    extra["card_name"] = card_data.get("name", "")

    board_id = str(card_data.get("boardId", ""))
    extra["board_id"] = board_id
    _resolve_board_name(extra, board_id, included)

    if base_url and task.card_id:
        extra["card_url"] = f"{base_url}/cards/{task.card_id}"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _resolve_board_name(
    extra: dict[str, Any], board_id: str, included: dict[str, Any],
) -> None:
    """Try to set board_name from included data."""
    if "board_name" in extra and extra["board_name"]:
        return
    board_data = included.get("board", {})
    if str(board_data.get("id", "")) == board_id:
        extra["board_name"] = board_data.get("name", "")
    elif not extra.get("board_name"):
        extra["board_name"] = ""


def _resolve_list_name(
    extra: dict[str, Any], list_id: str, included: dict[str, Any],
) -> None:
    """Try to set list_name from included data."""
    lists = included.get("lists", [])
    if isinstance(lists, list):
        for lst in lists:
            if str(lst.get("id", "")) == list_id:
                extra["list_name"] = lst.get("name", "")
                return
    elif isinstance(lists, dict) and str(lists.get("id", "")) == list_id:
        extra["list_name"] = lists.get("name", "")
        return
    if "list_name" not in extra:
        extra["list_name"] = ""


def _resolve_old_new_list_names(
    extra: dict[str, Any],
    old_list_id: Any,
    new_list_id: Any,
    included: dict[str, Any],
) -> None:
    """Resolve old_list_name and new_list_name for card moves."""
    old_id = str(old_list_id)
    new_id = str(new_list_id)
    lists = included.get("lists", [])
    if isinstance(lists, list):
        for lst in lists:
            lid = str(lst.get("id", ""))
            if lid == old_id:
                extra["old_list_name"] = lst.get("name", "")
            if lid == new_id:
                extra["new_list_name"] = lst.get("name", "")
    extra.setdefault("old_list_name", "")
    extra.setdefault("new_list_name", "")
