import os
from pathlib import Path
import re
import sys
import time
import traceback
import uuid

import telepot
from telepot.loop import MessageLoop
from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton

import keyboards
import ticketToday
import insertCompetence


ROOT_DIR = Path(__file__).resolve().parents[1]
for path in (ROOT_DIR / "conn", ROOT_DIR / "api"):
    if str(path) not in sys.path:
        sys.path.insert(0, str(path))

import user
import config
import filter as filter_ui
import filters as filter_store
import mysql5
import phrase_similarity
import ai_retriever


PAGE_SIZE = 3
MAX_TELEGRAM_TEXT = 3800
SESSION_TTL_SECONDS = 60 * 60
DELETE_FILTER_RE = re.compile(r"^/(Competen|Prodotto|Area|Sottoarea)_id_cf_(\d+)")
SEARCH_SESSIONS = {}


def _ticket_link(ticket_id):
    return f"https://tsnew.sanmarcoweb.com/it/ticket/index/index/operation/view/id/{ticket_id}"


def _short(value, limit=900):
    text = str(value or "").strip()
    if len(text) <= limit:
        return text
    return text[:limit].rsplit(" ", 1)[0] + "..."


def _send_chunks(chat_id, text, reply_markup=None):
    text = str(text or "")
    if len(text) <= MAX_TELEGRAM_TEXT:
        bot.sendMessage(chat_id, text, reply_markup=reply_markup)
        return

    chunks = [text[i:i + MAX_TELEGRAM_TEXT] for i in range(0, len(text), MAX_TELEGRAM_TEXT)]
    for index, chunk in enumerate(chunks):
        bot.sendMessage(chat_id, chunk, reply_markup=reply_markup if index == len(chunks) - 1 else None)


def _is_admin(user_id):
    if int(user_id) == int(config.TELEGRAM_ADMIN_ID):
        return True
    try:
        rows = user.getUserByTid(user_id)
        return bool(rows and len(rows[0]) > 4 and rows[0][4])
    except Exception:
        return False


def _is_pending_user(user_id):
    rows = user.getUserByTid(user_id)
    return bool(rows and rows[0][3] == "P" and not _is_admin(user_id))


def _parse_int_token(value):
    if value is None:
        return None
    value = str(value).strip()
    if value.startswith("_"):
        value = "-" + value[1:]
    try:
        return int(value)
    except ValueError:
        return None


def _looks_like_int(value):
    return _parse_int_token(value) is not None


def _add_filter_value(search_filters, key, value):
    parsed = _parse_int_token(value)
    if parsed is None:
        return False
    values = search_filters.setdefault(key, [])
    if parsed not in values:
        values.append(parsed)
    return True


def _add_label(labels, label):
    if label and label not in labels:
        labels.append(label)


def _has_metadata_filter_values(search_filters):
    return any(
        search_filters.get(key)
        for key in ("competence_ids", "product_ids", "area_ids", "subarea_ids", "client_ids")
    )


def _copy_filters(search_filters):
    copied = {}
    for key, value in (search_filters or {}).items():
        if isinstance(value, list):
            copied[key] = list(value)
        else:
            copied[key] = value
    return copied


def _merge_saved_filters(search_filters, user_id, labels):
    available = 0
    filter_map = {
        "C": ("competence_ids", "competenza"),
        "P": ("product_ids", "prodotto"),
        "A": ("area_ids", "area"),
        "S": ("subarea_ids", "sottoarea"),
    }
    for row in filter_store.getFilters(user_id):
        key_label = filter_map.get(str(row[2]))
        if not key_label:
            continue
        key, label = key_label
        if _parse_int_token(row[3]) is None:
            continue
        available += 1
        if _add_filter_value(search_filters, key, row[3]):
            _add_label(labels, f"mio filtro {label}")
        else:
            _add_label(labels, f"mio filtro {label}")
    return available


def _apply_default_saved_filters(search_filters, user_id, labels):
    if _has_metadata_filter_values(search_filters):
        return 0
    return _merge_saved_filters(search_filters, user_id, labels)


def _metadata_filter(search_filters, labels, query_ticket, metadata_key, filter_key, label):
    value = (query_ticket or {}).get(metadata_key)
    if _add_filter_value(search_filters, filter_key, value):
        _add_label(labels, label)
        return True
    return False


