"""
Dashboard Note Service — PostgreSQL-based
==========================================
CRUD for memos/notes, tags, pinning, search.
Uses the existing CanifaDbPool (psycopg sync pool).
"""

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

from common.db_pool import db_pool

logger = logging.getLogger(__name__)

VIETNAM_TZ = timezone(timedelta(hours=7))


# =============================================================================
# TABLE INITIALIZATION
# =============================================================================

def init_note_tables():
    """Create tables if not exist. Call once on startup."""
    try:
        with db_pool.get_conn() as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS dashboard_notes (
                    id SERIAL PRIMARY KEY,
                    uid VARCHAR(32) UNIQUE NOT NULL,
                    creator_id VARCHAR(128) NOT NULL DEFAULT 'system',
                    content TEXT NOT NULL DEFAULT '',
                    tags TEXT[] DEFAULT '{}',
                    visibility VARCHAR(20) DEFAULT 'PRIVATE',
                    pinned BOOLEAN DEFAULT FALSE,
                    row_status VARCHAR(20) DEFAULT 'NORMAL',
                    parent_id INTEGER DEFAULT NULL,
                    created_at TIMESTAMPTZ DEFAULT NOW(),
                    updated_at TIMESTAMPTZ DEFAULT NOW()
                );

                CREATE INDEX IF NOT EXISTS idx_notes_creator
                    ON dashboard_notes(creator_id, created_at DESC);
                CREATE INDEX IF NOT EXISTS idx_notes_uid
                    ON dashboard_notes(uid);
                CREATE INDEX IF NOT EXISTS idx_notes_tags
                    ON dashboard_notes USING GIN(tags);
                CREATE INDEX IF NOT EXISTS idx_notes_pinned
                    ON dashboard_notes(creator_id, pinned DESC, created_at DESC);

                -- ═══ COMMENTS TABLE ═══
                CREATE TABLE IF NOT EXISTS note_comments (
                    id SERIAL PRIMARY KEY,
                    uid VARCHAR(32) UNIQUE NOT NULL,
                    parent_note_uid VARCHAR(32) NOT NULL,
                    creator_id VARCHAR(128) NOT NULL DEFAULT 'anonymous',
                    creator_name VARCHAR(100) DEFAULT NULL,
                    content TEXT NOT NULL,
                    visibility VARCHAR(20) DEFAULT 'PUBLIC',
                    created_at TIMESTAMPTZ DEFAULT NOW()
                );

                CREATE INDEX IF NOT EXISTS idx_comments_parent
                    ON note_comments(parent_note_uid, created_at ASC);
                CREATE INDEX IF NOT EXISTS idx_comments_uid
                    ON note_comments(uid);

                -- ═══ NOTIFICATIONS TABLE ═══
                CREATE TABLE IF NOT EXISTS note_notifications (
                    id SERIAL PRIMARY KEY,
                    recipient_id VARCHAR(128) NOT NULL,
                    sender_id VARCHAR(128) NOT NULL,
                    sender_name VARCHAR(100) DEFAULT NULL,
                    notification_type VARCHAR(50) DEFAULT 'COMMENT',
                    note_uid VARCHAR(32) NOT NULL,
                    comment_uid VARCHAR(32),
                    status VARCHAR(20) DEFAULT 'UNREAD',
                    created_at TIMESTAMPTZ DEFAULT NOW()
                );

                CREATE INDEX IF NOT EXISTS idx_notifications_recipient
                    ON note_notifications(recipient_id, created_at DESC);
                CREATE INDEX IF NOT EXISTS idx_notifications_status
                    ON note_notifications(recipient_id, status);
            """)
            logger.info("✅ dashboard_notes + comments + notifications tables initialized")
    except Exception as e:
        logger.error("❌ Failed to init note tables: %s", e)


# =============================================================================
# UID GENERATOR
# =============================================================================

def _generate_uid() -> str:
    import secrets
    return secrets.token_urlsafe(16)[:22]


def _now_vn() -> datetime:
    return datetime.now(VIETNAM_TZ)


# =============================================================================
# CRUD OPERATIONS
# =============================================================================

def create_note(
    creator_id: str,
    content: str,
    tags: list[str] | None = None,
    visibility: str = "PRIVATE",
    parent_id: int | None = None,
) -> dict[str, Any]:
    """Create a new note/memo."""
    uid = _generate_uid()
    now = _now_vn()
    tags = tags or []

    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                """
                INSERT INTO dashboard_notes (uid, creator_id, content, tags, visibility, parent_id, created_at, updated_at)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                RETURNING id, uid, creator_id, content, tags, visibility, pinned, row_status, parent_id, created_at, updated_at
                """,
                (uid, creator_id, content, tags, visibility, parent_id, now, now),
            ).fetchone()

            return _row_to_dict(row)
    except Exception as e:
        logger.error("Error creating note: %s", e)
        raise


def get_notes(
    creator_id: str | None = None,
    tag: str | None = None,
    search: str | None = None,
    pinned_only: bool = False,
    limit: int = 50,
    offset: int = 0,
    include_archived: bool = False,
) -> list[dict[str, Any]]:
    """List notes with filtering."""
    conditions = []
    params = []

    if not include_archived:
        conditions.append("row_status = 'NORMAL'")

    if creator_id:
        conditions.append("creator_id = %s")
        params.append(creator_id)

    if tag:
        conditions.append("%s = ANY(tags)")
        params.append(tag)

    if search:
        conditions.append("content ILIKE %s")
        params.append(f"%{search}%")

    if pinned_only:
        conditions.append("pinned = TRUE")

    where = " AND ".join(conditions) if conditions else "TRUE"

    try:
        with db_pool.get_conn() as conn:
            rows = conn.execute(
                f"""
                SELECT id, uid, creator_id, content, tags, visibility, pinned,
                       row_status, parent_id, created_at, updated_at
                FROM dashboard_notes
                WHERE {where}
                ORDER BY pinned DESC, created_at DESC
                LIMIT %s OFFSET %s
                """,
                (*params, limit, offset),
            ).fetchall()

            return [_row_to_dict(r) for r in rows]
    except Exception as e:
        logger.error("Error fetching notes: %s", e)
        return []


def get_note_by_uid(uid: str) -> dict[str, Any] | None:
    """Get single note by UID."""
    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                """
                SELECT id, uid, creator_id, content, tags, visibility, pinned,
                       row_status, parent_id, created_at, updated_at
                FROM dashboard_notes WHERE uid = %s
                """,
                (uid,),
            ).fetchone()

            return _row_to_dict(row) if row else None
    except Exception as e:
        logger.error("Error fetching note %s: %s", uid, e)
        return None


def update_note(uid: str, **fields) -> dict[str, Any] | None:
    """Update note fields (content, tags, visibility, pinned, row_status)."""
    allowed = {"content", "tags", "visibility", "pinned", "row_status"}
    updates = {k: v for k, v in fields.items() if k in allowed}

    if not updates:
        return get_note_by_uid(uid)

    updates["updated_at"] = _now_vn()

    set_clauses = ", ".join(f"{k} = %s" for k in updates)
    values = list(updates.values())
    values.append(uid)

    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                f"""
                UPDATE dashboard_notes SET {set_clauses}
                WHERE uid = %s
                RETURNING id, uid, creator_id, content, tags, visibility, pinned,
                          row_status, parent_id, created_at, updated_at
                """,
                values,
            ).fetchone()

            return _row_to_dict(row) if row else None
    except Exception as e:
        logger.error("Error updating note %s: %s", uid, e)
        return None


def delete_note(uid: str) -> bool:
    """Soft-delete (archive) a note."""
    return update_note(uid, row_status="ARCHIVED") is not None


def hard_delete_note(uid: str) -> bool:
    """Permanently delete a note."""
    try:
        with db_pool.get_conn() as conn:
            conn.execute("DELETE FROM dashboard_notes WHERE uid = %s", (uid,))
            return True
    except Exception as e:
        logger.error("Error hard-deleting note %s: %s", uid, e)
        return False


def toggle_pin(uid: str) -> dict[str, Any] | None:
    """Toggle pinned status."""
    note = get_note_by_uid(uid)
    if not note:
        return None
    return update_note(uid, pinned=not note["pinned"])


def get_all_tags(creator_id: str | None = None) -> list[str]:
    """Get all unique tags."""
    try:
        with db_pool.get_conn() as conn:
            if creator_id:
                rows = conn.execute(
                    """
                    SELECT DISTINCT unnest(tags) AS tag
                    FROM dashboard_notes
                    WHERE creator_id = %s AND row_status = 'NORMAL'
                    ORDER BY tag
                    """,
                    (creator_id,),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT DISTINCT unnest(tags) AS tag
                    FROM dashboard_notes
                    WHERE row_status = 'NORMAL'
                    ORDER BY tag
                    """,
                ).fetchall()

            return [r[0] for r in rows]
    except Exception as e:
        logger.error("Error fetching tags: %s", e)
        return []


