"""Slack Interactive Gateway — Block Kit control panel for zhihu-hunter.

Manages two human-in-the-loop approval gates:

    Gate 1  [✍️ 生成草稿] / [🚫 忽略]
            Triggered when ZhihuHunter surfaces candidate questions.

    Gate 2  [✅ 确认发布] / [🔄 重新生成] / [❌ 取消]
            Triggered after an answer draft has been generated.

Usage:
    gateway = SlackInteractiveGateway(slack_client, hunter, engine, channel)
    gateway.register(app)   # call once at bot startup
    gateway.post_question_cards(questions)  # call from dispatcher
"""

import json
import uuid
from pathlib import Path
from typing import Optional

from slack_bolt import App
from slack_sdk import WebClient

from health.utils.logging_config import setup_logger
from slack_bot.zhihu.zhihu_hunter import AnswerDraft, ZhihuHunter, ZhihuQuestion
from slack_bot.zhihu.zhihu_playwright_engine import ZhihuPlaywrightEngine

logger = setup_logger(__name__)


# ── Action IDs ─────────────────────────────────────────────────────────────────
_ACT_GENERATE = "zhihu_generate_draft"
_ACT_IGNORE   = "zhihu_ignore"
_ACT_PUBLISH  = "zhihu_publish"
_ACT_REGEN    = "zhihu_regenerate"
_ACT_CANCEL   = "zhihu_cancel"
_VIEW_REGEN   = "zhihu_regen_modal"

# Characters shown in the draft preview section (Slack block limit: 3000)
_PREVIEW_CHARS = 800