def _parse_filter_tokens(tokens, user_id, query_ticket=None):
    search_filters = {}
    labels = []
    i = 0
    while i < len(tokens):
        token = tokens[i].strip().lower().replace("-", "_")
        next_token = tokens[i + 1] if i + 1 < len(tokens) else None

        if token in ("area", "stessa_area"):
            if _looks_like_int(next_token):
                _add_filter_value(search_filters, "area_ids", next_token)
                _add_label(labels, f"area {next_token}")
                i += 2
            else:
                _metadata_filter(search_filters, labels, query_ticket, "area_id", "area_ids", "stessa area")
                i += 1
        elif token in ("sottoarea", "subarea", "sott_area", "stessa_sottoarea"):
            if _looks_like_int(next_token):
                _add_filter_value(search_filters, "subarea_ids", next_token)
                _add_label(labels, f"sottoarea {next_token}")
                i += 2
            else:
                _metadata_filter(search_filters, labels, query_ticket, "subarea_id", "subarea_ids", "stessa sottoarea")
                i += 1
        elif token in ("prodotto", "product", "stesso_prodotto"):
            if _looks_like_int(next_token):
                _add_filter_value(search_filters, "product_ids", next_token)
                _add_label(labels, f"prodotto {next_token}")
                i += 2
            else:
                _metadata_filter(search_filters, labels, query_ticket, "product_id", "product_ids", "stesso prodotto")
                i += 1
        elif token in ("competenza", "competence", "stessa_competenza"):
            if _looks_like_int(next_token):
                _add_filter_value(search_filters, "competence_ids", next_token)
                _add_label(labels, f"competenza {next_token}")
                i += 2
            else:
                _metadata_filter(search_filters, labels, query_ticket, "competence_id", "competence_ids", "stessa competenza")
                i += 1
        elif token in ("cliente", "client", "stesso_cliente"):
            if _looks_like_int(next_token):
                _add_filter_value(search_filters, "client_ids", next_token)
                _add_label(labels, f"cliente {next_token}")
                i += 2
            else:
                _metadata_filter(search_filters, labels, query_ticket, "client_id", "client_ids", "stesso cliente")
                i += 1
        elif token in ("no_cliente", "diverso_cliente", "escludi_cliente"):
            if _looks_like_int(next_token):
                _add_filter_value(search_filters, "exclude_client_ids", next_token)
                _add_label(labels, f"escludi cliente {next_token}")
                i += 2
            else:
                _metadata_filter(search_filters, labels, query_ticket, "client_id", "exclude_client_ids", "escludi stesso cliente")
                i += 1
        elif token in ("filtri", "miei_filtri"):
            if _merge_saved_filters(search_filters, user_id, labels) == 0:
                _add_label(labels, "miei filtri vuoti")
            i += 1
        elif token in ("codice", "code", "errore"):
            search_filters["require_code_match"] = True
            _add_label(labels, "stesso codice")
            i += 1
        elif token in ("recenti", "ultimi", "ultimi_mesi"):
            months = _parse_int_token(next_token) or 24
            search_filters["recent_months"] = max(1, months)
            _add_label(labels, f"ultimi {max(1, months)} mesi")
            i += 2 if _looks_like_int(next_token) else 1
        elif token in ("media", "confidenza_media"):
            search_filters["min_confidence"] = "media"
            _add_label(labels, "confidenza media")
            i += 1
        elif token in ("alta", "confidenza_alta"):
            search_filters["min_confidence"] = "alta"
            _add_label(labels, "confidenza alta")
            i += 1
        else:
            i += 1
    return search_filters, labels


def _parse_ticket_search(text, user_id):
    parts = text.split()
    if len(parts) < 2 or not parts[1].isdigit():
        return None, None, None, "Usa: /tic <numero_ticket> [filtri]"

    ticket_id = int(parts[1])
    query_ticket = mysql5.get_ticket_context(ticket_id)
    if not query_ticket:
        return None, None, None, f"Ticket {ticket_id} non trovato."

    search_filters, labels = _parse_filter_tokens(parts[2:], user_id, query_ticket=query_ticket)
    _apply_default_saved_filters(search_filters, user_id, labels)
    return ticket_id, query_ticket, {"filters": search_filters, "labels": labels}, None


def _parse_text_search(text, user_id):
    raw = text[len("/cerca "):].strip()
    if not raw:
        return None, None, "Usa: /cerca <testo> [--area 12] [--filtri]"

    tokens = raw.split()
    query_parts = []
    search_filters = {}
    labels = []
    i = 0
    while i < len(tokens):
        token = tokens[i].lower()
        next_token = tokens[i + 1] if i + 1 < len(tokens) else None
        if token in ("--area", "--sottoarea", "--subarea", "--prodotto", "--competenza", "--cliente"):
            key_map = {
                "--area": ("area_ids", "area"),
                "--sottoarea": ("subarea_ids", "sottoarea"),
                "--subarea": ("subarea_ids", "sottoarea"),
                "--prodotto": ("product_ids", "prodotto"),
                "--competenza": ("competence_ids", "competenza"),
                "--cliente": ("client_ids", "cliente"),
            }
            key, label = key_map[token]
            if _add_filter_value(search_filters, key, next_token):
                _add_label(labels, f"{label} {next_token}")
                i += 2
            else:
                i += 1
        elif token == "--filtri":
            if _merge_saved_filters(search_filters, user_id, labels) == 0:
                _add_label(labels, "miei filtri vuoti")
            i += 1
        elif token == "--codice":
            search_filters["require_code_match"] = True
            _add_label(labels, "stesso codice")
            i += 1
        elif token == "--ultimi-mesi":
            months = _parse_int_token(next_token) or 24
            search_filters["recent_months"] = max(1, months)
            _add_label(labels, f"ultimi {max(1, months)} mesi")
            i += 2 if _looks_like_int(next_token) else 1
        elif token == "--media":
            search_filters["min_confidence"] = "media"
            _add_label(labels, "confidenza media")
            i += 1
        elif token == "--alta":
            search_filters["min_confidence"] = "alta"
            _add_label(labels, "confidenza alta")
            i += 1
        else:
            query_parts.append(tokens[i])
            i += 1

    query_text = " ".join(query_parts).strip()
    if not query_text:
        return None, None, "Manca il testo da cercare."
    _apply_default_saved_filters(search_filters, user_id, labels)
    return query_text, {"filters": search_filters, "labels": labels}, None


def _cleanup_sessions():
    now = time.time()
    for session_id in list(SEARCH_SESSIONS):
        if now - SEARCH_SESSIONS[session_id].get("created_at", now) > SESSION_TTL_SECONDS:
            del SEARCH_SESSIONS[session_id]


