"""
Social Media Publish Engine
Ported & adapted từ BrightBean Studio apps/publisher/engine.py
Chuyển từ Django + background_task → pure Python + asyncio

Core loop:
1. Poll queue cho entries có scheduled_at <= now()
2. Dispatch publish lên từng platform (parallel threads)
3. Exponential backoff retry: 1min → 5min → 30min
4. Mark published → remove từ queue
5. Feed kết quả vào notification engine
"""

import asyncio
import logging
import os
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta, timezone
from typing import Optional

logger = logging.getLogger(__name__)

# ─── Config ──────────────────────────────────────────────────────────────────
RETRY_BACKOFF_SECONDS = [60, 300, 1800]   # 1min, 5min, 30min
MAX_RETRIES           = 3
MAX_CONCURRENT_POSTS  = 4
POLL_INTERVAL_SECONDS = int(os.getenv("PUBLISH_POLL_INTERVAL", "30"))

# In-memory retry tracker: content_id → {retry_count, next_retry_at}
_retry_state: dict[str, dict] = {}


class PublishEngine:
    """
    Orchestrates publishing of scheduled social media content.
    Ported từ BrightBean PublishEngine (publisher/engine.py).
    """

    def __init__(self):
        self._running = False

    # ─── Main poll loop ───────────────────────────────────────────────────────

    def poll_and_publish(self) -> int:
        """
        Main poll tick — tìm content đã đến giờ → publish.
        Gọi theo timer (via APScheduler hoặc background thread).
        Returns: số content đã publish thành công.
        """
        from common.social.post_queue import get_due_for_publish, remove_from_queue
        from common.social.approval_gate import mark_published, mark_failed, get_content

        due_entries = get_due_for_publish()

        # Lọc bỏ entries đang trong retry wait
        now = datetime.now(timezone.utc)
        due_entries = [
            e for e in due_entries
            if e["content_id"] not in _retry_state
            or datetime.fromisoformat(_retry_state[e["content_id"]]["next_retry_at"]) <= now
        ]

        if not due_entries:
            return 0

        logger.info("🔔 Publish engine: %d posts due", len(due_entries))

        published_count = 0
        with ThreadPoolExecutor(max_workers=min(len(due_entries), MAX_CONCURRENT_POSTS)) as executor:
            futures = {
                executor.submit(self._publish_entry, entry): entry
                for entry in due_entries
            }
            for future in as_completed(futures):
                entry = futures[future]
                content_id = entry["content_id"]
                try:
                    result = future.result()
                    if result.get("success"):
                        content = get_content(content_id)
                        mark_published(content_id, result.get("platform_results", {}))
                        remove_from_queue(content_id)
                        if content_id in _retry_state:
                            del _retry_state[content_id]
                        published_count += 1
                        logger.info("✅ Published: %s", content_id)
                    else:
                        self._schedule_retry(content_id, result.get("error", "unknown error"))
                except Exception as e:
                    logger.exception("Unexpected publish error for %s", content_id)
                    self._schedule_retry(content_id, str(e))

        return published_count

    # ─── Publish single content to all platforms ──────────────────────────────

    def _publish_entry(self, entry: dict) -> dict:
        """
        Publish một queue entry lên tất cả platforms đã chọn.
        Returns: {"success": bool, "platform_results": dict, "error": str}
        """
        from common.social.approval_gate import get_content

        content_id = entry["content_id"]
        content = get_content(content_id)

        if not content:
            return {"success": False, "error": f"Content {content_id} not found"}

        if content.get("status") not in ("approved", "scheduled"):
            return {"success": False, "error": f"Status invalid: {content.get('status')}"}

        platforms = content.get("platforms", [])
        if not platforms:
            return {"success": False, "error": "No platforms configured"}

        platform_results = {}
        all_success = True
        errors = []

        # Publish tới từng platform parallel
        with ThreadPoolExecutor(max_workers=min(len(platforms), 3)) as executor:
            futures = {
                executor.submit(self._publish_to_platform, content, platform): platform
                for platform in platforms
            }
            for future in as_completed(futures):
                platform = futures[future]
                try:
                    result = future.result()
                    platform_results[platform] = result
                    if not result.get("success"):
                        all_success = False
                        errors.append(f"{platform}: {result.get('error', 'unknown')}")
                except Exception as e:
                    platform_results[platform] = {"success": False, "error": str(e)}
                    all_success = False
                    errors.append(f"{platform}: {e}")

        return {
            "success": all_success,
            "platform_results": platform_results,
            "error": "; ".join(errors) if errors else "",
        }

    def _publish_to_platform(self, content: dict, platform: str) -> dict:
        """
        Publish content lên một platform cụ thể.
        Lấy credentials từ env, gọi provider tương ứng.
        """
        start = time.monotonic()

        try:
            provider = self._get_provider(platform)
            access_token = self._get_token(platform)

            if not access_token:
                return {
                    "success": False,
                    "error": f"Không có access token cho {platform}. Set env: {platform.upper()}_ACCESS_TOKEN",
                }

            publish_content = {
                "text": content.get("caption", ""),
                "image_url": content.get("image_urls", [None])[0],
                "link": content.get("metadata", {}).get("link"),
                "page_id": os.getenv("FACEBOOK_PAGE_ID", "me"),
                "ig_user_id": os.getenv("INSTAGRAM_BUSINESS_ID", "me"),
                "title": content.get("title", ""),
                "privacy": "PUBLIC_TO_EVERYONE",
            }

            result = provider.publish_post(access_token, publish_content)
            duration_ms = int((time.monotonic() - start) * 1000)
            logger.info(
                "✅ Published to %s in %dms: %s",
                platform, duration_ms, result.get("post_id", ""),
            )
            return {**result, "success": True, "duration_ms": duration_ms}

        except Exception as e:
            duration_ms = int((time.monotonic() - start) * 1000)
            logger.error("❌ Publish to %s failed (%dms): %s", platform, duration_ms, e)
            return {"success": False, "error": str(e), "duration_ms": duration_ms}

    def _get_provider(self, platform: str):
        """Instantiate platform provider."""
        from common.social.providers import (
            FacebookProvider, InstagramProvider, TikTokProvider
        )
        mapping = {
            "facebook":  FacebookProvider,
            "instagram": InstagramProvider,
            "tiktok":    TikTokProvider,
        }
        cls = mapping.get(platform)
        if not cls:
            raise ValueError(f"Unsupported platform: {platform}")
        return cls()

    def _get_token(self, platform: str) -> str | None:
        """Lấy access token từ env."""
        env_map = {
            "facebook":  "FACEBOOK_PAGE_ACCESS_TOKEN",
            "instagram": "INSTAGRAM_ACCESS_TOKEN",
            "tiktok":    "TIKTOK_ACCESS_TOKEN",
        }
        return os.getenv(env_map.get(platform, ""), "")

    # ─── Retry logic ─────────────────────────────────────────────────────────

    def _schedule_retry(self, content_id: str, error: str):
        """Exponential backoff retry từ BrightBean."""
        from common.social.approval_gate import mark_failed

        state = _retry_state.get(content_id, {"retry_count": 0})
        retry_count = state["retry_count"]

        if retry_count >= MAX_RETRIES:
            logger.warning("❌ Max retries exceeded for %s: %s", content_id, error)
            mark_failed(content_id, f"Failed after {MAX_RETRIES} retries: {error}")
            if content_id in _retry_state:
                del _retry_state[content_id]
            return

        backoff = RETRY_BACKOFF_SECONDS[min(retry_count, len(RETRY_BACKOFF_SECONDS) - 1)]
        next_retry = (datetime.now(timezone.utc) + timedelta(seconds=backoff)).isoformat()

        _retry_state[content_id] = {
            "retry_count": retry_count + 1,
            "next_retry_at": next_retry,
            "last_error": error,
        }
        logger.info(
            "🔄 Retry %d/%d scheduled for %s in %ds",
            retry_count + 1, MAX_RETRIES, content_id, backoff,
        )


