"""
发布队列模块
SQLite 持久化的内容排期 + 发布队列，支持草稿预审、定时发布、失败重试
"""
import sqlite3
import json
import os
import time
import logging
import threading
from datetime import datetime, timedelta
from typing import Optional

logger = logging.getLogger(__name__)

# 队列项状态
STATUS_DRAFT = "draft"            # 草稿 — 待审核
STATUS_APPROVED = "approved"      # 已审核 — 待排期或立即可发布
STATUS_SCHEDULED = "scheduled"    # 已排期 — 定时发布
STATUS_PUBLISHING = "publishing"  # 发布中
STATUS_PUBLISHED = "published"    # 已发布
STATUS_FAILED = "failed"          # 发布失败
STATUS_REJECTED = "rejected"      # 已拒绝/丢弃

ALL_STATUSES = [STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED,
                STATUS_PUBLISHING, STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED]

STATUS_LABELS = {
    STATUS_DRAFT: "📝 草稿",
    STATUS_APPROVED: "✅ 待发布",
    STATUS_SCHEDULED: "🕐 已排期",
    STATUS_PUBLISHING: "🚀 发布中",
    STATUS_PUBLISHED: "✅ 已发布",
    STATUS_FAILED: "❌ 失败",
    STATUS_REJECTED: "🚫 已拒绝",
}

MAX_RETRIES = 2