def _store_session(session):
    _cleanup_sessions()
    session_id = uuid.uuid4().hex[:10]
    session["created_at"] = time.time()
    SEARCH_SESSIONS[session_id] = session
    return session_id


def _session_filter_text(session):
    labels = session.get("labels") or []
    return ", ".join(labels) if labels else "nessun filtro stretto"


def _format_suggestion_message(suggestion, rank):
    ticket_id = suggestion["ticket_id"]
    product = suggestion.get("product") or "-"
    competence = suggestion.get("competence") or "-"
    area = suggestion.get("area") or "-"
    subarea = suggestion.get("subarea") or "-"
    client = suggestion.get("client") or "-"
    reasons = ", ".join(suggestion.get("reasons") or [])
    opened = suggestion.get("opened_at")
    opened_text = opened.strftime("%Y-%m-%d") if hasattr(opened, "strftime") else str(opened or "-")
    duplicate_note = ""
    if suggestion.get("duplicates_skipped_before"):
        duplicate_note = f"Alternative simili saltate: {suggestion['duplicates_skipped_before']}\n"

    return (
        f"{rank}. Confidenza {suggestion['confidence']}\n"
        f"Ticket: {ticket_id}\n"
        f"{_ticket_link(ticket_id)}\n"
        f"Cliente: {client}\n"
        f"Prodotto: {product} | Competenza: {competence}\n"
        f"Area: {area} | Sottoarea: {subarea}\n"
        f"Apertura: {opened_text}\n"
        f"Perche: {reasons}\n"
        f"{duplicate_note}"
        f"Score: {suggestion['total_score']:.3f}\n"
        f"SOLUZIONE: {_short(suggestion.get('solution_preview') or 'Soluzione non disponibile')}"
    )


def _suggestion_keyboard(session, suggestion):
    suggested_ticket = suggestion["ticket_id"]
    if session["kind"] == "ticket":
        query_ticket = session["query_ticket_id"]
        return InlineKeyboardMarkup(inline_keyboard=[
            [InlineKeyboardButton(text="Dettaglio", callback_data=f"d:{query_ticket}:{suggested_ticket}")],
            [
                InlineKeyboardButton(text="Utile", callback_data=f"fb:u:{query_ticket}:{suggested_ticket}"),
                InlineKeyboardButton(text="Non utile", callback_data=f"fb:n:{query_ticket}:{suggested_ticket}"),
            ],
            [InlineKeyboardButton(text="Preferito", callback_data=f"fav:{query_ticket}:{suggested_ticket}")],
        ])
    return InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Dettaglio", callback_data=f"D:{suggested_ticket}")],
        [InlineKeyboardButton(text="Preferito", callback_data=f"F:{suggested_ticket}")],
    ])


def _search_controls_keyboard(session_id, session, offset, has_more):
    rows = []
    if has_more:
        rows.append([InlineKeyboardButton(text="Altri risultati", callback_data=f"more:{session_id}:{offset + PAGE_SIZE}")])

    if session["kind"] == "ticket":
        query_ticket = session.get("query_ticket") or {}
        first_row = []
        if query_ticket.get("area_id"):
            first_row.append(InlineKeyboardButton(text="Solo area", callback_data=f"mode:{session_id}:area"))
        if query_ticket.get("subarea_id"):
            first_row.append(InlineKeyboardButton(text="Solo sottoarea", callback_data=f"mode:{session_id}:subarea"))
        if first_row:
            rows.append(first_row)

        second_row = []
        if query_ticket.get("product_id"):
            second_row.append(InlineKeyboardButton(text="Solo prodotto", callback_data=f"mode:{session_id}:product"))
        if query_ticket.get("competence_id"):
            second_row.append(InlineKeyboardButton(text="Solo competenza", callback_data=f"mode:{session_id}:competence"))
        if second_row:
            rows.append(second_row)

        third_row = []
        if query_ticket.get("client_id"):
            third_row.append(InlineKeyboardButton(text="Stesso cliente", callback_data=f"mode:{session_id}:client"))
            third_row.append(InlineKeyboardButton(text="Escludi cliente", callback_data=f"mode:{session_id}:exclude_client"))
        if third_row:
            rows.append(third_row)

    rows.append([
        InlineKeyboardButton(text="Recenti 24m", callback_data=f"mode:{session_id}:recent"),
        InlineKeyboardButton(text="Stesso codice", callback_data=f"mode:{session_id}:code"),
    ])
    rows.append([
        InlineKeyboardButton(text="Min media", callback_data=f"mode:{session_id}:media"),
        InlineKeyboardButton(text="Miei filtri", callback_data=f"mode:{session_id}:saved"),
        InlineKeyboardButton(text="Reset", callback_data=f"mode:{session_id}:reset"),
    ])
    return InlineKeyboardMarkup(inline_keyboard=rows)