# ─── Background runner ────────────────────────────────────────────────────────

_engine = PublishEngine()
_bg_task: asyncio.Task | None = None


async def _publish_loop():
    """Background async loop — poll every POLL_INTERVAL_SECONDS."""
    logger.info("🚀 Publish engine started (interval: %ds)", POLL_INTERVAL_SECONDS)
    while True:
        try:
            loop = asyncio.get_event_loop()
            count = await loop.run_in_executor(None, _engine.poll_and_publish)
            if count > 0:
                logger.info("📤 Published %d posts this tick", count)
        except Exception as e:
            logger.error("Publish loop error: %s", e)
        await asyncio.sleep(POLL_INTERVAL_SECONDS)


def start_publish_engine(app=None):
    """
    Khởi động publish engine background loop.
    Gọi từ FastAPI startup event (server.py).
    """
    global _bg_task

    async def _start():
        global _bg_task
        _bg_task = asyncio.create_task(_publish_loop())
        logger.info("✅ Publish engine background task started")

    if app:
        @app.on_event("startup")
        async def startup():
            await _start()
    else:
        loop = asyncio.get_event_loop()
        loop.create_task(_publish_loop())


def get_retry_state() -> dict:
    """Expose retry state cho monitoring/dashboard."""
    return dict(_retry_state)


def manual_publish(content_id: str) -> dict:
    """
    Trigger publish ngay lập tức (không qua queue timing).
    Dùng cho "Publish Now" button trên dashboard.
    """
    from common.social.approval_gate import get_content, mark_failed, mark_published
    from common.social.post_queue import remove_from_queue

    content = get_content(content_id)
    if not content:
        return {"success": False, "error": "Content not found"}
    if content.get("status") not in ("approved", "scheduled", "draft"):
        return {"success": False, "error": f"Cannot publish with status: {content.get('status')}"}

    entry = {"content_id": content_id}
    result = _engine._publish_entry(entry)

    if result.get("success"):
        mark_published(content_id, result.get("platform_results", {}))
        remove_from_queue(content_id)
    else:
        mark_failed(content_id, result.get("error", "Manual publish failed"))

    return result
