"""인스타 카드 에이전트 — 매일 09:30 뉴스 수집·키워드 추출 → 텔레그램 후보 푸시.
사용자가 키워드 버튼을 누르면 카드 슬레이트 생성 + 10장 미디어 그룹 발송."""

import asyncio
import json
import logging
from typing import Any, Dict, List, Optional

import httpx

from .base import BaseAgent
from ..db import (
    create_task, update_task_status, add_log, get_agent_config,
)
from ..config import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
from .. import service_proxy
from ..telegram import messaging

logger = logging.getLogger(__name__)

# 텔레그램 후보 푸시 시 "확실한 것만" 보내기 위한 최소 신뢰도 (키워드 score 0~1)
KEYWORD_MIN_SCORE = 0.7


def _dedup_and_filter_keywords(
    keywords: List[Dict[str, Any]], min_score: float = KEYWORD_MIN_SCORE,
) -> List[Dict[str, Any]]:
    """score >= min_score 인 키워드만 남기고, 동일 keyword 중복 제거(최고 score 유지).
    결과는 score 내림차순. 텔레그램 후보 푸시 전 정리용."""
    best: Dict[str, Dict[str, Any]] = {}
    for k in keywords:
        if float(k.get("score", 0)) < min_score:
            continue
        name = str(k.get("keyword", "")).strip()
        if not name:
            continue
        if name not in best or k["score"] > best[name]["score"]:
            best[name] = k
    return sorted(best.values(), key=lambda k: -k["score"])


async def _send_media_group(media: List[Dict[str, Any]], caption: str = "") -> Dict[str, Any]:
    """텔레그램 sendMediaGroup. media는 InputMediaPhoto dicts.
    각 항목에는 임시 키 '_bytes'로 PNG 바이트가 담겨 있어 attach:// 형식으로 multipart 업로드."""
    if not TELEGRAM_BOT_TOKEN:
        return {"ok": False, "reason": "TELEGRAM_BOT_TOKEN missing"}
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMediaGroup"
    files: Dict[str, tuple] = {}
    for i, m in enumerate(media):
        attach_key = f"photo{i+1}"
        files[attach_key] = (f"{i+1}.png", m["_bytes"], "image/png")
        m["media"] = f"attach://{attach_key}"
        m.pop("_bytes", None)
    if caption and media:
        media[0]["caption"] = caption[:1024]
    payload = {"chat_id": TELEGRAM_CHAT_ID, "media": json.dumps(media, ensure_ascii=False)}
    async with httpx.AsyncClient(timeout=60) as client:
        resp = await client.post(url, data=payload, files=files)
    return resp.json()