def _send_ai_results(chat_id, session_id, offset=0, intro=None):
    session = SEARCH_SESSIONS.get(session_id)
    if not session:
        bot.sendMessage(chat_id, "Ricerca scaduta. Rilancia /tic oppure /cerca.")
        return

    offset = max(0, int(offset or 0))
    search_filters = session.get("filters") or {}
    if session["kind"] == "ticket":
        suggestions = ai_retriever.search_similar_tickets(
            session["query_ticket_id"],
            user_id=session["user_id"],
            limit=PAGE_SIZE,
            offset=offset,
            filters=search_filters,
        )
        title = f"Ticket principale: {_ticket_link(session['query_ticket_id'])}"
    else:
        suggestions = ai_retriever.search_similar_text(
            session["query_text"],
            user_id=session["user_id"],
            limit=PAGE_SIZE,
            offset=offset,
            filters=search_filters,
        )
        title = f"Ricerca testo: {_short(session['query_text'], 120)}"

    if intro or offset == 0:
        _send_chunks(chat_id, f"{intro or 'Risultati AI'}\n{title}\nFiltro: {_session_filter_text(session)}")

    if not suggestions:
        bot.sendMessage(
            chat_id,
            "Non ho trovato risultati con questi parametri.",
            reply_markup=_search_controls_keyboard(session_id, session, offset, False),
        )
        return

    for index, suggestion in enumerate(suggestions, start=1):
        rank = offset + index
        bot.sendMessage(
            chat_id,
            _format_suggestion_message(suggestion, rank),
            reply_markup=_suggestion_keyboard(session, suggestion),
        )

    bot.sendMessage(
        chat_id,
        "Azioni ricerca",
        reply_markup=_search_controls_keyboard(session_id, session, offset, len(suggestions) == PAGE_SIZE),
    )


def _mode_session(session, mode):
    new_session = dict(session)
    new_session["filters"] = _copy_filters(session.get("filters"))
    new_session["labels"] = list(session.get("labels") or [])
    query_ticket = session.get("query_ticket") or {}

    if mode == "reset":
        new_session["filters"] = {}
        new_session["labels"] = []
        return new_session, "Filtri rimossi."
    if mode == "area":
        ok = _metadata_filter(new_session["filters"], new_session["labels"], query_ticket, "area_id", "area_ids", "stessa area")
    elif mode == "subarea":
        ok = _metadata_filter(new_session["filters"], new_session["labels"], query_ticket, "subarea_id", "subarea_ids", "stessa sottoarea")
    elif mode == "product":
        ok = _metadata_filter(new_session["filters"], new_session["labels"], query_ticket, "product_id", "product_ids", "stesso prodotto")
    elif mode == "competence":
        ok = _metadata_filter(new_session["filters"], new_session["labels"], query_ticket, "competence_id", "competence_ids", "stessa competenza")
    elif mode == "client":
        ok = _metadata_filter(new_session["filters"], new_session["labels"], query_ticket, "client_id", "client_ids", "stesso cliente")
    elif mode == "exclude_client":
        ok = _metadata_filter(new_session["filters"], new_session["labels"], query_ticket, "client_id", "exclude_client_ids", "escludi stesso cliente")
    elif mode == "recent":
        new_session["filters"]["recent_months"] = 24
        _add_label(new_session["labels"], "ultimi 24 mesi")
        ok = True
    elif mode == "code":
        new_session["filters"]["require_code_match"] = True
        _add_label(new_session["labels"], "stesso codice")
        ok = True
    elif mode == "media":
        new_session["filters"]["min_confidence"] = "media"
        _add_label(new_session["labels"], "confidenza media")
        ok = True
    elif mode == "saved":
        ok = _merge_saved_filters(new_session["filters"], session["user_id"], new_session["labels"]) > 0
    else:
        return None, "Filtro non riconosciuto."

    if not ok:
        return None, "Filtro non disponibile per questa ricerca."
    return new_session, "Filtro applicato."


def _send_ticket_detail(chat_id, ticket_id, actor_id=None, first_name="", last_name="", query_ticket_id=None):
    bot.sendMessage(chat_id, f"Attendere per favore, sto caricando i dettagli per il ticket <b>{ticket_id}</b> ....", parse_mode="HTML")

    if query_ticket_id is not None:
        try:
            ai_retriever.record_feedback(actor_id or chat_id, query_ticket_id, ticket_id, "opened")
        except Exception:
            traceback.print_exc()

    if actor_id is not None and not _is_admin(actor_id):
        bot.sendMessage(
            config.TELEGRAM_ADMIN_ID,
            "Aperto Dettaglio Ticket nr: "
            + str(ticket_id)
            + " [ "
            + str(first_name)
            + " "
            + str(last_name)
            + " "
            + str(actor_id)
            + " ]",
        )

    ready_file = ticketToday.getDetailTicket(ticket_id)
    html_file = "json/" + str(ticket_id) + "_details.html"
    if ready_file and os.path.exists(html_file):
        with open(html_file, "rb") as document:
            bot.sendDocument(chat_id=chat_id, document=document, parse_mode="HTML")
        os.remove(html_file)
    else:
        bot.sendMessage(chat_id, "Non è stato possibile scaricare i dettagli")


def _handle_ticket_search(chat_id, user_id, text, first_name, last_name):
    ticket_id, query_ticket, parsed, error = _parse_ticket_search(text, user_id)
    if error:
        bot.sendMessage(chat_id, error)
        return

    bot.sendMessage(chat_id, "Attendere per favore sto cercando soluzioni...")
    session_id = _store_session({
        "kind": "ticket",
        "user_id": user_id,
        "query_ticket_id": ticket_id,
        "query_ticket": query_ticket,
        "filters": parsed["filters"],
        "labels": parsed["labels"],
    })
    _send_ai_results(chat_id, session_id, offset=0, intro="Risultati AI")

    if int(chat_id) != int(config.TELEGRAM_ADMIN_ID):
        bot.sendMessage(config.TELEGRAM_ADMIN_ID, "Manuale Da " + str(ticket_id) + " " + str(first_name) + " " + str(last_name))


