#!/usr/bin/env python3
"""
Survey Telegram handlers — inline keyboard flow for user feedback surveys.

Callback data format:
  survey_start:<survey_id>
  survey_ans:<inv_id>:<q_key>:<value>
  survey_tog:<inv_id>:<q_key>:<value>
  survey_done:<inv_id>:<q_key>
  survey_skip:<inv_id>:<q_key>
"""

import json
import logging
from pathlib import Path
from typing import Optional, Tuple

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, Bot
from telegram.ext import CallbackQueryHandler, ContextTypes

from database import get_db_path, create_gift_pack
from survey_db import (
    get_survey,
    get_invitation,
    get_invitation_by_id,
    create_invitation,
    update_invitation_status,
    mark_invitation_rewarded,
    save_response,
    get_user_responses,
)

logger = logging.getLogger(__name__)


# =============================================================================
# Send invite
# =============================================================================

async def send_survey_invite(
    bot: Bot,
    user_id: int,
    survey_id: int,
    db_path: Optional[Path] = None,
) -> bool:
    """Send survey invitation message to a user. Returns True if sent."""
    survey = get_survey(survey_id, db_path)
    if not survey or not survey["is_active"]:
        return False

    inv_id = create_invitation(survey_id, user_id, db_path)
    if inv_id is None:
        return False  # duplicate

    keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton("Начать опрос", callback_data=f"survey_start:{survey_id}")]
    ])

    try:
        await bot.send_message(
            chat_id=user_id,
            text=(
                "📋 *Помогите нам стать лучше!*\n\n"
                "Ответьте на 7 коротких вопросов (~1 мин)\n"
                "и получите 🎁 *3 бесплатных генерации!*"
            ),
            reply_markup=keyboard,
            parse_mode="Markdown",
        )
        return True
    except Exception as e:
        logger.warning(f"Failed to send survey invite to {user_id}: {e}")
        return False


# =============================================================================
# Advancing to the next question (shared logic)
# =============================================================================

async def _advance_question(
    query,
    context: ContextTypes.DEFAULT_TYPE,
    inv_id: int,
    survey: dict,
    db_path: Path,
):
    """Find and show the next unanswered question, or complete the survey."""
    questions = json.loads(survey["questions_json"])
    responses = get_user_responses(inv_id, db_path)
    next_step = _find_next_step(questions, responses)

    if next_step is None:
        await _complete_survey(query, context, inv_id, survey, db_path)
        return

    q = questions[next_step]
    text, keyboard = _render_question(inv_id, questions, next_step, responses)

    # Set up pending text handler if this is a text question
    if q["type"] == "text":
        context.user_data["survey_pending_text"] = {
            "inv_id": inv_id,
            "q_key": q["key"],
            "survey_id": survey["id"],
        }
    else:
        context.user_data.pop("survey_pending_text", None)

    await query.edit_message_text(text, reply_markup=keyboard, parse_mode="Markdown")


# =============================================================================
# Callback handlers
# =============================================================================

async def handle_survey_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """User clicked 'Начать опрос'."""
    query = update.callback_query
    await query.answer()

    survey_id = int(query.data.split(":")[1])
    user_id = update.effective_user.id
    db_path = get_db_path()

    survey = get_survey(survey_id, db_path)
    if not survey:
        await query.edit_message_text("❌ Опрос не найден.")
        return

    inv = get_invitation(survey_id, user_id, db_path)
    if not inv:
        await query.edit_message_text("❌ Приглашение не найдено.")
        return

    if inv["status"] == "completed":
        await query.edit_message_text("✅ Вы уже прошли этот опрос. Спасибо!")
        return

    # Mark as started
    if inv["status"] == "sent":
        update_invitation_status(inv["id"], "started", db_path)

    await _advance_question(query, context, inv["id"], survey, db_path)


