"""Notification target management API routes."""

import logging

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from typing import Any

from ..auth.dependencies import get_current_user
from ..database.engine import get_session
from ..database.models import NotificationTarget, NotificationTrackerTarget, TargetReceiver, TelegramBot, TelegramChat, User
from ..services.notifier import send_test_notification
from .helpers import get_owned_entity
from .target_receivers import _receiver_key

_LOGGER = logging.getLogger(__name__)

router = APIRouter(prefix="/api/targets", tags=["targets"])

# Delivery fields that belong in TargetReceiver, NOT in target.config
_DELIVERY_FIELDS: dict[str, str] = {
    "telegram": "chat_id",
    "webhook": "url",
    "email": "email",
    "discord": "webhook_url",
    "slack": "webhook_url",
    "ntfy": "topic",
    "matrix": "room_id",
}


def _extract_delivery_fields(target_type: str, config: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
    """Split config into (clean_config, receiver_config).

    Returns the target config with delivery fields removed,
    and a receiver config dict (empty if no delivery field found).
    """
    field = _DELIVERY_FIELDS.get(target_type)
    if not field:
        return dict(config), {}

    clean = dict(config)
    receiver_cfg: dict[str, Any] = {}

    value = clean.pop(field, None)
    if value:
        receiver_cfg[field] = value
        # For webhook, also move headers to receiver config
        if target_type == "webhook" and "headers" in clean:
            receiver_cfg["headers"] = clean.pop("headers")

    return clean, receiver_cfg


class TargetCreate(BaseModel):
    type: str  # "telegram" or "webhook"
    name: str
    icon: str = ""
    config: dict[str, Any] = {}
    chat_action: str | None = None


class TargetUpdate(BaseModel):
    name: str | None = None
    icon: str | None = None
    config: dict[str, Any] | None = None
    chat_action: str | None = None


@router.get("")
async def list_targets(
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """List all notification targets for the current user."""
    result = await session.exec(
        select(NotificationTarget).where(NotificationTarget.user_id == user.id)
    )
    targets = result.all()

    # Batch-load all receivers for the user's targets in one query
    target_ids = [tgt.id for tgt in targets]
    target_receivers: dict[int, list[TargetReceiver]] = {tid: [] for tid in target_ids}
    if target_ids:
        recv_result = await session.exec(
            select(TargetReceiver).where(TargetReceiver.target_id.in_(target_ids))
        )
        for recv in recv_result.all():
            target_receivers[recv.target_id].append(recv)

    # Batch-load chat names and languages for all telegram targets
    chat_names: dict[str, str] = {}
    chat_languages: dict[str, str] = {}
    chat_lookups: list[tuple[int, str]] = []  # (bot_id, chat_id)
    for tgt in targets:
        if tgt.type == "telegram":
            bot_id = tgt.config.get("bot_id")
            if not bot_id:
                continue
            for recv in target_receivers.get(tgt.id, []):
                chat_id = str(recv.config.get("chat_id", ""))
                if chat_id:
                    chat_lookups.append((bot_id, chat_id))

    if chat_lookups:
        all_bot_ids = list({bl[0] for bl in chat_lookups})
        all_chat_ids = list({bl[1] for bl in chat_lookups})
        chat_result = await session.exec(
            select(TelegramChat).where(
                TelegramChat.bot_id.in_(all_bot_ids),
                TelegramChat.chat_id.in_(all_chat_ids),
            )
        )
        for chat in chat_result.all():
            key = f"{chat.bot_id}_{chat.chat_id}"
            chat_names[key] = chat.title or chat.username or ""
            lang = getattr(chat, 'language_override', '') or getattr(chat, 'language_code', '') or ''
            if lang:
                chat_languages[key] = lang

    # Build lookup for broadcast child target resolution
    target_map = {t.id: t for t in targets}

    return [
        _target_response(t, chat_names, target_receivers.get(t.id, []), chat_languages, target_map)
        for t in targets
    ]


@router.post("", status_code=status.HTTP_201_CREATED)
async def create_target(
    body: TargetCreate,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """Create a new notification target."""
    valid_types = ("telegram", "webhook", "email", "discord", "slack", "ntfy", "matrix", "broadcast")
    if body.type not in valid_types:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Type must be one of: {', '.join(valid_types)}",
        )

    # Broadcast-specific validation
    if body.type == "broadcast":
        child_ids = body.config.get("child_target_ids", [])
        if not isinstance(child_ids, list):
            raise HTTPException(status_code=400, detail="child_target_ids must be a list")
        await _validate_broadcast_children(session, child_ids, user.id, exclude_target_id=None)
        clean_config = {"child_target_ids": child_ids}
        receiver_cfg: dict[str, Any] = {}
    else:
        # Extract delivery fields from config — they go into a TargetReceiver
        clean_config, receiver_cfg = _extract_delivery_fields(body.type, body.config)

    target = NotificationTarget(
        user_id=user.id,
        type=body.type,
        name=body.name,
        icon=body.icon,
        config=clean_config,
        chat_action=body.chat_action,
    )
    session.add(target)
    await session.flush()  # get target.id

    # Auto-create a receiver if delivery fields were present
    if receiver_cfg:
        key = _receiver_key(body.type, receiver_cfg)
        receiver = TargetReceiver(
            target_id=target.id,
            name=body.name,
            config=receiver_cfg,
            receiver_key=key,
            enabled=True,
        )
        session.add(receiver)

    await session.commit()
    await session.refresh(target)
    # Load receivers for a full response consistent with GET
    recv_result = await session.exec(
        select(TargetReceiver).where(TargetReceiver.target_id == target.id)
    )
    return _target_response(target, receivers=list(recv_result.all()))


@router.get("/{target_id}")
async def get_target(
    target_id: int,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """Get a specific notification target."""
    target = await _get_user_target(session, target_id, user.id)
    recv_result = await session.exec(
        select(TargetReceiver).where(TargetReceiver.target_id == target.id)
    )
    receivers = list(recv_result.all())
    return _target_response(target, receivers=receivers)


@router.put("/{target_id}")
async def update_target(
    target_id: int,
    body: TargetUpdate,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """Update a notification target."""
    target = await _get_user_target(session, target_id, user.id)
    updates = body.model_dump(exclude_unset=True)

    # If config is being updated, extract any delivery fields
    if "config" in updates and updates["config"] is not None:
        if target.type == "broadcast":
            child_ids = updates["config"].get("child_target_ids", [])
            if not isinstance(child_ids, list):
                raise HTTPException(status_code=400, detail="child_target_ids must be a list")
            await _validate_broadcast_children(session, child_ids, user.id, exclude_target_id=target.id)
            disabled_ids = updates["config"].get("disabled_child_ids", [])
            updates["config"] = {"child_target_ids": child_ids, "disabled_child_ids": disabled_ids}
        else:
            clean_config, receiver_cfg = _extract_delivery_fields(target.type, updates["config"])
            updates["config"] = clean_config

            # Update or create receiver if delivery fields were present
            if receiver_cfg:
                key = _receiver_key(target.type, receiver_cfg)
                existing_result = await session.exec(
                    select(TargetReceiver).where(
                        TargetReceiver.target_id == target.id,
                        TargetReceiver.receiver_key == key,
                    )
                )
                existing_recv = existing_result.first()
                if existing_recv:
                    existing_recv.config = receiver_cfg
                    session.add(existing_recv)
                else:
                    receiver = TargetReceiver(
                        target_id=target.id,
                        name=target.name,
                        config=receiver_cfg,
                        receiver_key=key,
                        enabled=True,
                    )
                    session.add(receiver)

    for field_name, value in updates.items():
        setattr(target, field_name, value)
    # Force SQLAlchemy to detect JSON column change
    from sqlalchemy.orm.attributes import flag_modified
    if "config" in updates:
        flag_modified(target, "config")
    session.add(target)
    await session.commit()
    await session.refresh(target)
    return {"id": target.id, "type": target.type, "name": target.name}


@router.delete("/{target_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_target(
    target_id: int,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """Delete a notification target. Fails if linked to any tracker."""
    from .delete_protection import check_notification_target, raise_if_used
    target = await _get_user_target(session, target_id, user.id)
    raise_if_used(await check_notification_target(session, target.id), target.name)
    # Delete child receivers
    recv_result = await session.exec(
        select(TargetReceiver).where(TargetReceiver.target_id == target_id)
    )
    for r in recv_result.all():
        await session.delete(r)
    await session.delete(target)
    await session.commit()


@router.post("/{target_id}/test")
async def test_target(
    target_id: int,
    locale: str = Query("en"),
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """Send a test notification to a target."""
    target = await _get_user_target(session, target_id, user.id)
    result = await send_test_notification(target, locale=locale)
    return result


async def _validate_broadcast_children(
    session: AsyncSession,
    child_ids: list[int],
    user_id: int,
    *,
    exclude_target_id: int | None = None,
) -> None:
    """Validate broadcast child target IDs.

    - All IDs must exist and belong to the user
    - No broadcast targets allowed as children (prevents circular refs)
    - Cannot reference itself
    """
    if not child_ids:
        return
    if exclude_target_id and exclude_target_id in child_ids:
        raise HTTPException(status_code=400, detail="A broadcast target cannot include itself")

    # Batch-load all children in a single IN query instead of N+1 individual fetches
    children = (await session.exec(
        select(NotificationTarget).where(NotificationTarget.id.in_(child_ids))
    )).all()
    children_by_id = {c.id: c for c in children}

    for child_id in child_ids:
        child = children_by_id.get(child_id)
        if not child or child.user_id != user_id:
            raise HTTPException(status_code=400, detail=f"Child target {child_id} not found")
        if child.type == "broadcast":
            raise HTTPException(status_code=400, detail="A broadcast target cannot include another broadcast")


def _target_response(
    target: NotificationTarget,
    chat_names: dict[str, str] | None = None,
    receivers: list[TargetReceiver] | None = None,
    chat_languages: dict[str, str] | None = None,
    target_map: dict[int, NotificationTarget] | None = None,
) -> dict:
    recv_list = receivers or []
    resp = {
        "id": target.id,
        "type": target.type,
        "name": target.name,
        "icon": target.icon,
        "config": _safe_config(target),
        "chat_action": target.chat_action,
        "receiver_count": len(recv_list),
        "receivers": [
            {
                "id": r.id,
                "name": r.name,
                "config": dict(r.config),
                "receiver_key": r.receiver_key,
                "locale": getattr(r, 'locale', '') or '',
                "enabled": r.enabled,
            }
            for r in recv_list
        ],
        "created_at": target.created_at.isoformat(),
    }
    # Attach resolved chat names from receivers for telegram targets
    if target.type == "telegram" and chat_names:
        bot_id = target.config.get("bot_id")
        for recv_resp in resp["receivers"]:
            chat_id = str(recv_resp["config"].get("chat_id", ""))
            key = f"{bot_id}_{chat_id}"
            if key in chat_names:
                recv_resp["chat_name"] = chat_names[key]
            if chat_languages and key in chat_languages:
                recv_resp["language_code"] = chat_languages[key]
    # Attach child target summaries for broadcast targets
    if target.type == "broadcast" and target_map:
        child_ids = target.config.get("child_target_ids", [])
        resp["child_targets"] = [
            {"id": cid, "name": target_map[cid].name, "type": target_map[cid].type, "icon": target_map[cid].icon}
            for cid in child_ids if cid in target_map
        ]
        resp["receiver_count"] = len(resp["child_targets"])
    return resp


def _safe_config(target: NotificationTarget) -> dict:
    """Return config with sensitive fields masked."""
    config = dict(target.config)
    if "bot_token" in config:
        token = config["bot_token"]
        config["bot_token"] = f"{token[:8]}...{token[-4:]}" if len(token) > 12 else "***"
    if "api_key" in config:
        config["api_key"] = "***"
    return config


async def _get_user_target(
    session: AsyncSession, target_id: int, user_id: int
) -> NotificationTarget:
    return await get_owned_entity(
        session, NotificationTarget, target_id, user_id,
        not_found_msg="Target not found",
    )
