from typing import List, Optional
import asyncio
import tempfile
import os
import shutil
import httpx
from common import logging

logger = logging.get_logger(__name__)

FFMPEG_BIN = "ffmpeg"

IG_ARGS = [
    "-c:v",
    "libx264",
    "-preset",
    "veryfast",
    "-profile:v",
    "high",
    "-level",
    "4.0",
    "-pix_fmt",
    "yuv420p",
    "-vf",
    "scale='min(1080,iw)':'min(1920,ih)':force_original_aspect_ratio=decrease",
    "-r",
    "30",
    "-g",
    "60",
    "-x264-params",
    "keyint=60:min-keyint=60:scenecut=0",
    "-movflags",
    "+faststart",
    "-c:a",
    "aac",
    "-b:a",
    "128k",
    "-ar",
    "48000",
    "-ac",
    "2",
    "-b:v",
    "6M",
    "-maxrate",
    "8M",
    "-bufsize",
    "12M",
]


class InstagramPublisher:
    FB_API_VERSION = "v23.0"
    GRAPH = f"https://graph.facebook.com/{FB_API_VERSION}"
    RUPLOAD = f"https://rupload.facebook.com/ig-api-upload/{FB_API_VERSION}"

    @staticmethod
    def resolve_ffmpeg_bin() -> str:
        cand = os.environ.get("FFMPEG_BIN", "ffmpeg")
        path = shutil.which(cand)
        if path:
            return path
        raise RuntimeError(
            "ffmpeg binary not found. Install ffmpeg or set env FFMPEG_BIN to the absolute path"
        )

    @staticmethod
    def _log_http_error(prefix: str, resp: httpx.Response, extra: dict) -> None:
        # Meta suele mandar el detalle en JSON: {"error": {...}}
        text = resp.text
        try:
            payload = resp.json()
        except Exception:
            payload = None

        logger.error(
            prefix,
            extra={
                **extra,
                "status_code": resp.status_code,
                "response_text": text[:2000],
                "response_json": payload,
                "x_fb_trace_id": resp.headers.get("x-fb-trace-id"),
                "www_authenticate": resp.headers.get("www-authenticate"),
            },
        )

    @staticmethod
    async def transcode_bytes_to_ig_safe(video_bytes: bytes) -> str:
        tmp_in = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
        tmp_out = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
        ffmpeg_bin = InstagramPublisher.resolve_ffmpeg_bin()
        try:
            with open(tmp_in.name, "wb") as f:
                f.write(video_bytes)
            cmd = [ffmpeg_bin, "-y", "-i", tmp_in.name, *IG_ARGS, tmp_out.name]
            proc = await asyncio.create_subprocess_exec(
                *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
            )
            _, stderr = await proc.communicate()
            if proc.returncode != 0:
                raise RuntimeError(
                    f"ffmpeg failed ({proc.returncode}): {stderr.decode(errors='ignore')}"
                )
            return tmp_out.name
        except Exception as e:
            logger.error("IG /transcode_bytes_to_ig_safe failed: %s", e)
            if os.path.exists(tmp_out.name):
                os.unlink(tmp_out.name)
            raise
        finally:
            if os.path.exists(tmp_in.name):
                os.unlink(tmp_in.name)

    @staticmethod
    async def get_user_pages(user_access_token: str) -> List[dict]:
        url = f"{InstagramPublisher.GRAPH}/me/accounts"
        params = {"access_token": user_access_token}
        async with httpx.AsyncClient(timeout=30) as client:
            r = await client.get(url, params=params)
            if r.status_code >= 400:
                InstagramPublisher._log_http_error(
                    "[Instagram] get_user_pages failed", r, {}
                )
            r.raise_for_status()
            return r.json().get("data", [])

    @staticmethod
    async def get_ig_user_from_page(
        page_id: str, user_access_token: str
    ) -> Optional[str]:
        url = f"{InstagramPublisher.GRAPH}/{page_id}"
        params = {
            "fields": "instagram_business_account",
            "access_token": user_access_token,
        }
        async with httpx.AsyncClient(timeout=30) as client:
            r = await client.get(url, params=params)
            r.raise_for_status()
            ig = r.json().get("instagram_business_account")
            return ig.get("id") if ig else None

    @staticmethod
    async def resolve_ig_user_id(user_access_token: str) -> str:
        pages = await InstagramPublisher.get_user_pages(user_access_token)
        if not pages:
            raise RuntimeError("IG Page not found.")
        logger.error(
            "checking IG Pages",
            extra={"pages": pages},
        )
        for p in pages:
            logger.error(
                "checking IG Pages (indiviual record)",
                extra={"page": p},
            )
            page_id = p.get("id")
            if not page_id:
                continue
            ig_user_id = await InstagramPublisher.get_ig_user_from_page(
                page_id, user_access_token
            )
            logger.error(
                "checking IG Pages (ig_user_id)",
                extra={"ig_user_id": ig_user_id},
            )
            if ig_user_id:
                return ig_user_id
        raise RuntimeError("No IG Pages found.")

    @staticmethod
    async def create_media_container_video(
        ig_user_id: str,
        user_access_token: str,
        caption: Optional[str] = None,
    ) -> str:
        url = f"{InstagramPublisher.GRAPH}/{ig_user_id}/media"
        data = {
            "access_token": user_access_token,
            "media_type": "REELS",
            "upload_type": "resumable",
        }
        if caption:
            data["caption"] = caption
        async with httpx.AsyncClient(timeout=None) as client:
            r = await client.post(url, data=data)
            if r.status_code >= 400:
                InstagramPublisher._log_http_error(
                    "[Instagram] create_media_container_video failed",
                    r,
                    {"ig_user_id": ig_user_id, "media_type": "REELS"},
                )
            r.raise_for_status()
            return r.json()["id"]

    @staticmethod
    async def create_media_container_story(
        ig_user_id: str, user_access_token: str
    ) -> str:
        url = f"{InstagramPublisher.GRAPH}/{ig_user_id}/media"
        data = {
            "access_token": user_access_token,
            "media_type": "STORIES",
            "upload_type": "resumable",
        }

        async with httpx.AsyncClient(timeout=None) as client:
            r = await client.post(url, data=data)
            if r.status_code >= 400:
                InstagramPublisher._log_http_error(
                    "[Instagram] create_media_container_story failed",
                    r,
                    {"ig_user_id": ig_user_id, "media_type": "STORY"},
                )
            r.raise_for_status()
            return r.json()["id"]

    @staticmethod
    async def rupload_via_file_url(
        creation_id: str,
        user_access_token: str,
        video_bytes: bytes,
    ) -> None:
        out_path = await InstagramPublisher.transcode_bytes_to_ig_safe(video_bytes)
        file_size = os.path.getsize(out_path)
        url = f"{InstagramPublisher.RUPLOAD}/{creation_id}"
        logger.info(
            "[Instagram] rupload starting",
            extra={
                "creation_id": creation_id,
                "url": url,
                "file_size_bytes": file_size,
                "file_size_mb": round(file_size / (1024 * 1024), 2),
            },
        )
        try:
            with open(out_path, "rb") as f:
                data = f.read()
            headers = {
                "Authorization": f"OAuth {user_access_token}",
                "Content-Type": "application/octet-stream",
                "Offset": "0",
                "X-Entity-Length": str(file_size),
                "X-Entity-Name": creation_id,
                "X-Entity-Type": "video/mp4",
            }
            async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
                r = await client.post(url, headers=headers, content=data)
                if r.status_code >= 400:
                    InstagramPublisher._log_http_error(
                        "[Instagram] rupload failed",
                        r,
                        {
                            "creation_id": creation_id,
                            "file_size_bytes": file_size,
                            "file_size_mb": round(file_size / (1024 * 1024), 2),
                        },
                    )
                r.raise_for_status()
        finally:
            if os.path.exists(out_path):
                os.unlink(out_path)

    @staticmethod
    async def publish_media(
        ig_user_id: str, user_access_token: str, creation_id: str
    ) -> str:
        url = f"{InstagramPublisher.GRAPH}/{ig_user_id}/media_publish"
        data = {"access_token": user_access_token, "creation_id": creation_id}
        async with httpx.AsyncClient(timeout=60) as client:
            r = await client.post(url, data=data)
            if r.status_code >= 400:
                InstagramPublisher._log_http_error(
                    "[Instagram] publish_media failed",
                    r,
                    {"creation_id": creation_id, "ig_user_id": ig_user_id},
                )
            r.raise_for_status()
            return r.json()["id"]

    @staticmethod
    async def get_container_status(creation_id: str, user_access_token: str) -> dict:
        url = f"{InstagramPublisher.GRAPH}/{creation_id}"
        params = {"fields": "status_code,status", "access_token": user_access_token}
        async with httpx.AsyncClient(timeout=30) as client:
            r = await client.get(url, params=params)
            if r.status_code >= 400:
                InstagramPublisher._log_http_error(
                    "[Instagram] get_container_status failed",
                    r,
                    {"creation_id": creation_id},
                )
            r.raise_for_status()
            return r.json()