class SlackInteractiveGateway:
    """Block Kit gateway providing two approval gates for the zhihu-hunter workflow.

    Args:
        slack_client: Authenticated WebClient (same token as the Bolt App).
        hunter: ZhihuHunter instance for draft generation.
        engine: ZhihuPlaywrightEngine instance for publishing.
        notify_channel: Default Slack channel/DM for outbound cards.
        state_dir: Directory for storing draft JSON files between interactions.
            Defaults to <project_root>/data/zhihu_drafts.
    """

    def __init__(
        self,
        slack_client: WebClient,
        hunter: ZhihuHunter,
        engine: ZhihuPlaywrightEngine,
        notify_channel: str,
        state_dir: Optional[Path] = None,
    ) -> None:
        self.client        = slack_client
        self.hunter        = hunter
        self.engine        = engine
        self.notify_channel = notify_channel
        self.state_dir     = state_dir or (
            Path(__file__).resolve().parents[3] / "data" / "zhihu_drafts"
        )
        self.state_dir.mkdir(parents=True, exist_ok=True)

    # ── Registration ───────────────────────────────────────────────────────────

    def register(self, app: App) -> None:
        """Attach all action handlers to a Slack Bolt App instance.

        Call this once during bot startup, after the App object is created.

        Args:
            app: The Slack Bolt App that receives Socket Mode events.
        """
        app.action(_ACT_GENERATE)(self._handle_generate_draft)
        app.action(_ACT_IGNORE)(self._handle_ignore)
        app.action(_ACT_PUBLISH)(self._handle_publish)
        app.action(_ACT_REGEN)(self._handle_regenerate)
        app.action(_ACT_CANCEL)(self._handle_cancel)
        app.view(_VIEW_REGEN)(self._handle_regenerate_submit)
        logger.info("ZhihuHunter: Slack action handlers registered")

    # ── Outbound: Gate 1 ───────────────────────────────────────────────────────

    def post_question_cards(
        self,
        questions: list[ZhihuQuestion],
        channel: Optional[str] = None,
    ) -> None:
        """Post one Block Kit card per discovered question (Gate 1).

        Each card includes [✍️ 生成草稿] and [🚫 忽略] buttons.

        Args:
            questions: Candidate questions returned by ZhihuHunter.scan_and_hunt().
            channel: Override channel. Defaults to self.notify_channel.
        """
        ch = channel or self.notify_channel
        if not questions:
            self.client.chat_postMessage(
                channel=ch,
                text="🔍 本次未发现符合条件的知乎问题，下次再试。",
            )
            return

        self.client.chat_postMessage(
            channel=ch,
            text=f"🔍 发现 *{len(questions)}* 个知乎相关问题，请逐一确认：",
        )
        for q in questions:
            try:
                self.client.chat_postMessage(
                    channel=ch,
                    blocks=self._question_blocks(q),
                    text=f"🔍 知乎问题: {q.title}",
                )
            except Exception as e:
                logger.error(f"Failed to post question card for '{q.title}': {e}")

    # ── Outbound: Gate 2 ───────────────────────────────────────────────────────

    def post_draft_card(
        self,
        draft: AnswerDraft,
        channel: str,
        thread_ts: str,
    ) -> None:
        """Post an answer draft card as a thread reply (Gate 2).

        Includes [✅ 确认发布], [🔄 重新生成], and [❌ 取消] buttons.

        Args:
            draft: Generated AnswerDraft.
            channel: Channel containing the original question card.
            thread_ts: ts of the question card message (reply target).
        """
        draft_id = self._save_draft(draft)
        try:
            self.client.chat_postMessage(
                channel=channel,
                thread_ts=thread_ts,
                blocks=self._draft_blocks(draft, draft_id),
                text=f"📝 草稿已生成，等待确认发布: {draft.question.title}",
            )
        except Exception as e:
            logger.error(f"Failed to post draft card: {e}")

    # ── Action Handlers ────────────────────────────────────────────────────────

    def _handle_generate_draft(self, ack, body, client) -> None:
        """Gate 1 → [✍️ 生成草稿]: generate draft and post as thread reply."""
        ack()
        channel  = body["channel"]["id"]
        msg_ts   = body["message"]["ts"]
        raw_val  = body["actions"][0]["value"]

        # Immediately show loading state
        client.chat_update(
            channel=channel,
            ts=msg_ts,
            blocks=self._loading_block("✍️ 草稿生成中，请稍候..."),
            text="草稿生成中...",
        )

        try:
            question = ZhihuQuestion.model_validate_json(raw_val)
            draft    = self.hunter.draft_answer(question)

            # Persist to vault for manual fallback publishing
            vault_file = self._save_to_vault(draft)
            if vault_file:
                draft = draft.model_copy(update={"vault_file": str(vault_file)})

            draft_id = self._save_draft(draft)

            # Post draft as thread reply
            client.chat_postMessage(
                channel=channel,
                thread_ts=msg_ts,
                blocks=self._draft_blocks(draft, draft_id),
                text=f"📝 草稿已生成: {question.title}",
            )

            # Update original question card to show "done" state
            client.chat_update(
                channel=channel,
                ts=msg_ts,
                blocks=self._done_block(
                    f"📩 草稿已生成，请在上方回复中查看并确认。\n*<{question.url}|{question.title}>*"
                ),
                text="草稿已生成",
            )

        except Exception as e:
            logger.error(f"Draft generation failed: {e}", exc_info=True)
            client.chat_update(
                channel=channel,
                ts=msg_ts,
                blocks=self._error_block(f"草稿生成失败：{e}"),
                text="草稿生成失败",
            )

    def _handle_ignore(self, ack, respond) -> None:
        """Gate 1 → [🚫 忽略]: dismiss the question card."""
        ack()
        respond(
            replace_original=True,
            text="🚫 已忽略",
            blocks=self._done_block("🚫 已忽略此问题"),
        )

    def _handle_publish(self, ack, body, client) -> None:
        """Gate 2 → [✅ 确认发布]: login to Zhihu and publish the answer."""
        ack()
        channel  = body["channel"]["id"]
        msg_ts   = body["message"]["ts"]
        draft_id = body["actions"][0]["value"]

        client.chat_update(
            channel=channel,
            ts=msg_ts,
            blocks=self._loading_block("⏳ 正在登录知乎并发布，请稍候..."),
            text="发布中...",
        )

        try:
            draft = self._load_draft(draft_id)
            if draft is None:
                raise RuntimeError(f"草稿文件不存在（id={draft_id}），可能已过期")

            # Ensure valid Zhihu session (may send QR to Slack)
            if not self.engine.ensure_logged_in():
                raise RuntimeError("知乎登录失败，请扫码后重试")

            answer_url = self.engine.publish_answer(
                question_url=draft.question.url,
                answer_text=draft.content,
            )

            client.chat_update(
                channel=channel,
                ts=msg_ts,
                blocks=self._done_block(
                    f"✅ *发布成功！*\n"
                    f"问题: *<{draft.question.url}|{draft.question.title}>*\n"
                    f"回答: <{answer_url}|查看已发布回答>"
                ),
                text=f"✅ 已发布: {answer_url}",
            )
            # Update vault file status to published
            if draft.vault_file:
                self._update_vault_published(draft.vault_file, answer_url)
            # Clean up draft file
            self._delete_draft(draft_id)

        except Exception as e:
            logger.error(f"Publish failed: {e}", exc_info=True)
            client.chat_update(
                channel=channel,
                ts=msg_ts,
                blocks=self._error_block(
                    f"发布失败：{e}\n你可以重新点击发布，或联系排查日志。"
                ),
                text="发布失败",
            )

    def _handle_regenerate(self, ack, body, client) -> None:
        """Gate 2 → [🔄 重新生成]: open a modal for user to enter revision feedback."""
        ack()
        channel  = body["channel"]["id"]
        msg_ts   = body["message"]["ts"]
        raw_val  = body["actions"][0]["value"]

        try:
            question = ZhihuQuestion.model_validate_json(raw_val)
        except Exception:
            question_title = "（问题信息丢失）"
        else:
            question_title = question.title

        private_metadata = json.dumps({
            "channel":    channel,
            "msg_ts":     msg_ts,
            "regen_value": raw_val,
        })

        client.views_open(
            trigger_id=body["trigger_id"],
            view={
                "type": "modal",
                "callback_id": _VIEW_REGEN,
                "title": {"type": "plain_text", "text": "重新生成草稿"},
                "submit": {"type": "plain_text", "text": "生成"},
                "close":  {"type": "plain_text", "text": "取消"},
                "private_metadata": private_metadata,
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"*{question_title}*\n\n请输入修改意见，Bot 会据此重新生成草稿。",
                        },
                    },
                    {
                        "type": "input",
                        "block_id": "feedback_block",
                        "optional": True,
                        "label": {"type": "plain_text", "text": "修改意见"},
                        "element": {
                            "type": "plain_text_input",
                            "action_id": "feedback_input",
                            "multiline": True,
                            "placeholder": {
                                "type": "plain_text",
                                "text": "例：内容太技术了，偏向大众一些；字数控制在500字以内；结尾加一句诗",
                            },
                        },
                    },
                ],
            },
        )

    def _handle_regenerate_submit(self, ack, body, client) -> None:
        """Modal submit → regenerate draft with user feedback."""
        ack()
        metadata  = json.loads(body["view"]["private_metadata"])
        channel   = metadata["channel"]
        msg_ts    = metadata["msg_ts"]
        raw_val   = metadata["regen_value"]
        feedback  = (
            body["view"]["state"]["values"]
            .get("feedback_block", {})
            .get("feedback_input", {})
            .get("value") or ""
        )

        client.chat_update(
            channel=channel,
            ts=msg_ts,
            blocks=self._loading_block("🔄 重新生成中，请稍候..."),
            text="重新生成中...",
        )

        try:
            question = ZhihuQuestion.model_validate_json(raw_val)
            draft    = self.hunter.draft_answer(question, feedback=feedback)

            # Persist to vault (overwrites previous draft file for same question)
            vault_file = self._save_to_vault(draft)
            if vault_file:
                draft = draft.model_copy(update={"vault_file": str(vault_file)})

            draft_id = self._save_draft(draft)

            client.chat_update(
                channel=channel,
                ts=msg_ts,
                blocks=self._draft_blocks(draft, draft_id),
                text=f"📝 草稿已重新生成: {question.title}",
            )

        except Exception as e:
            logger.error(f"Regenerate submit failed: {e}", exc_info=True)
            client.chat_update(
                channel=channel,
                ts=msg_ts,
                blocks=self._error_block(f"重新生成失败：{e}"),
                text="重新生成失败",
            )

    def _handle_cancel(self, ack, respond) -> None:
        """Gate 2 → [❌ 取消]: dismiss the draft card."""
        ack()
        respond(
            replace_original=True,
            text="❌ 已取消",
            blocks=self._done_block("❌ 已取消本次发布"),
        )

    # ── Block Kit Builders ─────────────────────────────────────────────────────

    def _question_blocks(self, question: ZhihuQuestion) -> list[dict]:
        """Build Gate 1 Block Kit blocks for a single question.

        Args:
            question: ZhihuQuestion to display.

        Returns:
            List of Slack Block Kit block dicts.
        """
        # Truncate snippet so the button value stays < 2000 chars
        q_for_button = question.model_copy(
            update={"snippet": (question.snippet or "")[:100]}
        )
        btn_value = q_for_button.model_dump_json()

        body_lines = [f"*<{question.url}|{question.title}>*"]
        if question.snippet:
            body_lines.append(f"> {question.snippet[:200]}")
        if question.keywords:
            body_lines.append(f"_关键词：{' · '.join(question.keywords)}_")

        return [
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": "\n".join(body_lines)},
            },
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "✍️ 生成草稿", "emoji": True},
                        "style": "primary",
                        "action_id": _ACT_GENERATE,
                        "value": btn_value,
                    },
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "🚫 忽略", "emoji": True},
                        "action_id": _ACT_IGNORE,
                        "value": "ignore",
                    },
                ],
            },
        ]

    def _draft_blocks(self, draft: AnswerDraft, draft_id: str) -> list[dict]:
        """Build Gate 2 Block Kit blocks for a draft answer.

        Args:
            draft: AnswerDraft to display.
            draft_id: UUID key used as the publish button value.

        Returns:
            List of Slack Block Kit block dicts.
        """
        preview = draft.content[:_PREVIEW_CHARS]
        if len(draft.content) > _PREVIEW_CHARS:
            preview += f"\n_…（共 {len(draft.content)} 字，发布时使用全文）_"

        # Truncate question for regenerate button value
        q_for_btn = draft.question.model_copy(
            update={"snippet": (draft.question.snippet or "")[:100]}
        )
        regen_value = q_for_btn.model_dump_json()

        blocks: list[dict] = [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"📝 *草稿已生成* | "
                        f"<{draft.question.url}|{draft.question.title}>"
                    ),
                },
            },
            {"type": "divider"},
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": preview},
            },
        ]

        if draft.rag_sources:
            blocks.append({
                "type": "context",
                "elements": [{
                    "type": "mrkdwn",
                    "text": f"📚 RAG 来源：{' · '.join(draft.rag_sources[:5])}",
                }],
            })

        blocks += [
            {"type": "divider"},
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "✅ 确认发布", "emoji": True},
                        "style": "primary",
                        "action_id": _ACT_PUBLISH,
                        "value": draft_id,
                    },
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "🔄 重新生成", "emoji": True},
                        "action_id": _ACT_REGEN,
                        "value": regen_value,
                    },
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "❌ 取消", "emoji": True},
                        "style": "danger",
                        "action_id": _ACT_CANCEL,
                        "value": "cancel",
                    },
                ],
            },
        ]
        return blocks

    def _loading_block(self, message: str) -> list[dict]:
        """Simple single-section block for loading state.

        Args:
            message: Text to display (supports mrkdwn).

        Returns:
            List with one section block.
        """
        return [{"type": "section", "text": {"type": "mrkdwn", "text": message}}]

    def _done_block(self, message: str) -> list[dict]:
        """Single-section block for a completed/dismissed state.

        Args:
            message: Text to display (supports mrkdwn).

        Returns:
            List with one section block (no action buttons).
        """
        return [{"type": "section", "text": {"type": "mrkdwn", "text": message}}]

    def _error_block(self, message: str) -> list[dict]:
        """Single-section block for error display.

        Args:
            message: Error description (supports mrkdwn).

        Returns:
            List with one section block prefixed with ⚠️.
        """
        return [{
            "type": "section",
            "text": {"type": "mrkdwn", "text": f"⚠️ {message}"},
        }]

    # ── Draft State ────────────────────────────────────────────────────────────

    def _save_to_vault(self, draft: AnswerDraft) -> Optional[Path]:
        """Persist a draft as a Markdown file in <vault>/zhihu/.

        Args:
            draft: The generated AnswerDraft to save.

        Returns:
            Absolute path of the written file, or None on failure.
        """
        import re
        from datetime import date as _date

        try:
            zhihu_dir = self.hunter.vault_path / "zhihu"
            zhihu_dir.mkdir(parents=True, exist_ok=True)

            date_str = _date.today().strftime("%Y-%m-%d")
            slug = re.sub(r"[^\w\u4e00-\u9fff]+", "-", draft.question.title)[:40].strip("-")
            file_path = zhihu_dir / f"{date_str}-{slug}.md"

            file_path.write_text(
                f"---\n"
                f"title: \"{draft.question.title}\"\n"
                f"url: {draft.question.url}\n"
                f"date: {date_str}\n"
                f"status: draft\n"
                f"tags: [zhihu]\n"
                f"---\n\n"
                f"# {draft.question.title}\n\n"
                f"{draft.content}\n",
                encoding="utf-8",
            )
            logger.info(f"Draft saved to vault: {file_path}")
            return file_path
        except Exception as e:
            logger.warning(f"Failed to save draft to vault: {e}")
            return None

    def _update_vault_published(self, vault_file: str, answer_url: str) -> None:
        """Update the vault file's frontmatter status to published.

        Args:
            vault_file: Absolute path string stored in AnswerDraft.vault_file.
            answer_url: Published answer URL to record.
        """
        try:
            fp = Path(vault_file)
            if not fp.exists():
                return
            content = fp.read_text(encoding="utf-8")
            content = content.replace(
                "status: draft",
                f"status: published\nanswer_url: {answer_url}",
                1,
            )
            fp.write_text(content, encoding="utf-8")
            logger.info(f"Vault file marked published: {fp}")
        except Exception as e:
            logger.warning(f"Failed to update vault file: {e}")

    def _save_draft(self, draft: AnswerDraft) -> str:
        """Persist a draft to disk and return its UUID key.

        Args:
            draft: AnswerDraft to save.

        Returns:
            UUID string key (used as publish button value).
        """
        draft_id  = uuid.uuid4().hex[:12]
        file_path = self.state_dir / f"{draft_id}.json"
        file_path.write_text(draft.model_dump_json(), encoding="utf-8")
        logger.debug(f"Draft saved: {file_path}")
        return draft_id

    def _load_draft(self, draft_id: str) -> Optional[AnswerDraft]:
        """Load a draft by its UUID key.

        Args:
            draft_id: Key returned by _save_draft().

        Returns:
            AnswerDraft if found, None if the file does not exist.
        """
        file_path = self.state_dir / f"{draft_id}.json"
        if not file_path.exists():
            return None
        try:
            return AnswerDraft.model_validate_json(file_path.read_text(encoding="utf-8"))
        except Exception as e:
            logger.error(f"Failed to load draft {draft_id}: {e}")
            return None

    def _delete_draft(self, draft_id: str) -> None:
        """Remove a draft file after successful publish.

        Args:
            draft_id: Key of the draft to delete.
        """
        file_path = self.state_dir / f"{draft_id}.json"
        try:
            file_path.unlink(missing_ok=True)
        except Exception as e:
            logger.warning(f"Could not delete draft file {draft_id}: {e}")