def get_note_stats(creator_id: str | None = None) -> dict[str, Any]:
    """Get summary stats."""
    try:
        with db_pool.get_conn() as conn:
            where = "WHERE creator_id = %s" if creator_id else ""
            params = (creator_id,) if creator_id else ()

            stats = conn.execute(
                f"""
                SELECT
                    COUNT(*) FILTER (WHERE row_status = 'NORMAL') AS total,
                    COUNT(*) FILTER (WHERE pinned = TRUE AND row_status = 'NORMAL') AS pinned,
                    COUNT(*) FILTER (WHERE row_status = 'ARCHIVED') AS archived,
                    COUNT(DISTINCT unnest(tags)) AS tag_count
                FROM dashboard_notes {where}
                """,
                params,
            ).fetchone()

            # Fallback if the query fails due to unnest in aggregate
            total = 0
            pinned = 0
            archived = 0
            if stats:
                total = stats[0] or 0
                pinned = stats[1] or 0
                archived = stats[2] or 0

            return {
                "total": total,
                "pinned": pinned,
                "archived": archived,
            }
    except Exception as e:
        logger.error("Error fetching stats: %s", e)
        return {"total": 0, "pinned": 0, "archived": 0}


# =============================================================================
# HELPERS
# =============================================================================

def _row_to_dict(row) -> dict[str, Any]:
    """Convert psycopg row tuple to dict."""
    if row is None:
        return {}
    # Columns: id, uid, creator_id, content, tags, visibility, pinned,
    #          row_status, parent_id, created_at, updated_at
    return {
        "id": row[0],
        "uid": row[1],
        "creator_id": row[2],
        "content": row[3],
        "tags": list(row[4]) if row[4] else [],
        "visibility": row[5],
        "pinned": row[6],
        "row_status": row[7],
        "parent_id": row[8],
        "created_at": row[9].isoformat() if row[9] else None,
        "updated_at": row[10].isoformat() if row[10] else None,
    }


