#!/usr/bin/env python3
"""
FlareSolverr-based page fetcher for Grabbit
Bypasses Cloudflare and other anti-bot protections using a real browser.

Usage:
  python flaresolverr_fetch.py fetch <url>     # Fetch page HTML
  python flaresolverr_fetch.py extract <url>   # Extract video URLs from page
"""

import sys
import json
import re
import os

FLARESOLVERR_CONTAINER = os.environ.get('FLARESOLVERR_CONTAINER', 'grabbit-flaresolverr')


def output_json(data: dict):
    """Print JSON to stdout and exit"""
    print(json.dumps(data))
    sys.exit(0 if data.get('success') else 1)


def output_error(message: str, error_type: str = 'error'):
    """Print error JSON and exit"""
    output_json({
        'success': False,
        'error': message,
        'error_type': error_type
    })


try:
    import subprocess
except ImportError:
    output_error('subprocess not available', 'missing_dependency')


def fetch_with_flaresolverr(url: str, timeout: int = 60, session: str = None) -> dict:
    """
    Fetch a URL using FlareSolverr to bypass Cloudflare.
    Uses docker exec to communicate with the container.
    Returns the page HTML and cookies.
    If session is provided, reuses that session for cookies/state.
    """
    try:
        # Build the curl command to run inside the container
        payload_dict = {
            'cmd': 'request.get',
            'url': url,
            'maxTimeout': timeout * 1000,
        }
        if session:
            payload_dict['session'] = session
        payload = json.dumps(payload_dict)

        # Execute curl inside the FlareSolverr container
        result = subprocess.run(
            [
                'docker', 'exec', FLARESOLVERR_CONTAINER,
                'curl', '-s', '-X', 'POST',
                'http://localhost:8851/v1',
                '-H', 'Content-Type: application/json',
                '-d', payload
            ],
            capture_output=True,
            text=True,
            timeout=timeout + 30
        )

        if result.returncode != 0:
            # Check if container exists
            check = subprocess.run(
                ['docker', 'ps', '-q', '-f', f'name={FLARESOLVERR_CONTAINER}'],
                capture_output=True,
                text=True
            )
            if not check.stdout.strip():
                return {
                    'success': False,
                    'error': 'FlareSolverr not running - start with: docker-compose up -d',
                    'error_type': 'container_not_running'
                }
            return {
                'success': False,
                'error': result.stderr or 'Docker exec failed',
                'error_type': 'docker_error'
            }

        data = json.loads(result.stdout)

        if data.get('status') == 'ok':
            solution = data.get('solution', {})
            return {
                'success': True,
                'html': solution.get('response', ''),
                'url': solution.get('url', url),
                'cookies': solution.get('cookies', []),
                'user_agent': solution.get('userAgent', ''),
                'status': solution.get('status', 200)
            }
        else:
            return {
                'success': False,
                'error': data.get('message', 'FlareSolverr request failed'),
                'error_type': 'flaresolverr_error'
            }

    except subprocess.TimeoutExpired:
        return {
            'success': False,
            'error': f'FlareSolverr request timed out after {timeout}s',
            'error_type': 'timeout'
        }
    except json.JSONDecodeError as e:
        return {
            'success': False,
            'error': f'Invalid JSON response: {e}',
            'error_type': 'json_error'
        }
    except Exception as e:
        return {
            'success': False,
            'error': str(e),
            'error_type': 'request_error'
        }