class InstaAgent(BaseAgent):
    agent_id = "insta"
    display_name = "인스타 큐레이터"

    async def on_schedule(self) -> None:
        """09:30 매일: 뉴스 수집 → 키워드 추출 → 텔레그램 후보 푸시.
        custom_config.auto_select=True면 카테고리당 1위 키워드 자동 슬레이트 생성."""
        if self.state != "idle":
            return
        config = get_agent_config(self.agent_id) or {}
        custom = config.get("custom_config", {}) or {}
        auto_select = bool(custom.get("auto_select", False))
        autonomous = bool(custom.get("autonomous_issue", False))
        threshold = float(custom.get("select_threshold", 0.6))
        max_per_day = int(custom.get("max_per_day", 2))
        dedup_window_days = int(custom.get("dedup_window_days", 14))

        task_id = create_task(self.agent_id, "insta_daily", {"auto_select": auto_select},
                              requires_approval=False)
        await self.transition("working", "뉴스 수집·키워드 추출", task_id)
        try:
            try:
                prefs = await service_proxy.insta_get_preferences()
                add_log(self.agent_id, f"insta preferences: {prefs}", "info", task_id)
            except Exception as _pref_err:
                add_log(self.agent_id, f"insta preferences unavailable: {_pref_err}", "warning", task_id)
            await self._run_collect_and_extract()
            if autonomous:
                ranked = await service_proxy.insta_ranked(threshold=threshold, limit=20, dedup_window_days=dedup_window_days)
                eligible = [r for r in ranked if r.get("eligible")][:max_per_day]
                if not eligible:
                    await messaging.send_raw("📰 [인스타 큐레이터] 오늘은 발행할 가치 있는 주제가 없습니다.")
                else:
                    for pick in eligible:
                        await self._generate_and_preview(pick)
                update_task_status(task_id, "succeeded", {"issued": len(eligible)})
                await self.transition("idle", "자율 발급 후보 프리뷰 완료")
                return
            kws = await service_proxy.insta_list_keywords(used=False)
            if auto_select:
                await self._auto_render(kws)
            else:
                await self._push_keyword_candidates(kws)
            update_task_status(task_id, "succeeded", {"keywords": len(kws)})
            await self.transition("idle", "후보 푸시 완료")
        except Exception as e:
            add_log(self.agent_id, f"insta daily failed: {e}", "error", task_id)
            update_task_status(task_id, "failed", {"error": str(e)})
            await self.transition("idle", f"오류: {e}")

    async def _run_collect_and_extract(self) -> None:
        col = await service_proxy.insta_collect()
        await self._wait_task(col["task_id"], step="collect", timeout_sec=300)
        ext = await service_proxy.insta_extract()
        await self._wait_task(ext["task_id"], step="extract", timeout_sec=300)

    async def _wait_task(self, task_id: str, step: str, timeout_sec: int = 300) -> Dict[str, Any]:
        attempts = max(1, timeout_sec // 5)
        for _ in range(attempts):
            await asyncio.sleep(5)
            st = await service_proxy.insta_task_status(task_id)
            if st["status"] == "succeeded":
                return st
            if st["status"] == "failed":
                raise RuntimeError(f"{step} failed: {st.get('error')}")
        raise TimeoutError(f"{step} timeout {timeout_sec}s")

    async def _push_keyword_candidates(self, keywords: List[Dict[str, Any]]) -> None:
        # 중복 제거 + 신뢰도(score) 임계값 이상만 — "확실한 것만" 정리해서 전송
        filtered = _dedup_and_filter_keywords(keywords)
        if not filtered:
            await messaging.send_raw(
                f"📰 [인스타 큐레이터] 오늘은 확실한 추천 키워드가 없습니다 (신뢰도 {KEYWORD_MIN_SCORE:.1f}+ 기준)."
            )
            return
        by_cat: Dict[str, List[Dict[str, Any]]] = {}
        for k in filtered:
            by_cat.setdefault(k["category"], []).append(k)
        rows: List[List[Dict[str, Any]]] = []
        text_lines = [f"📰 <b>[인스타 큐레이터]</b> 오늘의 키워드 후보 (신뢰도 {KEYWORD_MIN_SCORE:.1f}+)"]
        for cat, items in by_cat.items():
            text_lines.append(f"\n<b>{cat}</b>")
            for k in items[:5]:
                text_lines.append(f"  · {k['keyword']} (score {k['score']:.2f})")
                rows.append([{
                    "text": f"🎴 {k['keyword']}",
                    "callback_data": f"render_{k['id']}",
                }])
        await messaging.send_raw("\n".join(text_lines), reply_markup={"inline_keyboard": rows})

    async def _auto_render(self, keywords: List[Dict[str, Any]]) -> None:
        by_cat: Dict[str, Dict[str, Any]] = {}
        for k in keywords:
            cat = k["category"]
            if cat not in by_cat or k["score"] > by_cat[cat]["score"]:
                by_cat[cat] = k
        for kw in by_cat.values():
            await self._render_and_push(kw["id"])

    async def _render_and_push(self, keyword_id: int) -> None:
        kw = await service_proxy.insta_get_keyword(keyword_id)
        if not kw:
            await messaging.send_raw(f"⚠️ 키워드 {keyword_id} 없음")
            return
        await messaging.send_raw(f"🎨 카드 생성 중: <b>{kw['keyword']}</b>")
        created = await service_proxy.insta_create_slate(
            keyword=kw["keyword"], category=kw["category"], keyword_id=kw["id"],
        )
        st = await self._wait_task(created["task_id"], step="slate", timeout_sec=600)
        slate_id = st["result_id"]
        slate = await service_proxy.insta_get_slate(slate_id)
        media = []
        for a in slate["assets"][:10]:
            data = await service_proxy.insta_get_asset_bytes(slate_id, a["page_index"])
            media.append({"type": "photo", "_bytes": data})
        caption = slate.get("suggested_caption", "")
        hashtags = " ".join(slate.get("hashtags", []) or [])
        full_caption = f"{caption}\n\n{hashtags}".strip()
        await _send_media_group(media, caption=full_caption)

    async def _generate_and_preview(self, pick: dict) -> None:
        """eligible 픽 → 슬레이트 생성·렌더 → 커버 프리뷰 + 승인 버튼."""
        created = await service_proxy.insta_create_slate(
            keyword=pick["keyword"], category=pick["category"], keyword_id=pick["id"],
        )
        st = await self._wait_task(created["task_id"], step="slate", timeout_sec=600)
        slate_id = st["result_id"]
        cover = await service_proxy.insta_get_asset_bytes(slate_id, 1)
        bd = pick.get("breakdown", {})
        caption = (f"🎴 <b>{pick['keyword']}</b> ({pick['category']})\n"
                   f"점수 {pick.get('final_score')} · fresh {bd.get('freshness')} "
                   f"fit {bd.get('account_fit')} claude {bd.get('claude')}\n승인하시겠어요?")
        kb = {"inline_keyboard": [[
            {"text": "✅ 승인", "callback_data": f"issue_approve_{slate_id}"},
            {"text": "❌ 반려", "callback_data": f"issue_reject_{slate_id}"},
            {"text": "🔄 재생성", "callback_data": f"issue_regen_{slate_id}"},
        ]]}
        await messaging.send_photo(cover, caption=caption, reply_markup=kb)
        create_task(self.agent_id, "insta_issue", {"slate_id": slate_id, "keyword_id": pick["id"]},
                    requires_approval=True)

    async def on_command(self, command: str, params: dict) -> dict:
        if command == "extract":
            await self._run_collect_and_extract()
            kws = await service_proxy.insta_list_keywords(used=False)
            await self._push_keyword_candidates(kws)
            return {"ok": True, "count": len(kws)}
        if command == "render":
            kid = int(params.get("keyword_id") or 0)
            if not kid:
                return {"ok": False, "message": "keyword_id 필수"}
            await self._render_and_push(kid)
            return {"ok": True}
        if command == "collect_trends":
            await messaging.send_raw("🌐 외부 트렌드 수집 시작")
            created = await service_proxy.insta_collect_trends()
            st = await self._wait_task(created["task_id"], step="trends_collect", timeout_sec=300)
            await messaging.send_raw(f"✅ 트렌드 수집 완료: {st.get('message', '')}")
            return {"ok": True, "result": st}
        return {"ok": False, "message": f"Unknown command: {command}"}

    async def on_callback(self, action: str, params: dict) -> dict:
        if action == "render":
            kid = int(params.get("keyword_id") or 0)
            if not kid:
                return {"ok": False}
            await self._render_and_push(kid)
            return {"ok": True}
        if action in ("issue_approve", "issue_reject"):
            sid = int(params.get("slate_id") or 0)
            if not sid:
                return {"ok": False}
            decision = "approved" if action == "issue_approve" else "rejected"
            await service_proxy.insta_decision(sid, decision)
            if decision == "approved":
                slate = await service_proxy.insta_get_slate(sid)
                media = []
                for a in slate["assets"][:10]:
                    data = await service_proxy.insta_get_asset_bytes(sid, a["page_index"])
                    media.append({"type": "photo", "_bytes": data})
                cap = f"{slate.get('suggested_caption','')}\n\n{' '.join(slate.get('hashtags', []) or [])}".strip()
                await _send_media_group(media, caption=cap)
                await messaging.send_raw(f"✅ 발행 완료 (slate {sid})")
            else:
                await messaging.send_raw(f"❌ 반려됨 (slate {sid})")
            return {"ok": True}
        if action == "issue_regen":
            sid = int(params.get("slate_id") or 0)
            if not sid:
                return {"ok": False}
            slate = await service_proxy.insta_get_slate(sid)
            await service_proxy.insta_decision(sid, "rejected")
            await self._generate_and_preview({
                "id": 0,
                "keyword": slate["keyword"],
                "category": slate["category"],
                "final_score": None,
                "breakdown": {},
            })
            return {"ok": True}
        return {"ok": False}

    async def on_approval(self, task_id: str, approved: bool, feedback: str = "") -> None:
        return