def _comment_to_dict(row) -> dict[str, Any]:
    """Convert comment row to dict."""
    if row is None:
        return {}
    return {
        "id": row[0],
        "uid": row[1],
        "parent_note_uid": row[2],
        "creator_id": row[3],
        "creator_name": row[4],
        "content": row[5],
        "visibility": row[6],
        "created_at": row[7].isoformat() if row[7] else None,
    }


def _notification_to_dict(row) -> dict[str, Any]:
    """Convert notification row to dict."""
    if row is None:
        return {}
    return {
        "id": row[0],
        "recipient_id": row[1],
        "sender_id": row[2],
        "sender_name": row[3],
        "notification_type": row[4],
        "note_uid": row[5],
        "comment_uid": row[6],
        "status": row[7],
        "created_at": row[8].isoformat() if row[8] else None,
    }


# =============================================================================
# COMMENT OPERATIONS
# =============================================================================

def create_comment(
    parent_note_uid: str,
    content: str,
    creator_id: str = "anonymous",
    creator_name: str | None = None,
    visibility: str = "PUBLIC",
) -> dict[str, Any]:
    """Create a comment on a note."""
    uid = _generate_uid()
    now = _now_vn()

    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                """
                INSERT INTO note_comments (uid, parent_note_uid, creator_id, creator_name, content, visibility, created_at)
                VALUES (%s, %s, %s, %s, %s, %s, %s)
                RETURNING id, uid, parent_note_uid, creator_id, creator_name, content, visibility, created_at
                """,
                (uid, parent_note_uid, creator_id, creator_name, content, visibility, now),
            ).fetchone()

            return _comment_to_dict(row)
    except Exception as e:
        logger.error("Error creating comment: %s", e)
        raise