def extract_video_urls(html: str, page_url: str) -> dict:
    """
    Extract video URLs from page HTML.
    Handles various video player formats including KVS.
    Prioritizes quality: 1080p > 720p > others.
    """
    videos = []

    # Patterns to exclude (thumbnails, previews, icons)
    exclude_patterns = [
        r'_preview\.mp4',
        r'preview_',
        r'_thumb',
        r'screenshot',
        r'/site\.webm',
        r'/icon',
        r'\.jpg',
        r'\.png',
        r'\.gif',
    ]

    def is_excluded(url: str) -> bool:
        url_lower = url.lower()
        return any(re.search(p, url_lower) for p in exclude_patterns)

    def get_quality_score(url: str) -> int:
        """Higher score = better quality"""
        if '1080p' in url or '1080' in url:
            return 100
        if '720p' in url or '720' in url:
            return 80
        if '480p' in url or '480' in url:
            return 60
        if '360p' in url or '360' in url:
            return 40
        # Full quality files often don't have resolution suffix
        if re.search(r'/\d+\.mp4/?$', url):
            return 90  # Likely full quality
        return 50

    # Priority 1: KVS-style URLs (function/N/URL format) - these are the actual video files
    kvs_urls = re.findall(r'function/\d+/(https?://[^\s"\']+\.mp4[^\s"\']*)', html)
    for url in kvs_urls:
        clean_url = url.rstrip('/')
        if not is_excluded(clean_url):
            videos.append({'url': clean_url, 'type': 'kvs', 'score': get_quality_score(clean_url)})

    # Priority 2: KVS get_file URLs (direct video files with hashes)
    kvs_direct = re.findall(
        r'https?://[^\s"\'<>]+/get_file/[^\s"\'<>]+\.mp4[^\s"\'<>]*',
        html,
        re.I
    )
    for url in kvs_direct:
        clean_url = url.rstrip('/')
        if not is_excluded(clean_url) and clean_url not in [v['url'] for v in videos]:
            videos.append({'url': clean_url, 'type': 'kvs_direct', 'score': get_quality_score(clean_url)})

    # Priority 3: Video source tags
    source_urls = re.findall(r'<source[^>]+src=["\']([^"\']+)["\']', html, re.I)
    for url in source_urls:
        if url.startswith('http') and not is_excluded(url):
            clean_url = url.rstrip('/')
            if clean_url not in [v['url'] for v in videos]:
                videos.append({'url': clean_url, 'type': 'source', 'score': get_quality_score(clean_url)})

    # Priority 4: JW Player / Video.js sources
    js_sources = re.findall(r'(?:file|src|source)["\']?\s*[:=]\s*["\']([^"\']+\.(?:mp4|m3u8)[^"\']*)["\']', html, re.I)
    for url in js_sources:
        if url.startswith('http') and not is_excluded(url):
            clean_url = url.rstrip('/')
            if clean_url not in [v['url'] for v in videos]:
                videos.append({'url': clean_url, 'type': 'player', 'score': get_quality_score(clean_url)})

    # Priority 5: Other direct video URLs (fallback)
    direct_urls = re.findall(
        r'https?://[^\s"\'<>]+\.(?:mp4|m3u8)(?:\?[^\s"\'<>]*)?',
        html,
        re.I
    )
    for url in direct_urls:
        clean_url = url.rstrip('/')
        if not is_excluded(clean_url) and clean_url not in [v['url'] for v in videos]:
            # Skip if it looks like a get_file URL (already handled)
            if '/get_file/' not in clean_url:
                videos.append({'url': clean_url, 'type': 'direct', 'score': get_quality_score(clean_url)})

    # Sort by quality score (best first)
    videos.sort(key=lambda x: x.get('score', 0), reverse=True)

    # Remove score from output
    unique_videos = [{'url': v['url'], 'type': v['type']} for v in videos]

    # Extract title
    title_match = re.search(r'<title>([^<]+)</title>', html, re.I)
    title = title_match.group(1).strip() if title_match else 'Unknown'
    title = re.sub(r'\s*[-|]\s*[^|]+$', '', title).strip()

    # Extract thumbnail
    thumb_patterns = [
        r'poster=["\']([^"\']+)["\']',
        r'thumbnail["\']?\s*[:=]\s*["\']([^"\']+)["\']',
        r'og:image["\'][^>]+content=["\']([^"\']+)["\']',
    ]
    thumbnail = ''
    for pattern in thumb_patterns:
        match = re.search(pattern, html, re.I)
        if match:
            thumbnail = match.group(1)
            break

    return {
        'success': True,
        'title': title,
        'thumbnail': thumbnail,
        'videos': unique_videos,
        'video_count': len(unique_videos),
        'page_url': page_url
    }


def main():
    if len(sys.argv) < 3:
        output_error('Usage: flaresolverr_fetch.py <fetch|extract> <url>', 'usage')

    command = sys.argv[1].lower()
    url = sys.argv[2]

    if command == 'fetch':
        result = fetch_with_flaresolverr(url)
        # Don't include full HTML in output for brevity
        if result.get('success'):
            result['html_length'] = len(result.get('html', ''))
            result['html'] = result.get('html', '')[:1000] + '...'
        output_json(result)

    elif command == 'extract':
        # First fetch the page
        fetch_result = fetch_with_flaresolverr(url)
        if not fetch_result.get('success'):
            output_json(fetch_result)
            return

        # Then extract video URLs
        extract_result = extract_video_urls(
            fetch_result.get('html', ''),
            fetch_result.get('url', url)
        )
        extract_result['cookies'] = fetch_result.get('cookies', [])
        extract_result['user_agent'] = fetch_result.get('user_agent', '')
        output_json(extract_result)

    else:
        output_error(f'Unknown command: {command}. Use: fetch, extract', 'usage')


if __name__ == '__main__':
    main()
