"""Applicant in-app notifications (admin adjudication)."""

from __future__ import annotations

import logging
import math
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from sqlalchemy import desc, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from src.initiative_db.models import Draft, Initiative, UserNotification

logger = logging.getLogger(__name__)

__all__ = [
    "merit_category_label_from_draft_payload",
    "best_effort_notify_applicant_after_admin_decision",
    "list_notifications_for_user",
    "count_unread_notifications",
    "mark_notification_read",
]


def merit_category_label_from_draft_payload(payload: Dict[str, Any]) -> Optional[str]:
    """
    Align with fe0 `getApplicationMeritCategoryHint`: 2.1.1 / 2.1.2 và sách giáo trình (textbook + book) → Xuất sắc;
    2.1.4 (`poster-without-review`) → Trung bình; else Khá for known groups.
    Returns None if classification is missing (caller may still show «Khá» for approved).
    """
    tabs = payload.get("tabs") if isinstance(payload.get("tabs"), dict) else {}
    app = tabs.get("application") if isinstance(tabs.get("application"), dict) else {}
    c = app.get("initiativeClassification")
    k = str(app.get("researchEvidenceKind") or "").strip()
    if c == "research" and k in ("international", "domestic"):
        return "Xuất sắc"
    if c == "research" and k == "poster-without-review":
        return "Trung bình"
    tb = str(app.get("textbookEvidenceKind") or "").strip()
    if c == "textbook" and tb == "book":
        return "Xuất sắc"
    if c in ("technical", "research", "textbook"):
        return "Khá"
    return None


def _iso(dt: Optional[datetime]) -> Optional[str]:
    if dt is None:
        return None
    v = dt.astimezone(timezone.utc).replace(microsecond=0).isoformat()
    return v.replace("+00:00", "Z")


def _notification_to_api(row: UserNotification) -> Dict[str, Any]:
    return {
        "id": str(row.id),
        "type": row.type,
        "title": row.title,
        "body": row.body,
        "applicationId": row.application_id,
        "relatedInitiativeId": str(row.related_initiative_id) if row.related_initiative_id else None,
        "sourceAdminResultId": str(row.source_admin_result_id) if row.source_admin_result_id else None,
        "decision": row.decision,
        "meritCategoryLabel": row.merit_category_label,
        "feedback": row.feedback_text or "",
        "rationale": row.rationale_text,
        "readAt": _iso(row.read_at),
        "createdAt": _iso(row.created_at),
    }


async def best_effort_notify_applicant_after_admin_decision(result: Dict[str, Any]) -> None:
    """
    Second transaction after admin-result commit: insert inbox row for initiative owner.
    Swallows all errors (logged); never raises.
    """
    try:
        from src.initiative_db.engine import get_session, is_postgres_enabled

        if not is_postgres_enabled():
            return

        initiative_id = uuid.UUID(str(result["initiativeId"]))
        application_id = str(result["applicationId"]).strip()
        decision = str(result.get("decision") or "").strip().lower()
        if decision not in ("approved", "rejected"):
            return

        feedback = str(result.get("feedback") or "")
        rationale = result.get("rationale")
        rationale_s = (str(rationale).strip() if rationale is not None else None) or None
        admin_result_id = uuid.UUID(str(result["id"]))

        async with get_session() as session:
            ini = await session.get(Initiative, initiative_id)
            if ini is None:
                return
            recipient_id = ini.owner_id

            draft_stmt = (
                select(Draft)
                .where(Draft.initiative_id == initiative_id)
                .order_by(desc(Draft.updated_at))
                .limit(1)
            )
            draft = (await session.execute(draft_stmt)).scalar_one_or_none()
            payload: Dict[str, Any] = dict(draft.payload) if draft and isinstance(draft.payload, dict) else {}

            merit: Optional[str] = None
            if decision == "approved":
                merit = merit_category_label_from_draft_payload(payload)
                if merit is None:
                    merit = "Khá"

            if decision == "approved":
                title = "Hồ sơ được duyệt"
            else:
                title = "Hồ sơ không được duyệt"

            body_parts: List[str] = []
            if decision == "approved" and merit:
                body_parts.append(f"Phân hạng đề xuất: {merit}.")
            if feedback.strip():
                fb = feedback.strip()
                body_parts.append(fb[:280] + ("…" if len(fb) > 280 else ""))
            elif decision == "rejected":
                body_parts.append("Xem phản hồi và lý do chi tiết bên dưới.")
            body = " ".join(body_parts) if body_parts else title

            row = UserNotification(
                recipient_user_id=recipient_id,
                type="admin_application_decision",
                title=title,
                body=body,
                application_id=application_id,
                related_initiative_id=initiative_id,
                source_admin_result_id=admin_result_id,
                decision=decision,
                merit_category_label=merit if decision == "approved" else None,
                feedback_text=feedback,
                rationale_text=rationale_s,
            )
            session.add(row)
            await session.commit()
    except Exception:
        logger.exception("best_effort_notify_applicant_after_admin_decision failed")


async def list_notifications_for_user(
    session: AsyncSession,
    user_id: uuid.UUID,
    *,
    page: int,
    page_size: int,
) -> Dict[str, Any]:
    page = max(1, page)
    page_size = max(1, min(100, page_size))

    count_stmt = select(func.count()).select_from(UserNotification).where(UserNotification.recipient_user_id == user_id)
    total = int((await session.execute(count_stmt)).scalar_one())
    total_pages = max(1, math.ceil(total / page_size)) if total else 1
    start = (page - 1) * page_size

    stmt = (
        select(UserNotification)
        .where(UserNotification.recipient_user_id == user_id)
        .order_by(desc(UserNotification.created_at))
        .offset(start)
        .limit(page_size)
    )
    rows = (await session.execute(stmt)).scalars().all()

    return {
        "data": [_notification_to_api(r) for r in rows],
        "pagination": {
            "page": page,
            "pageSize": page_size,
            "totalItems": total,
            "totalPages": total_pages,
        },
    }


async def count_unread_notifications(session: AsyncSession, user_id: uuid.UUID) -> int:
    stmt = select(func.count()).select_from(UserNotification).where(
        UserNotification.recipient_user_id == user_id,
        UserNotification.read_at.is_(None),
    )
    return int((await session.execute(stmt)).scalar_one())


async def mark_notification_read(session: AsyncSession, user_id: uuid.UUID, notification_id: uuid.UUID) -> bool:
    """Return True if a row was updated."""
    now = datetime.now(timezone.utc)
    stmt = (
        update(UserNotification)
        .where(
            UserNotification.id == notification_id,
            UserNotification.recipient_user_id == user_id,
        )
        .values(read_at=now)
    )
    res = await session.execute(stmt)
    await session.flush()
    return (res.rowcount or 0) > 0