def get_comments(parent_note_uid: str, limit: int = 100, offset: int = 0) -> list[dict[str, Any]]:
    """Get all comments for a note, sorted oldest first."""
    try:
        with db_pool.get_conn() as conn:
            rows = conn.execute(
                """
                SELECT id, uid, parent_note_uid, creator_id, creator_name, content, visibility, created_at
                FROM note_comments
                WHERE parent_note_uid = %s
                ORDER BY created_at ASC
                LIMIT %s OFFSET %s
                """,
                (parent_note_uid, limit, offset),
            ).fetchall()

            return [_comment_to_dict(r) for r in rows]
    except Exception as e:
        logger.error("Error fetching comments for %s: %s", parent_note_uid, e)
        return []


def get_comment_count(parent_note_uid: str) -> int:
    """Get comment count for a note."""
    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                "SELECT COUNT(*) FROM note_comments WHERE parent_note_uid = %s",
                (parent_note_uid,),
            ).fetchone()
            return row[0] if row else 0
    except Exception as e:
        logger.error("Error counting comments: %s", e)
        return 0


def get_comment_counts_batch(note_uids: list[str]) -> dict[str, int]:
    """Get comment counts for multiple notes in one query."""
    if not note_uids:
        return {}
    try:
        with db_pool.get_conn() as conn:
            rows = conn.execute(
                """
                SELECT parent_note_uid, COUNT(*)
                FROM note_comments
                WHERE parent_note_uid = ANY(%s)
                GROUP BY parent_note_uid
                """,
                (note_uids,),
            ).fetchall()
            return {r[0]: r[1] for r in rows}
    except Exception as e:
        logger.error("Error batch counting comments: %s", e)
        return {}


def delete_comment(uid: str) -> bool:
    """Delete a comment by UID."""
    try:
        with db_pool.get_conn() as conn:
            conn.execute("DELETE FROM note_comments WHERE uid = %s", (uid,))
            return True
    except Exception as e:
        logger.error("Error deleting comment %s: %s", uid, e)
        return False


# =============================================================================
# NOTIFICATION OPERATIONS
# =============================================================================

def create_notification(
    recipient_id: str,
    sender_id: str,
    note_uid: str,
    notification_type: str = "COMMENT",
    sender_name: str | None = None,
    comment_uid: str | None = None,
) -> dict[str, Any]:
    """Create a notification for a user."""
    now = _now_vn()

    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                """
                INSERT INTO note_notifications
                    (recipient_id, sender_id, sender_name, notification_type, note_uid, comment_uid, status, created_at)
                VALUES (%s, %s, %s, %s, %s, %s, 'UNREAD', %s)
                RETURNING id, recipient_id, sender_id, sender_name, notification_type, note_uid, comment_uid, status, created_at
                """,
                (recipient_id, sender_id, sender_name, notification_type, note_uid, comment_uid, now),
            ).fetchone()

            return _notification_to_dict(row)
    except Exception as e:
        logger.error("Error creating notification: %s", e)
        raise