async def handle_survey_answer(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Handle single-choice / rating / nps answer."""
    query = update.callback_query
    await query.answer()

    # survey_ans:<inv_id>:<q_key>:<value>
    parts = query.data.split(":", 3)
    inv_id = int(parts[1])
    q_key = parts[2]
    value = parts[3]

    db_path = get_db_path()
    inv = get_invitation_by_id(inv_id, db_path)
    if not inv or inv["status"] == "completed":
        return

    survey = get_survey(inv["survey_id"], db_path)
    if not survey:
        return

    save_response(inv_id, inv["survey_id"], inv["user_id"], q_key, value, db_path)
    await _advance_question(query, context, inv_id, survey, db_path)


async def handle_survey_multi_toggle(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Toggle a multi-select option."""
    query = update.callback_query
    await query.answer()

    # survey_tog:<inv_id>:<q_key>:<value>
    parts = query.data.split(":", 3)
    inv_id = int(parts[1])
    q_key = parts[2]
    value = parts[3]

    db_path = get_db_path()
    inv = get_invitation_by_id(inv_id, db_path)
    if not inv or inv["status"] == "completed":
        return

    survey = get_survey(inv["survey_id"], db_path)
    if not survey:
        return

    responses = get_user_responses(inv_id, db_path)
    current = responses.get(q_key, "")
    selected = set(current.split("|")) if current else set()

    if value in selected:
        selected.discard(value)
    else:
        selected.add(value)

    selected.discard("")
    new_value = "|".join(sorted(selected))
    save_response(inv_id, inv["survey_id"], inv["user_id"], q_key, new_value, db_path)

    # Re-render same question with updated selections
    questions = json.loads(survey["questions_json"])
    responses = get_user_responses(inv_id, db_path)

    step = next(i for i, q in enumerate(questions) if q["key"] == q_key)
    text, keyboard = _render_question(inv_id, questions, step, responses)
    await query.edit_message_text(text, reply_markup=keyboard, parse_mode="Markdown")


async def handle_survey_multi_done(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Finish multi-select question."""
    query = update.callback_query
    await query.answer()

    # survey_done:<inv_id>:<q_key>
    parts = query.data.split(":")
    inv_id = int(parts[1])
    q_key = parts[2]

    db_path = get_db_path()
    inv = get_invitation_by_id(inv_id, db_path)
    if not inv or inv["status"] == "completed":
        return

    survey = get_survey(inv["survey_id"], db_path)
    if not survey:
        return

    responses = get_user_responses(inv_id, db_path)

    # Ensure at least something is saved for this question
    if q_key not in responses:
        save_response(inv_id, inv["survey_id"], inv["user_id"], q_key, "", db_path)

    await _advance_question(query, context, inv_id, survey, db_path)


async def handle_survey_skip(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Skip an optional question."""
    query = update.callback_query
    await query.answer()

    # survey_skip:<inv_id>:<q_key>
    parts = query.data.split(":")
    inv_id = int(parts[1])
    q_key = parts[2]

    db_path = get_db_path()
    inv = get_invitation_by_id(inv_id, db_path)
    if not inv or inv["status"] == "completed":
        return

    survey = get_survey(inv["survey_id"], db_path)
    if not survey:
        return

    save_response(inv_id, inv["survey_id"], inv["user_id"], q_key, "(пропущено)", db_path)

    context.user_data.pop("survey_pending_text", None)
    await _advance_question(query, context, inv_id, survey, db_path)


async def handle_survey_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
    """Handle free-text answer. Returns True if handled, False otherwise."""
    pending = context.user_data.get("survey_pending_text")
    if not pending:
        return False

    text = update.message.text.strip()
    inv_id = pending["inv_id"]
    q_key = pending["q_key"]
    survey_id = pending["survey_id"]

    db_path = get_db_path()
    inv = get_invitation_by_id(inv_id, db_path)
    if not inv or inv["status"] == "completed":
        context.user_data.pop("survey_pending_text", None)
        return True

    survey = get_survey(survey_id, db_path)
    if not survey:
        context.user_data.pop("survey_pending_text", None)
        return True

    # "." means skip
    answer = "(пропущено)" if text == "." else text
    save_response(inv_id, survey_id, inv["user_id"], q_key, answer, db_path)
    context.user_data.pop("survey_pending_text", None)

    questions = json.loads(survey["questions_json"])
    responses = get_user_responses(inv_id, db_path)
    next_step = _find_next_step(questions, responses)

    if next_step is None:
        await _complete_survey_message(update.message, context, inv_id, survey, db_path)
    else:
        q = questions[next_step]
        msg_text, keyboard = _render_question(inv_id, questions, next_step, responses)

        if q["type"] == "text":
            context.user_data["survey_pending_text"] = {
                "inv_id": inv_id,
                "q_key": q["key"],
                "survey_id": survey_id,
            }

        await update.message.reply_text(msg_text, reply_markup=keyboard, parse_mode="Markdown")

    return True


# =============================================================================
# Rendering
# =============================================================================

def _find_next_step(questions: list, responses: dict) -> Optional[int]:
    """Find the index of the next unanswered question, or None if all done."""
    for i, q in enumerate(questions):
        if q["key"] not in responses:
            return i
    return None


def _render_question(
    inv_id: int, questions: list, step: int, responses: dict
) -> Tuple[str, InlineKeyboardMarkup]:
    """Build message text + InlineKeyboardMarkup for the current question."""
    q = questions[step]
    total = len(questions)
    q_key = q["key"]
    q_type = q["type"]

    text = f"📋 *Вопрос {step + 1} из {total}*\n\n{q['text']}"

    buttons = []

    if q_type == "rating":
        row = [
            InlineKeyboardButton(str(v), callback_data=f"survey_ans:{inv_id}:{q_key}:{v}")
            for v in range(q["min"], q["max"] + 1)
        ]
        buttons.append(row)

    elif q_type == "single":
        for i, opt in enumerate(q["options"]):
            buttons.append([
                InlineKeyboardButton(opt, callback_data=f"survey_ans:{inv_id}:{q_key}:{i}")
            ])

    elif q_type == "multi":
        current = responses.get(q_key, "")
        selected = set(current.split("|")) if current else set()
        selected.discard("")
        for i, opt in enumerate(q["options"]):
            idx = str(i)
            check = "✅" if idx in selected else "⬜"
            buttons.append([
                InlineKeyboardButton(
                    f"{check} {opt}",
                    callback_data=f"survey_tog:{inv_id}:{q_key}:{i}",
                )
            ])
        buttons.append([
            InlineKeyboardButton("Готово →", callback_data=f"survey_done:{inv_id}:{q_key}")
        ])

    elif q_type == "nps":
        text += f"\n_{q.get('label', '')}_"
        row1 = [
            InlineKeyboardButton(str(v), callback_data=f"survey_ans:{inv_id}:{q_key}:{v}")
            for v in range(6, 11)
        ]
        row2 = [
            InlineKeyboardButton(str(v), callback_data=f"survey_ans:{inv_id}:{q_key}:{v}")
            for v in range(0, 6)
        ]
        buttons.append(row1)
        buttons.append(row2)

    elif q_type == "text":
        text += "\n\n_Напишите ответ текстом или отправьте точку (.) чтобы пропустить._"

    return text, InlineKeyboardMarkup(buttons)


# =============================================================================
# Completion
# =============================================================================

async def _complete_survey(query, context, invitation_id: int, survey: dict, db_path: Path):
    """Complete survey via callback query (edit message)."""
    update_invitation_status(invitation_id, "completed", db_path)

    inv = get_invitation_by_id(invitation_id, db_path)
    reward_text = ""
    if inv and not inv["rewarded"]:
        reward_text = _grant_reward(inv["user_id"], survey, invitation_id, db_path)
        mark_invitation_rewarded(invitation_id, db_path)

    context.user_data.pop("survey_pending_text", None)

    await query.edit_message_text(
        f"✅ *Спасибо за ответы!*\n\n{reward_text}"
        "Отправьте ссылку на товар, чтобы использовать генерации!",
        parse_mode="Markdown",
    )


async def _complete_survey_message(message, context, invitation_id: int, survey: dict, db_path: Path):
    """Complete survey via text message (send new message)."""
    update_invitation_status(invitation_id, "completed", db_path)

    inv = get_invitation_by_id(invitation_id, db_path)
    reward_text = ""
    if inv and not inv["rewarded"]:
        reward_text = _grant_reward(inv["user_id"], survey, invitation_id, db_path)
        mark_invitation_rewarded(invitation_id, db_path)

    context.user_data.pop("survey_pending_text", None)

    await message.reply_text(
        f"✅ *Спасибо за ответы!*\n\n{reward_text}"
        "Отправьте ссылку на товар, чтобы использовать генерации!",
        parse_mode="Markdown",
    )


def _grant_reward(user_id: int, survey: dict, invitation_id: int, db_path: Path) -> str:
    """Grant gift pack reward. Returns reward text."""
    tokens = survey["reward_tokens"]
    days = survey["reward_days"]
    try:
        create_gift_pack(
            user_id=user_id,
            tokens=tokens,
            expiration_days=days,
            campaign_id=f"survey_{survey['id']}",
            description=f"Survey reward: {tokens} tokens for {days} days",
            db_path=db_path,
        )
        return f"🎁 *{tokens} бесплатных генераций* начислены (действуют {days} дней)!\n\n"
    except Exception as e:
        logger.error(f"Failed to grant survey reward to {user_id}: {e}")
        return ""


# =============================================================================
# Handler registration
# =============================================================================

def register_handlers(app):
    """Register all survey-related handlers on the Application."""
    app.add_handler(CallbackQueryHandler(handle_survey_start, pattern=r"^survey_start:"))
    app.add_handler(CallbackQueryHandler(handle_survey_answer, pattern=r"^survey_ans:"))
    app.add_handler(CallbackQueryHandler(handle_survey_multi_toggle, pattern=r"^survey_tog:"))
    app.add_handler(CallbackQueryHandler(handle_survey_multi_done, pattern=r"^survey_done:"))
    app.add_handler(CallbackQueryHandler(handle_survey_skip, pattern=r"^survey_skip:"))