class PublishQueue:
    """发布队列管理器 (SQLite 持久化)"""

    def __init__(self, workspace_dir: str):
        self.db_path = os.path.join(workspace_dir, "publish_queue.db")
        os.makedirs(workspace_dir, exist_ok=True)
        self._init_db()
        # 发布中状态恢复 (启动时把 publishing → failed)
        self._recover_stale()

    def _get_conn(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self.db_path, timeout=10)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        return conn

    def _init_db(self):
        """初始化数据库表"""
        conn = self._get_conn()
        try:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS queue (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    title TEXT NOT NULL,
                    content TEXT NOT NULL DEFAULT '',
                    sd_prompt TEXT DEFAULT '',
                    tags TEXT DEFAULT '[]',
                    image_paths TEXT DEFAULT '[]',
                    backup_dir TEXT DEFAULT '',
                    status TEXT NOT NULL DEFAULT 'draft',
                    scheduled_time TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    published_at TEXT,
                    topic TEXT DEFAULT '',
                    style TEXT DEFAULT '',
                    persona TEXT DEFAULT '',
                    error_message TEXT DEFAULT '',
                    retry_count INTEGER DEFAULT 0,
                    publish_result TEXT DEFAULT ''
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_queue_status ON queue(status)
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_queue_scheduled ON queue(scheduled_time)
            """)
            conn.commit()
        finally:
            conn.close()

    def _recover_stale(self):
        """启动时将残留的 publishing 状态恢复为 failed"""
        conn = self._get_conn()
        try:
            conn.execute(
                "UPDATE queue SET status = ?, error_message = '程序重启，发布中断' "
                "WHERE status = ?",
                (STATUS_FAILED, STATUS_PUBLISHING),
            )
            conn.commit()
        finally:
            conn.close()

    # ---------- CRUD ----------

    def add(self, title: str, content: str, sd_prompt: str = "",
            tags: list = None, image_paths: list = None,
            backup_dir: str = "", topic: str = "", style: str = "",
            persona: str = "", status: str = STATUS_DRAFT,
            scheduled_time: str = None) -> int:
        """添加一个队列项，返回 ID"""
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        conn = self._get_conn()
        try:
            cur = conn.execute(
                """INSERT INTO queue (title, content, sd_prompt, tags, image_paths,
                   backup_dir, status, scheduled_time, created_at, updated_at,
                   topic, style, persona)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (title, content, sd_prompt,
                 json.dumps(tags or [], ensure_ascii=False),
                 json.dumps(image_paths or [], ensure_ascii=False),
                 backup_dir, status, scheduled_time, now, now,
                 topic, style, persona),
            )
            conn.commit()
            item_id = cur.lastrowid
            logger.info("📋 队列添加 #%d: %s [%s]", item_id, title[:20], status)
            return item_id
        finally:
            conn.close()

    def get(self, item_id: int) -> Optional[dict]:
        """获取单个队列项"""
        conn = self._get_conn()
        try:
            row = conn.execute("SELECT * FROM queue WHERE id = ?", (item_id,)).fetchone()
            return self._row_to_dict(row) if row else None
        finally:
            conn.close()

    def update_status(self, item_id: int, status: str,
                      error_message: str = "", publish_result: str = ""):
        """更新状态"""
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        conn = self._get_conn()
        try:
            fields = "status = ?, updated_at = ?"
            params = [status, now]
            if error_message:
                fields += ", error_message = ?"
                params.append(error_message)
            if publish_result:
                fields += ", publish_result = ?"
                params.append(publish_result)
            if status == STATUS_PUBLISHED:
                fields += ", published_at = ?"
                params.append(now)
            params.append(item_id)
            conn.execute(f"UPDATE queue SET {fields} WHERE id = ?", params)
            conn.commit()
        finally:
            conn.close()

    def update_content(self, item_id: int, title: str = None, content: str = None,
                       sd_prompt: str = None, tags: list = None,
                       scheduled_time: str = None):
        """更新内容 (仅 draft/approved/scheduled/failed 状态可编辑)"""
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        conn = self._get_conn()
        try:
            row = conn.execute("SELECT status FROM queue WHERE id = ?", (item_id,)).fetchone()
            if not row or row["status"] in (STATUS_PUBLISHING, STATUS_PUBLISHED):
                return False
            sets, params = ["updated_at = ?"], [now]
            if title is not None:
                sets.append("title = ?"); params.append(title)
            if content is not None:
                sets.append("content = ?"); params.append(content)
            if sd_prompt is not None:
                sets.append("sd_prompt = ?"); params.append(sd_prompt)
            if tags is not None:
                sets.append("tags = ?"); params.append(json.dumps(tags, ensure_ascii=False))
            if scheduled_time is not None:
                sets.append("scheduled_time = ?"); params.append(scheduled_time)
            params.append(item_id)
            conn.execute(f"UPDATE queue SET {', '.join(sets)} WHERE id = ?", params)
            conn.commit()
            return True
        finally:
            conn.close()

    def delete(self, item_id: int) -> bool:
        """删除队列项 (仅非 publishing 状态可删)"""
        conn = self._get_conn()
        try:
            row = conn.execute("SELECT status FROM queue WHERE id = ?", (item_id,)).fetchone()
            if not row or row["status"] == STATUS_PUBLISHING:
                return False
            conn.execute("DELETE FROM queue WHERE id = ?", (item_id,))
            conn.commit()
            return True
        finally:
            conn.close()

    def approve(self, item_id: int, scheduled_time: str = None) -> bool:
        """审核通过 → 进入待发布或排期"""
        conn = self._get_conn()
        try:
            row = conn.execute("SELECT status FROM queue WHERE id = ?", (item_id,)).fetchone()
            if not row or row["status"] not in (STATUS_DRAFT, STATUS_FAILED):
                return False
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            new_status = STATUS_SCHEDULED if scheduled_time else STATUS_APPROVED
            conn.execute(
                "UPDATE queue SET status = ?, scheduled_time = ?, updated_at = ?, "
                "error_message = '', retry_count = 0 WHERE id = ?",
                (new_status, scheduled_time, now, item_id),
            )
            conn.commit()
            return True
        finally:
            conn.close()

    def reject(self, item_id: int) -> bool:
        """拒绝/丢弃"""
        return self._set_status_if(item_id, STATUS_REJECTED,
                                   allowed_from=[STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_FAILED])

    def retry(self, item_id: int) -> bool:
        """失败项重试"""
        conn = self._get_conn()
        try:
            row = conn.execute("SELECT status, retry_count FROM queue WHERE id = ?", (item_id,)).fetchone()
            if not row or row["status"] != STATUS_FAILED:
                return False
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            conn.execute(
                "UPDATE queue SET status = ?, updated_at = ?, error_message = '' WHERE id = ?",
                (STATUS_APPROVED, now, item_id),
            )
            conn.commit()
            return True
        finally:
            conn.close()

    def _set_status_if(self, item_id: int, new_status: str, allowed_from: list) -> bool:
        conn = self._get_conn()
        try:
            row = conn.execute("SELECT status FROM queue WHERE id = ?", (item_id,)).fetchone()
            if not row or row["status"] not in allowed_from:
                return False
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            conn.execute("UPDATE queue SET status = ?, updated_at = ? WHERE id = ?",
                         (new_status, now, item_id))
            conn.commit()
            return True
        finally:
            conn.close()

    # ---------- 查询 ----------

    def list_by_status(self, statuses: list = None, limit: int = 50) -> list[dict]:
        """按状态查询队列项"""
        conn = self._get_conn()
        try:
            if statuses:
                placeholders = ",".join("?" * len(statuses))
                rows = conn.execute(
                    f"SELECT * FROM queue WHERE status IN ({placeholders}) "
                    "ORDER BY created_at DESC LIMIT ?",
                    statuses + [limit],
                ).fetchall()
            else:
                rows = conn.execute(
                    "SELECT * FROM queue ORDER BY created_at DESC LIMIT ?", (limit,)
                ).fetchall()
            return [self._row_to_dict(r) for r in rows]
        finally:
            conn.close()

    def get_pending_publish(self) -> list[dict]:
        """获取待发布项: approved 或 scheduled 且已到时间"""
        conn = self._get_conn()
        try:
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            rows = conn.execute(
                """SELECT * FROM queue
                   WHERE (status = ? OR (status = ? AND scheduled_time <= ?))
                   ORDER BY
                     CASE WHEN scheduled_time IS NOT NULL THEN scheduled_time
                          ELSE created_at END ASC
                   LIMIT 10""",
                (STATUS_APPROVED, STATUS_SCHEDULED, now),
            ).fetchall()
            return [self._row_to_dict(r) for r in rows]
        finally:
            conn.close()

    def count_by_status(self) -> dict:
        """统计各状态数量"""
        conn = self._get_conn()
        try:
            rows = conn.execute(
                "SELECT status, COUNT(*) as cnt FROM queue GROUP BY status"
            ).fetchall()
            return {r["status"]: r["cnt"] for r in rows}
        finally:
            conn.close()

    def get_calendar_data(self, days: int = 30) -> list[dict]:
        """获取日历数据 (最近 N 天的发布/排期概览)"""
        conn = self._get_conn()
        try:
            cutoff = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d %H:%M:%S")
            rows = conn.execute(
                """SELECT id, title, status, scheduled_time, published_at, created_at
                   FROM queue
                   WHERE created_at >= ? OR scheduled_time >= ? OR published_at >= ?
                   ORDER BY COALESCE(scheduled_time, published_at, created_at) ASC""",
                (cutoff, cutoff, cutoff),
            ).fetchall()
            return [dict(r) for r in rows]
        finally:
            conn.close()

    # ---------- 辅助 ----------

    @staticmethod
    def _row_to_dict(row: sqlite3.Row) -> dict:
        """Row → dict, 并解析 JSON 字段"""
        d = dict(row)
        for key in ("tags", "image_paths"):
            if key in d and isinstance(d[key], str):
                try:
                    d[key] = json.loads(d[key])
                except json.JSONDecodeError:
                    d[key] = []
        return d

    def format_queue_table(self, statuses: list = None, limit: int = 30) -> str:
        """生成 Markdown 格式的队列表格"""
        items = self.list_by_status(statuses, limit)
        if not items:
            return "📭 队列为空"

        lines = ["| # | 状态 | 标题 | 主题 | 排期时间 | 创建时间 |",
                 "|---|------|------|------|----------|----------|"]
        for item in items:
            status_label = STATUS_LABELS.get(item["status"], item["status"])
            sched = item.get("scheduled_time") or "—"
            if sched != "—":
                sched = sched[:16]  # 去掉秒
            created = item["created_at"][:16] if item.get("created_at") else "—"
            title_short = (item.get("title") or "")[:18]
            topic_short = (item.get("topic") or "")[:10]
            lines.append(f"| {item['id']} | {status_label} | {title_short} | {topic_short} | {sched} | {created} |")

        # 统计摘要
        counts = self.count_by_status()
        summary_parts = []
        for s, label in STATUS_LABELS.items():
            cnt = counts.get(s, 0)
            if cnt > 0:
                summary_parts.append(f"{label}: {cnt}")
        summary = " · ".join(summary_parts) if summary_parts else "全部为空"

        return f"**队列统计**: {summary}\n\n" + "\n".join(lines)

    def format_calendar(self, days: int = 14) -> str:
        """生成简易日历视图 (Markdown)"""
        data = self.get_calendar_data(days)
        if not data:
            return "📅 暂无排期数据"

        # 按日期分组
        by_date = {}
        for item in data:
            # 优先用排期时间，其次发布时间，最后创建时间
            dt_str = item.get("scheduled_time") or item.get("published_at") or item["created_at"]
            date_key = dt_str[:10] if dt_str else "未知"
            by_date.setdefault(date_key, []).append(item)

        lines = ["### 📅 内容日历 (近 %d 天)\n" % days]
        today = datetime.now().strftime("%Y-%m-%d")

        for date_key in sorted(by_date.keys()):
            marker = " 📌 **今天**" if date_key == today else ""
            lines.append(f"**{date_key}**{marker}")
            for item in by_date[date_key]:
                status_icon = STATUS_LABELS.get(item["status"], "❓")
                time_part = ""
                if item.get("scheduled_time"):
                    time_part = f" ⏰{item['scheduled_time'][11:16]}"
                elif item.get("published_at"):
                    time_part = f" ✅{item['published_at'][11:16]}"
                title_short = (item.get("title") or "无标题")[:20]
                lines.append(f"  - {status_icon} #{item['id']} {title_short}{time_part}")
            lines.append("")

        return "\n".join(lines)

    def format_preview(self, item_id: int) -> str:
        """生成单个项目的详细预览 (Markdown)"""
        item = self.get(item_id)
        if not item:
            return "❌ 未找到该队列项"

        status_label = STATUS_LABELS.get(item["status"], item["status"])
        tags = item.get("tags", [])
        tags_str = " ".join(f"#{t}" for t in tags) if tags else "无标签"
        images = item.get("image_paths", [])
        img_count = len(images) if images else 0

        lines = [
            f"## {status_label} #{item['id']}",
            f"### 📌 {item.get('title', '无标题')}",
            "",
            item.get("content", "无正文"),
            "",
            f"---",
            f"**主题**: {item.get('topic', '—')} · **风格**: {item.get('style', '—')}",
            f"**标签**: {tags_str}",
            f"**图片**: {img_count} 张",
            f"**人设**: {(item.get('persona') or '—')[:30]}",
        ]

        if item.get("scheduled_time"):
            lines.append(f"**排期**: {item['scheduled_time']}")
        if item.get("error_message"):
            lines.append(f"**错误**: ❌ {item['error_message']}")
        if item.get("backup_dir"):
            lines.append(f"**备份**: `{item['backup_dir']}`")

        lines.extend([
            f"**创建**: {item.get('created_at', '—')}",
            f"**更新**: {item.get('updated_at', '—')}",
        ])
        if item.get("published_at"):
            lines.append(f"**发布**: {item['published_at']}")

        return "\n".join(lines)


