# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Privacy Player using Invidious.

Invidious is an alternative front-end for YouTube that:
- Doesn't track users
- Doesn't require Google account
- Provides direct video stream URLs
- Has built-in SponsorBlock support
"""

import asyncio
import json
import logging
import re
from typing import Any, Dict, List, Optional
from urllib.parse import urlencode

logger = logging.getLogger(__name__)

# Try to import aiohttp for async HTTP
try:
    import aiohttp

    HAS_AIOHTTP = True
except ImportError:
    HAS_AIOHTTP = False


# Public Invidious instances (fallbacks if configured one fails)
INVIDIOUS_INSTANCES = [
    "https://invidious.snopyta.org",
    "https://yewtu.be",
    "https://invidious.kavin.rocks",
    "https://vid.puffyan.us",
    "https://invidious.namazso.eu",
    "https://inv.riverside.rocks",
]

# Quality mappings
QUALITY_ITAGS = {
    "360p": ["18"],  # 360p MP4
    "480p": ["135", "244"],  # 480p
    "720p": ["22", "136", "247"],  # 720p
    "1080p": ["137", "248", "303"],  # 1080p
    "best": ["313", "271", "308", "303", "248", "137", "22"],
}


class PrivacyPlayer:
    """
    Privacy-focused video player using Invidious.

    For YouTube videos, uses Invidious API to:
    - Get video info without Google tracking
    - Get direct stream URLs
    - Search videos privately

    For non-YouTube videos, passes through to yt-dlp.
    """

    def __init__(self, config):
        self.config = config
        self.instance = config.invidious_instance
        self._session: Optional[aiohttp.ClientSession] = None

    async def _get_session(self) -> aiohttp.ClientSession:
        """Get or create HTTP session."""
        if self._session is None or self._session.closed:
            timeout = aiohttp.ClientTimeout(total=30)
            self._session = aiohttp.ClientSession(timeout=timeout)
        return self._session

    async def _api_request(
        self,
        endpoint: str,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make request to Invidious API."""
        if not HAS_AIOHTTP:
            return await self._api_request_urllib(endpoint, params)

        session = await self._get_session()

        url = f"{self.instance}/api/v1/{endpoint}"
        if params:
            url += "?" + urlencode(params)

        try:
            async with session.get(url) as response:
                if response.status == 200:
                    return await response.json()
                else:
                    # Try fallback instances
                    return await self._try_fallback_instances(endpoint, params)
        except Exception as e:
            logger.warning(f"Invidious request failed: {e}")
            return await self._try_fallback_instances(endpoint, params)

    async def _try_fallback_instances(
        self,
        endpoint: str,
        params: Optional[Dict[str, Any]],
    ) -> Dict[str, Any]:
        """Try fallback Invidious instances."""
        session = await self._get_session()

        for instance in INVIDIOUS_INSTANCES:
            if instance == self.instance:
                continue

            url = f"{instance}/api/v1/{endpoint}"
            if params:
                url += "?" + urlencode(params)

            try:
                async with session.get(url) as response:
                    if response.status == 200:
                        logger.info(f"Using fallback instance: {instance}")
                        return await response.json()
            except Exception as e:
                logger.debug("Error suppressed: %s", e)
                continue
        raise Exception("All Invidious instances failed")

    async def _api_request_urllib(
        self,
        endpoint: str,
        params: Optional[Dict[str, Any]],
    ) -> Dict[str, Any]:
        """Fallback using urllib."""
        import urllib.request

        url = f"{self.instance}/api/v1/{endpoint}"
        if params:
            url += "?" + urlencode(params)

        def fetch():
            req = urllib.request.Request(url)
            req.add_header("User-Agent", "Familiar/1.0")

            with urllib.request.urlopen(req, timeout=30) as response:
                return json.loads(response.read().decode("utf-8"))

        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(None, fetch)

    def _extract_youtube_id(self, url: str) -> Optional[str]:
        """Extract YouTube video ID from URL."""
        patterns = [
            r"youtube\.com/watch\?v=([a-zA-Z0-9_-]+)",
            r"youtu\.be/([a-zA-Z0-9_-]+)",
            r"youtube\.com/embed/([a-zA-Z0-9_-]+)",
            r"youtube\.com/v/([a-zA-Z0-9_-]+)",
        ]

        for pattern in patterns:
            match = re.search(pattern, url)
            if match:
                return match.group(1)

        return None

    def _is_youtube(self, url: str) -> bool:
        """Check if URL is a YouTube video."""
        return bool(re.search(r"youtube\.com|youtu\.be", url, re.IGNORECASE))

    async def get_stream(
        self,
        url: str,
        quality: str = "720p",
    ) -> Dict[str, Any]:
        """
        Get a privacy-friendly stream URL.

        Args:
            url: Video URL
            quality: Desired quality

        Returns:
            Stream info with URL, title, duration, etc.
        """
        if self._is_youtube(url):
            video_id = self._extract_youtube_id(url)
            if video_id:
                return await self._get_youtube_stream(video_id, quality)

        # For non-YouTube, return original URL (will need yt-dlp)
        return {
            "stream_url": url,
            "title": "Unknown",
            "duration": 0,
            "thumbnail": None,
            "privacy_proxy": False,
        }

    async def _get_youtube_stream(
        self,
        video_id: str,
        quality: str,
    ) -> Dict[str, Any]:
        """Get YouTube stream via Invidious."""
        data = await self._api_request(f"videos/{video_id}")

        # Find best matching format
        stream_url = None

        # Try adaptive formats first
        formats = data.get("adaptiveFormats", []) + data.get("formatStreams", [])

        # Get quality itags
        target_itags = QUALITY_ITAGS.get(quality, QUALITY_ITAGS["720p"])

        # Find matching format
        for itag in target_itags:
            for fmt in formats:
                if str(fmt.get("itag")) == itag:
                    stream_url = fmt.get("url")
                    if stream_url:
                        break
            if stream_url:
                break

        # Fallback to first available format
        if not stream_url and formats:
            for fmt in formats:
                if fmt.get("url"):
                    stream_url = fmt["url"]
                    break

        return {
            "stream_url": stream_url,
            "title": data.get("title", "Unknown"),
            "duration": data.get("lengthSeconds", 0),
            "thumbnail": self._get_best_thumbnail(data),
            "description": data.get("description", ""),
            "author": data.get("author", ""),
            "privacy_proxy": True,
            "video_id": video_id,
        }

    def _get_best_thumbnail(self, data: Dict[str, Any]) -> Optional[str]:
        """Get best quality thumbnail."""
        thumbnails = data.get("videoThumbnails", [])

        # Prefer maxres or high quality
        for quality in ["maxres", "sddefault", "high", "medium"]:
            for thumb in thumbnails:
                if thumb.get("quality") == quality:
                    return thumb.get("url")

        return thumbnails[0]["url"] if thumbnails else None

    async def get_embed_html(
        self,
        url: str,
        width: int = 640,
        height: int = 360,
        autoplay: bool = False,
    ) -> str:
        """
        Get embeddable HTML for privacy playback.

        Uses Invidious embed for YouTube.
        """
        if self._is_youtube(url):
            video_id = self._extract_youtube_id(url)
            if video_id:
                autoplay_param = "1" if autoplay else "0"
                return f'''
<iframe
    width="{width}"
    height="{height}"
    src="{self.instance}/embed/{video_id}?autoplay={autoplay_param}"
    frameborder="0"
    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
    allowfullscreen>
</iframe>
'''

        # Fallback: HTML5 video tag with stream URL
        stream = await self.get_stream(url)
        if stream.get("stream_url"):
            autoplay_attr = "autoplay" if autoplay else ""
            return f'''
<video
    width="{width}"
    height="{height}"
    controls
    {autoplay_attr}
    poster="{stream.get("thumbnail", "")}">
    <source src="{stream["stream_url"]}" type="video/mp4">
    Your browser does not support the video tag.
</video>
'''

        return f"<p>Unable to embed video: {url}</p>"

    async def search(
        self,
        query: str,
        max_results: int = 10,
    ) -> List[Dict[str, Any]]:
        """
        Search for videos privately using Invidious.

        Args:
            query: Search query
            max_results: Maximum results

        Returns:
            List of video results
        """
        data = await self._api_request(
            "search",
            {
                "q": query,
                "type": "video",
            },
        )

        results = []
        for item in data[:max_results]:
            if item.get("type") != "video":
                continue

            results.append(
                {
                    "video_id": item.get("videoId"),
                    "title": item.get("title"),
                    "author": item.get("author"),
                    "duration": item.get("lengthSeconds", 0),
                    "duration_formatted": self._format_duration(item.get("lengthSeconds", 0)),
                    "view_count": item.get("viewCount"),
                    "thumbnail": self._get_thumbnail_from_list(item.get("videoThumbnails", [])),
                    "url": f"https://youtube.com/watch?v={item.get('videoId')}",
                    "privacy_url": f"{self.instance}/watch?v={item.get('videoId')}",
                }
            )

        return results

    async def get_trending(
        self,
        region: str = "US",
        category: str = "default",
    ) -> List[Dict[str, Any]]:
        """
        Get trending videos.

        Args:
            region: Region code (US, UK, etc.)
            category: Category (default, music, gaming, news, movies)

        Returns:
            List of trending videos
        """
        endpoint = "trending"
        params = {"region": region}

        if category != "default":
            params["type"] = category

        data = await self._api_request(endpoint, params)

        results = []
        for item in data:
            results.append(
                {
                    "video_id": item.get("videoId"),
                    "title": item.get("title"),
                    "author": item.get("author"),
                    "duration": item.get("lengthSeconds", 0),
                    "view_count": item.get("viewCount"),
                    "thumbnail": self._get_thumbnail_from_list(item.get("videoThumbnails", [])),
                    "url": f"https://youtube.com/watch?v={item.get('videoId')}",
                }
            )

        return results

    async def get_channel_videos(
        self,
        channel_id: str,
        max_results: int = 30,
    ) -> List[Dict[str, Any]]:
        """Get videos from a channel."""
        data = await self._api_request(f"channels/{channel_id}/videos")

        results = []
        for item in data.get("videos", [])[:max_results]:
            results.append(
                {
                    "video_id": item.get("videoId"),
                    "title": item.get("title"),
                    "duration": item.get("lengthSeconds", 0),
                    "view_count": item.get("viewCount"),
                    "thumbnail": self._get_thumbnail_from_list(item.get("videoThumbnails", [])),
                    "published": item.get("published"),
                }
            )

        return results

    def _get_thumbnail_from_list(self, thumbnails: List[Dict]) -> Optional[str]:
        """Get thumbnail URL from list."""
        for thumb in thumbnails:
            if thumb.get("quality") in ["medium", "high", "sddefault"]:
                return thumb.get("url")
        return thumbnails[0]["url"] if thumbnails else None

    def _format_duration(self, seconds: int) -> str:
        """Format duration as HH:MM:SS or MM:SS."""
        hours, remainder = divmod(seconds, 3600)
        minutes, secs = divmod(remainder, 60)
        if hours:
            return f"{hours}:{minutes:02d}:{secs:02d}"
        return f"{minutes}:{secs:02d}"

    async def close(self):
        """Close the session."""
        if self._session and not self._session.closed:
            await self._session.close()