def _handle_text_search(chat_id, user_id, text):
    query_text, parsed, error = _parse_text_search(text, user_id)
    if error:
        bot.sendMessage(chat_id, error)
        return

    bot.sendMessage(chat_id, "Attendere per favore sto cercando soluzioni...")
    session_id = _store_session({
        "kind": "text",
        "user_id": user_id,
        "query_text": query_text,
        "filters": parsed["filters"],
        "labels": parsed["labels"],
    })
    _send_ai_results(chat_id, session_id, offset=0, intro="Risultati AI")


def _handle_feedback_command(chat_id, text, feedback):
    prefix = "/utile_" if feedback == "useful" else "/non_utile_"
    payload = str(text)[len(prefix):]
    parts = payload.split("_", 1)
    if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
        ai_retriever.record_feedback(chat_id, parts[0], parts[1], feedback)
        bot.sendMessage(chat_id, "Feedback registrato: soluzione utile." if feedback == "useful" else "Feedback registrato: soluzione non utile.")
    else:
        bot.sendMessage(chat_id, "Feedback non valido.")


def _handle_favorite_command(chat_id, user_id, text):
    ticket_id = str(text)[len("/preferito_"):].strip()
    if not ticket_id.isdigit():
        bot.sendMessage(chat_id, "Preferito non valido.")
        return

    try:
        inserted = ai_retriever.record_favorite(user_id or chat_id, ticket_id)
    except Exception:
        traceback.print_exc()
        bot.sendMessage(chat_id, "Non e' stato possibile salvare il preferito.")
        return

    bot.sendMessage(chat_id, "Ticket aggiunto ai preferiti." if inserted else "Ticket gia' nei preferiti.")
    if int(chat_id) != int(config.TELEGRAM_ADMIN_ID):
        bot.sendMessage(config.TELEGRAM_ADMIN_ID, "Preferito " + str(ticket_id) + " Da " + str(chat_id))


def _format_settings(settings):
    enabled = "attive" if settings.get("notifications_enabled", True) else "in pausa"
    quiet = "nessuno"
    if settings.get("quiet_start_hour") is not None and settings.get("quiet_end_hour") is not None:
        quiet = f"{settings['quiet_start_hour']:02d}:00-{settings['quiet_end_hour']:02d}:00"
    digest = "immediato"
    if int(settings.get("digest_minutes") or 0) > 0:
        digest = f"ogni {settings['digest_minutes']} minuti"
    gravity = "solo alta" if settings.get("high_gravity_only") else "tutte"
    return (
        "Stato notifiche\n"
        f"Notifiche: {enabled}\n"
        f"Silenzio: {quiet}\n"
        f"Digest: {digest}\n"
        f"Gravita: {gravity}"
    )


def _handle_notification_command(chat_id, user_id, text):
    if text == "/notifiche_on":
        settings = ai_retriever.set_notifications_enabled(user_id, True)
        bot.sendMessage(chat_id, _format_settings(settings))
        return True
    if text == "/notifiche_off":
        settings = ai_retriever.set_notifications_enabled(user_id, False)
        bot.sendMessage(chat_id, _format_settings(settings))
        return True
    if text == "/notifiche_status":
        bot.sendMessage(chat_id, _format_settings(ai_retriever.get_user_bot_settings(user_id)))
        return True
    if text == "/silenzio_off":
        bot.sendMessage(chat_id, _format_settings(ai_retriever.clear_quiet_hours(user_id)))
        return True
    if text.startswith("/silenzio "):
        parts = text.split()
        if len(parts) == 3 and _looks_like_int(parts[1]) and _looks_like_int(parts[2]):
            settings = ai_retriever.set_quiet_hours(user_id, int(parts[1]), int(parts[2]))
            bot.sendMessage(chat_id, _format_settings(settings))
        else:
            bot.sendMessage(chat_id, "Usa: /silenzio <ora_inizio> <ora_fine>, esempio /silenzio 22 7")
        return True
    if text == "/digest_off":
        bot.sendMessage(chat_id, _format_settings(ai_retriever.set_digest_minutes(user_id, 0)))
        return True
    if text.startswith("/digest "):
        parts = text.split()
        if len(parts) == 2 and _looks_like_int(parts[1]):
            settings = ai_retriever.set_digest_minutes(user_id, int(parts[1]))
            bot.sendMessage(chat_id, _format_settings(settings))
        else:
            bot.sendMessage(chat_id, "Usa: /digest <minuti>, esempio /digest 30")
        return True
    if text == "/gravita_alta_on":
        bot.sendMessage(chat_id, _format_settings(ai_retriever.set_high_gravity_only(user_id, True)))
        return True
    if text == "/gravita_alta_off":
        bot.sendMessage(chat_id, _format_settings(ai_retriever.set_high_gravity_only(user_id, False)))
        return True
    return False


def _format_dt(value):
    if not value:
        return "-"
    if hasattr(value, "strftime"):
        return value.strftime("%Y-%m-%d %H:%M")
    return str(value)


def _format_favorite(row):
    ticket_id = row.get("ticket_id")
    lines = [
        f"🎫 /Ticket_dettaglio_{ticket_id}",
        _ticket_link(ticket_id),
    ]
    metadata = []
    if row.get("product"):
        metadata.append(str(row.get("product")))
    if row.get("area"):
        metadata.append(str(row.get("area")))
    if metadata:
        lines.append(" | ".join(metadata))
    if row.get("subject_text"):
        lines.append("Oggetto: " + _short(row.get("subject_text"), 180))
    lines.append("Salvato: " + _format_dt(row.get("created_at")))
    return "\n".join(lines)