class QueuePublisher:
    """后台队列发布处理器"""

    def __init__(self, queue: PublishQueue):
        self.queue = queue
        self._running = threading.Event()
        self._thread = None
        self._publish_fn = None  # 由外部注册的发布回调
        self._log_fn = None      # 日志回调

    def set_publish_callback(self, fn):
        """注册发布回调: fn(item: dict) -> (success: bool, message: str)"""
        self._publish_fn = fn

    def set_log_callback(self, fn):
        """注册日志回调: fn(msg: str)"""
        self._log_fn = fn

    def _log(self, msg: str):
        logger.info(msg)
        if self._log_fn:
            try:
                self._log_fn(msg)
            except Exception:
                pass

    def start(self, check_interval: int = 60):
        """启动后台队列处理"""
        if self._running.is_set():
            return
        self._running.set()
        self._thread = threading.Thread(
            target=self._loop, args=(check_interval,), daemon=True
        )
        self._thread.start()
        self._log("📋 发布队列处理器已启动")

    def stop(self):
        """停止队列处理"""
        self._running.clear()
        self._log("📋 发布队列处理器已停止")

    @property
    def is_running(self) -> bool:
        return self._running.is_set()

    def _loop(self, interval: int):
        while self._running.is_set():
            try:
                self._process_pending()
            except Exception as e:
                self._log(f"❌ 队列处理异常: {e}")
                logger.error("队列处理异常: %s", e, exc_info=True)
            # 等待，但可中断
            for _ in range(interval):
                if not self._running.is_set():
                    break
                time.sleep(1)

    def _process_pending(self):
        """处理所有待发布项"""
        if not self._publish_fn:
            return

        pending = self.queue.get_pending_publish()
        if not pending:
            return

        for item in pending:
            if not self._running.is_set():
                break

            item_id = item["id"]
            title = item.get("title", "")[:20]
            self._log(f"📋 队列发布 #{item_id}: {title}")

            # 标记为发布中
            self.queue.update_status(item_id, STATUS_PUBLISHING)

            try:
                success, message = self._publish_fn(item)
                if success:
                    self.queue.update_status(item_id, STATUS_PUBLISHED, publish_result=message)
                    self._log(f"✅ 队列发布成功 #{item_id}: {title}")
                else:
                    retry_count = item.get("retry_count", 0) + 1
                    if retry_count <= MAX_RETRIES:
                        # 还有重试机会 → approved 状态等下一轮
                        self.queue.update_status(item_id, STATUS_APPROVED, error_message=f"第{retry_count}次失败: {message}")
                        conn = self.queue._get_conn()
                        try:
                            conn.execute("UPDATE queue SET retry_count = ? WHERE id = ?",
                                         (retry_count, item_id))
                            conn.commit()
                        finally:
                            conn.close()
                        self._log(f"⚠️ #{item_id} 发布失败 (重试 {retry_count}/{MAX_RETRIES}): {message}")
                    else:
                        self.queue.update_status(item_id, STATUS_FAILED, error_message=message)
                        self._log(f"❌ #{item_id} 发布失败已达重试上限: {message}")

            except Exception as e:
                self.queue.update_status(item_id, STATUS_FAILED, error_message=str(e))
                self._log(f"❌ #{item_id} 发布异常: {e}")

            # 发布间隔 (模拟真人)
            import random
            wait = random.randint(5, 15)
            self._log(f"⏳ 等待 {wait}s 后处理下一项...")
            for _ in range(wait):
                if not self._running.is_set():
                    break
                time.sleep(1)

    def publish_now(self, item_id: int) -> str:
        """立即发布指定项 (不经过后台循环)"""
        if not self._publish_fn:
            return "❌ 发布回调未注册"

        item = self.queue.get(item_id)
        if not item:
            return "❌ 未找到队列项"
        if item["status"] not in (STATUS_APPROVED, STATUS_SCHEDULED, STATUS_FAILED):
            return f"❌ 当前状态 [{STATUS_LABELS.get(item['status'], item['status'])}] 不可发布"

        self.queue.update_status(item_id, STATUS_PUBLISHING)
        try:
            success, message = self._publish_fn(item)
            if success:
                self.queue.update_status(item_id, STATUS_PUBLISHED, publish_result=message)
                return f"✅ 发布成功: {message}"
            else:
                self.queue.update_status(item_id, STATUS_FAILED, error_message=message)
                return f"❌ 发布失败: {message}"
        except Exception as e:
            self.queue.update_status(item_id, STATUS_FAILED, error_message=str(e))
            return f"❌ 发布异常: {e}"