def list_notifications(
    recipient_id: str,
    status: str | None = None,
    limit: int = 50,
    offset: int = 0,
) -> list[dict[str, Any]]:
    """List notifications for a user."""
    try:
        with db_pool.get_conn() as conn:
            if status:
                rows = conn.execute(
                    """
                    SELECT id, recipient_id, sender_id, sender_name, notification_type,
                           note_uid, comment_uid, status, created_at
                    FROM note_notifications
                    WHERE recipient_id = %s AND status = %s
                    ORDER BY created_at DESC
                    LIMIT %s OFFSET %s
                    """,
                    (recipient_id, status, limit, offset),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT id, recipient_id, sender_id, sender_name, notification_type,
                           note_uid, comment_uid, status, created_at
                    FROM note_notifications
                    WHERE recipient_id = %s
                    ORDER BY created_at DESC
                    LIMIT %s OFFSET %s
                    """,
                    (recipient_id, limit, offset),
                ).fetchall()

            return [_notification_to_dict(r) for r in rows]
    except Exception as e:
        logger.error("Error listing notifications: %s", e)
        return []


def count_unread_notifications(recipient_id: str) -> int:
    """Count unread notifications for a user."""
    try:
        with db_pool.get_conn() as conn:
            row = conn.execute(
                "SELECT COUNT(*) FROM note_notifications WHERE recipient_id = %s AND status = 'UNREAD'",
                (recipient_id,),
            ).fetchone()
            return row[0] if row else 0
    except Exception as e:
        logger.error("Error counting unread notifications: %s", e)
        return 0


def mark_notification_read(notification_id: int) -> bool:
    """Mark a notification as read."""
    try:
        with db_pool.get_conn() as conn:
            conn.execute(
                "UPDATE note_notifications SET status = 'READ' WHERE id = %s",
                (notification_id,),
            )
            return True
    except Exception as e:
        logger.error("Error marking notification read: %s", e)
        return False


def mark_all_notifications_read(recipient_id: str) -> bool:
    """Mark all notifications as read for a user."""
    try:
        with db_pool.get_conn() as conn:
            conn.execute(
                "UPDATE note_notifications SET status = 'READ' WHERE recipient_id = %s AND status = 'UNREAD'",
                (recipient_id,),
            )
            return True
    except Exception as e:
        logger.error("Error marking all notifications read: %s", e)
        return False


def delete_notification(notification_id: int) -> bool:
    """Delete a notification."""
    try:
        with db_pool.get_conn() as conn:
            conn.execute("DELETE FROM note_notifications WHERE id = %s", (notification_id,))
            return True
    except Exception as e:
        logger.error("Error deleting notification %s: %s", notification_id, e)
        return False


# =============================================================================
# ACTIVITY STATS (Calendar Heatmap)
# =============================================================================

def get_activity_stats(creator_id: str | None = None, year: int | None = None) -> dict[str, int]:
    """Get daily memo count for the activity calendar heatmap.
    Returns dict like {"2026-04-13": 3, "2026-04-12": 1, ...}
    """
    try:
        if not year:
            year = datetime.now(VIETNAM_TZ).year

        with db_pool.get_conn() as conn:
            if creator_id:
                rows = conn.execute(
                    """
                    SELECT DATE(created_at AT TIME ZONE 'Asia/Ho_Chi_Minh') AS day, COUNT(*)
                    FROM dashboard_notes
                    WHERE creator_id = %s
                      AND row_status = 'NORMAL'
                      AND EXTRACT(YEAR FROM created_at AT TIME ZONE 'Asia/Ho_Chi_Minh') = %s
                    GROUP BY day
                    ORDER BY day
                    """,
                    (creator_id, year),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT DATE(created_at AT TIME ZONE 'Asia/Ho_Chi_Minh') AS day, COUNT(*)
                    FROM dashboard_notes
                    WHERE row_status = 'NORMAL'
                      AND EXTRACT(YEAR FROM created_at AT TIME ZONE 'Asia/Ho_Chi_Minh') = %s
                    GROUP BY day
                    ORDER BY day
                    """,
                    (year,),
                ).fetchall()

            return {str(r[0]): r[1] for r in rows}
    except Exception as e:
        logger.error("Error fetching activity stats: %s", e)
        return {}