def _send_user_favorites(chat_id, user_id):
    try:
        rows = ai_retriever.get_user_favorites(user_id or chat_id)
    except Exception:
        traceback.print_exc()
        bot.sendMessage(chat_id, "Non e' stato possibile caricare i preferiti.")
        return

    if not rows:
        bot.sendMessage(chat_id, "Non hai ancora ticket preferiti.")
        return

    lines = ["I tuoi preferiti"]
    lines.extend(_format_favorite(row) for row in rows)
    _send_chunks(chat_id, "\n\n".join(lines))


def _send_admin_report(chat_id, command):
    if command == "/ai_stats":
        stats = ai_retriever.get_ai_stats()
        feedback = stats.get("feedback_counts") or {}
        message = (
            "AI stats\n"
            f"Ticket indicizzati: {stats.get('indexed_count', 0)}\n"
            f"Ultimo indice: {_format_dt(stats.get('last_indexed_at'))}\n"
            f"Ricerche: {stats.get('search_count', 0)}\n"
            f"Senza risultati: {stats.get('no_result_count', 0)}\n"
            f"Tempo medio: {stats.get('avg_elapsed_ms', 0)} ms\n"
            f"Feedback utile: {feedback.get('useful', 0)}\n"
            f"Feedback non utile: {feedback.get('not_useful', 0)}\n"
            f"Preferiti: {feedback.get('favorite', 0)}\n"
            f"Dettagli aperti: {feedback.get('opened', 0)}"
        )
        bot.sendMessage(chat_id, message)
        return

    if command == "/ai_no_result":
        rows = ai_retriever.get_no_result_searches()
        if not rows:
            bot.sendMessage(chat_id, "Nessuna ricerca senza risultati.")
            return
        lines = ["Ultime ricerche senza risultati"]
        for row in rows:
            ref = row.get("query_ticket_id") or row.get("query_kind")
            lines.append(f"{_format_dt(row.get('created_at'))} | {ref} | {_short(row.get('query_text'), 120)}")
        _send_chunks(chat_id, "\n".join(lines))
        return

    if command == "/ai_negative":
        rows = ai_retriever.get_negative_feedback()
        if not rows:
            bot.sendMessage(chat_id, "Nessun feedback negativo.")
            return
        lines = ["Ultimi feedback negativi"]
        for row in rows:
            lines.append(
                f"{_format_dt(row.get('created_at'))} | query {row.get('query_ticket_id')} -> {row.get('suggested_ticket_id')} | "
                f"{row.get('product') or '-'} | {row.get('area') or '-'} | {_short(row.get('subject_text'), 80)}"
            )
        _send_chunks(chat_id, "\n".join(lines))
        return

    if command == "/ai_top_useful":
        rows = ai_retriever.get_top_useful()
        if not rows:
            bot.sendMessage(chat_id, "Nessun feedback utile registrato.")
            return
        lines = ["Ticket piu utili"]
        for row in rows:
            lines.append(
                f"{row.get('ticket_id')} | +{row.get('positive_count')} -{row.get('negative_count')} | "
                f"{row.get('product') or '-'} | {row.get('area') or '-'} | {_short(row.get('subject_text'), 80)}"
            )
        _send_chunks(chat_id, "\n".join(lines))


def _send_help(chat_id):
    _send_chunks(
        chat_id,
        "Help bot ticket\n"
        "\n"
        "Importante: i nuovi ticket automatici arrivano solo in base ai filtri salvati. Anche /tic e /cerca usano i filtri salvati se non indichi un filtro diverso.\n"
        "Filtri diversi si sommano: Area + Competenza + Prodotto devono combaciare tutti. Piu valori dello stesso tipo sono alternative.\n"
        "\n"
        "Avvio\n"
        "/start - registra o riapre il menu\n"
        "/help - mostra questo aiuto\n"
        "\n"
        "Filtri\n"
        "Usa i pulsanti del menu per creare filtri per Competenza, Prodotto, Area e Sott.Area.\n"
        "/_id_c_<id> - salva filtro competenza\n"
        "/_id_p_<id> - salva filtro prodotto\n"
        "/_id_area_<id> - salva filtro area\n"
        "/_id_suba_<id> - salva filtro sottoarea\n"
        "📌 I mie filtri - mostra i filtri salvati\n"
        "✖ Cancella filtro - mostra i comandi per eliminarli\n"
        "/Competen_id_cf_<id>, /Prodotto_id_cf_<id>, /Area_id_cf_<id>, /Sottoarea_id_cf_<id> - elimina filtro\n"
        "\n"
        "Ricerca soluzioni\n"
        "/tic <ticket> - cerca ticket simili usando i tuoi filtri salvati\n"
        "/tic <ticket> area - solo stessa area del ticket\n"
        "/tic <ticket> sottoarea - solo stessa sottoarea\n"
        "/tic <ticket> prodotto - solo stesso prodotto\n"
        "/tic <ticket> competenza - solo stessa competenza\n"
        "/tic <ticket> cliente - solo stesso cliente\n"
        "/tic <ticket> no_cliente - esclude stesso cliente\n"
        "/tic <ticket> area <id> - usa area specifica\n"
        "/tic <ticket> prodotto <id> - usa prodotto specifico\n"
        "/tic <ticket> competenza <id> - usa competenza specifica\n"
        "/tic <ticket> recenti <mesi> - limita ai ticket recenti\n"
        "/tic <ticket> codice - richiede stesso codice errore\n"
        "/tic <ticket> media oppure alta - alza la confidenza minima\n"
        "/cerca <testo> - cerca da testo libero usando i filtri salvati\n"
        "/cerca --area <id> --prodotto <id> --competenza <id> <testo> - cerca con filtri specifici\n"
        "/cerca --filtri <testo> - forza i filtri salvati\n"
        "/Ticket_dettaglio_<ticket> - scarica il dettaglio ticket\n"
        "\n"
        "Feedback e preferiti\n"
        "/utile_<ticket_origine>_<ticket_suggerito> - segna soluzione utile\n"
        "/non_utile_<ticket_origine>_<ticket_suggerito> - segna soluzione non utile\n"
        "/preferito_<ticket> - salva un ticket nei preferiti\n"
        "/preferiti - mostra i tuoi preferiti\n"
        "\n"
        "Notifiche\n"
        "/notifiche_status - mostra stato notifiche\n"
        "/notifiche_on - attiva notifiche\n"
        "/notifiche_off - mette in pausa notifiche\n"
        "/silenzio <ora_inizio> <ora_fine> - pausa in una fascia oraria, esempio /silenzio 22 7\n"
        "/silenzio_off - rimuove la fascia silenzio\n"
        "/digest <minuti> - raggruppa notifiche, esempio /digest 30\n"
        "/digest_off - notifiche immediate\n"
        "/gravita_alta_on - solo ticket con gravita alta\n"
        "/gravita_alta_off - tutte le gravita",
    )


def on_chat_message(msg):
    try:
        first_name = msg.get("from", {}).get("first_name", "")
        last_name = msg.get("from", {}).get("last_name", "noname")
        user_id = msg.get("from", {}).get("id")
        content_type, chat_type, chat_id = telepot.glance(msg)
        print("Chat Message:", last_name, first_name, chat_id)
        print(content_type)

        if _is_pending_user(user_id):
            bot.sendMessage(user_id, "In attesa che un amministratore accetti la sua richiesta \n Per favore attendere a breve sarà approvata")
            bot.sendMessage(config.TELEGRAM_ADMIN_ID, "Utente nuovo: [ " + str(user_id) + " " + first_name + " " + last_name + " ] Approva qui: \n" + "/Approval_" + str(chat_id))
            return

        if content_type != "text":
            return

        text = str(msg.get("text", "")).strip()

        if text == "/start":
            keyboard = keyboards.getKeyboard()
            if user.insertUser(user_id, first_name, last_name):
                bot.sendMessage(chat_id, "Registrazione effettuata correttamente. \n Per favore attendere che un amministratore approvi la sua richiesta")
                bot.sendDocument(chat_id=chat_id, document=open("gifs/welcome.gif", "rb"))
                bot.sendMessage(config.TELEGRAM_ADMIN_ID, "Utente nuovo: [ " + str(user_id) + " " + first_name + " " + last_name + " ] Approva qui: \n" + "/Approval_" + str(chat_id))
                return
            bot.sendMessage(chat_id, "Sei nel menu principale", reply_markup=keyboard)

        elif text in ("/help", "Aiuto"):
            _send_help(chat_id)

        elif text.startswith("/Approval_"):
            if not _is_admin(user_id):
                bot.sendMessage(chat_id, "Comando riservato agli amministratori.")
                return
            telegram_id = text[len("/Approval_"):]
            is_user_approved, already_approved = user.approveUser(telegram_id)
            if already_approved and already_approved[0][3] == "A":
                bot.sendMessage(chat_id, "Utente è stato approvato già precedentemente!")
                return
            if is_user_approved:
                keyboard = keyboards.getKeyboard()
                bot.sendMessage(int(telegram_id), "L'approvazione avvenuta con successo. \n Adesso può usare il menu principale", reply_markup=keyboard)
                bot.sendMessage(chat_id, "Utente approvato correttamente")

        elif text == "🖍 Crea filtro x Competenze":
            comp_first, comp_second = filter_ui.get_competences()
            bot.sendMessage(chat_id, comp_first)
            bot.sendMessage(chat_id, comp_second)

        elif text == "📦 Crea filtro x Prodotto":
            bot.sendMessage(chat_id, filter_ui.get_products())

        elif text == "👥 Crea filtro x Area":
            bot.sendMessage(chat_id, filter_ui.get_areas())

        elif text == "👥 Crea filtro x Sott.Area":
            bot.sendMessage(chat_id, filter_ui.get_sub_areas(chat_id))

        elif text in ("💜 Preferiti", "/preferiti"):
            _send_user_favorites(chat_id, user_id)

        elif text.startswith("/tic "):
            _handle_ticket_search(chat_id, user_id, text, first_name, last_name)

        elif text.startswith("/cerca "):
            _handle_text_search(chat_id, user_id, text)

        elif text.startswith("/utile_"):
            _handle_feedback_command(chat_id, text, "useful")

        elif text.startswith("/non_utile_"):
            _handle_feedback_command(chat_id, text, "not_useful")

        elif text.startswith("/preferito_"):
            _handle_favorite_command(chat_id, user_id, text)

        elif text.startswith("/_id_c_"):
            bot.sendMessage(chat_id, insertCompetence.manageCompetence(chat_id, text[7:], "C"))

        elif text.startswith("/_id_p_"):
            bot.sendMessage(chat_id, insertCompetence.manageCompetence(chat_id, text[7:], "P"))

        elif text.startswith("/_id_area_"):
            bot.sendMessage(chat_id, insertCompetence.manageCompetence(chat_id, text[10:], "A"))

        elif text.startswith("/_id_suba_"):
            bot.sendMessage(chat_id, insertCompetence.manageCompetence(chat_id, text[10:], "S"))

        elif text == "📌 I mie filtri":
            filters_list = filter_ui.getMyFilters(chat_id)
            bot.sendMessage(chat_id, filters_list if len(filters_list) > 0 else "Non hai creato ancora un filtro!")

        elif text == "✖ Cancella filtro":
            filters_to_delete = filter_ui.showFilterToBeDelete(chat_id)
            bot.sendMessage(chat_id, filters_to_delete if len(filters_to_delete) > 0 else "Non esistono filtri da cancellare")

        elif DELETE_FILTER_RE.match(text):
            match = DELETE_FILTER_RE.match(text)
            bot.sendMessage(chat_id, filter_ui.deleteFilter(chat_id, match.group(2)))

        elif text.startswith("/Ticket_dettaglio_"):
            ticket_id = text[len("/Ticket_dettaglio_"):]
            _send_ticket_detail(chat_id, ticket_id, actor_id=user_id, first_name=first_name, last_name=last_name)

        elif text in ("/ai_stats", "/ai_no_result", "/ai_negative", "/ai_top_useful"):
            if not _is_admin(user_id):
                bot.sendMessage(chat_id, "Comando riservato agli amministratori.")
                return
            _send_admin_report(chat_id, text)

        elif _handle_notification_command(chat_id, user_id, text):
            return

        else:
            bot.sendMessage(chat_id, "Commando non riconosciuto! Premere /start per iniziare.")
    except Exception:
        traceback.print_exc()
        print("Error in on_chat_message")


def on_callback_query(msg):
    try:
        query_id, from_id, query_data = telepot.glance(msg, flavor="callback_query")
        bot.answerCallbackQuery(query_id, text="Ricevuto")
        print(query_data)

        if query_data.startswith("d:"):
            _, query_ticket, suggested_ticket = query_data.split(":", 2)
            _send_ticket_detail(from_id, suggested_ticket, actor_id=from_id, query_ticket_id=query_ticket)
        elif query_data.startswith("D:"):
            _send_ticket_detail(from_id, query_data[2:], actor_id=from_id)
        elif query_data.startswith("fb:"):
            _, feedback_value, query_ticket, suggested_ticket = query_data.split(":", 3)
            feedback = "useful" if feedback_value == "u" else "not_useful"
            ai_retriever.record_feedback(from_id, query_ticket, suggested_ticket, feedback)
            bot.sendMessage(from_id, "Feedback registrato: soluzione utile." if feedback == "useful" else "Feedback registrato: soluzione non utile.")
        elif query_data.startswith("fav:"):
            _, query_ticket, suggested_ticket = query_data.split(":", 2)
            inserted = ai_retriever.record_favorite(from_id, suggested_ticket, query_ticket_id=query_ticket)
            bot.sendMessage(from_id, "Ticket aggiunto ai preferiti." if inserted else "Ticket gia' nei preferiti.")
        elif query_data.startswith("F:"):
            inserted = ai_retriever.record_favorite(from_id, query_data[2:])
            bot.sendMessage(from_id, "Ticket aggiunto ai preferiti." if inserted else "Ticket gia' nei preferiti.")
        elif query_data.startswith("more:"):
            _, session_id, offset = query_data.split(":", 2)
            _send_ai_results(from_id, session_id, offset=int(offset), intro="Altri risultati AI")
        elif query_data.startswith("mode:"):
            _, session_id, mode = query_data.split(":", 2)
            session = SEARCH_SESSIONS.get(session_id)
            if not session:
                bot.sendMessage(from_id, "Ricerca scaduta. Rilancia /tic oppure /cerca.")
                return
            new_session, message = _mode_session(session, mode)
            if not new_session:
                bot.sendMessage(from_id, message)
                return
            new_session_id = _store_session(new_session)
            _send_ai_results(from_id, new_session_id, offset=0, intro=message)
    except Exception:
        traceback.print_exc()
        print("Error in on_callback_query")


bot = telepot.Bot(config.TELEGRAM_BOT_TOKEN)


try:
    MessageLoop(bot, {"chat": on_chat_message, "callback_query": on_callback_query}).run_as_thread()
    print("Listening ...")

    while 1:
        if config.ENABLE_NOTIFICATIONS:
            print("Passati 30 Secondi")
            tids = user.getUsers()
            for u in tids:
                if u is None or u[0] is None:
                    continue
                target_user_id = int(u[0])
                if not ai_retriever.should_send_notifications(target_user_id):
                    continue
                settings = ai_retriever.get_user_bot_settings(target_user_id)
                new_ticket_list = ticketToday.getTicketTodayForNotification(
                    target_user_id,
                    high_gravity_only=bool(settings.get("high_gravity_only")),
                )
                sent_any = False
                for ticket in new_ticket_list:
                    message = ticket
                    prefix = "🎫 /Ticket_dettaglio_"
                    ticket_value = ticket.split(prefix, 1)[-1].split("\n", 1)[0].strip()
                    saved_filters = {}
                    saved_labels = []
                    _merge_saved_filters(saved_filters, target_user_id, saved_labels)
                    best_matches_indices = phrase_similarity.start_guessing_manual(
                        ticket_value,
                        user_id=target_user_id,
                        filters=saved_filters,
                        limit=3,
                    )
                    for ticket_info in best_matches_indices:
                        message += str(ticket_info)

                    bot.sendMessage(target_user_id, str(message))
                    sent_any = True
                if sent_any:
                    ai_retriever.mark_notification_sent(target_user_id)
        else:
            print("Notification scan disabled")

        time.sleep(30)
except Exception:
    traceback.print_exc()
