"""
Instagram Graph API Helper Functions.

This module contains all the functions needed to interact with the
Instagram Graph API for fetching user data and insights.
"""

import requests
from django.conf import settings
from typing import Dict, List, Optional
import logging
import base64
import json

logger = logging.getLogger(__name__)


class InstagramAPIError(Exception):
    """Custom exception for Instagram API errors"""
    pass


def get_oauth_url(user_id: int) -> str:
    """
    Generate the Instagram OAuth authorization URL.
    
    This is the URL the influencer will be redirected to for
    granting permissions to your app.
    
    Args:
        user_id: The ID of the user who is authorizing (stored in state for callback)
    
    Returns:
        str: The full OAuth URL with user_id encoded in state parameter
    """
    scopes = ','.join(settings.INSTAGRAM_SCOPES)
    base_url = "https://www.facebook.com/v24.0/dialog/oauth"
    
    # Encode user_id in state parameter for security
    # State will be returned by Facebook in the callback
    state_data = {
        'user_id': user_id
    }
    state_encoded = base64.b64encode(json.dumps(state_data).encode()).decode()
    
    params = {
        'client_id': settings.FACEBOOK_APP_ID,
        'redirect_uri': settings.INSTAGRAM_REDIRECT_URI,
        'scope': scopes,
        'response_type': 'code',
        'state': state_encoded
    }
    
    query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
    return f"{base_url}?{query_string}"


def decode_oauth_state(state: str) -> Optional[Dict]:
    """
    Decode the OAuth state parameter to extract user_id.
    
    Args:
        state: The state parameter from OAuth callback
    
    Returns:
        dict: Contains 'user_id' or None if invalid
    """
    try:
        decoded = base64.b64decode(state.encode()).decode()
        return json.loads(decoded)
    except Exception as e:
        logger.error(f"Failed to decode OAuth state: {e}")
        return None


def exchange_code_for_token(code: str) -> Dict:
    """
    Exchange the authorization code for an access token.
    
    Args:
        code: The authorization code from the OAuth callback
    
    Returns:
        dict: Contains 'access_token' and 'token_type'
    
    Raises:
        InstagramAPIError: If the token exchange fails
    """
    url = "https://graph.facebook.com/v24.0/oauth/access_token"
    
    params = {
        'client_id': settings.FACEBOOK_APP_ID,
        'client_secret': settings.FACEBOOK_APP_SECRET,
        'redirect_uri': settings.INSTAGRAM_REDIRECT_URI,
        'code': code
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()
        
        if 'access_token' not in data:
            raise InstagramAPIError("No access token in response")
        
        return data
    except requests.RequestException as e:
        logger.error(f"Token exchange failed: {str(e)}")
        raise InstagramAPIError(f"Failed to exchange code for token: {str(e)}")


def get_long_lived_token(short_lived_token: str) -> Dict:
    """
    Exchange a short-lived token for a long-lived token (60 days).
    
    Args:
        short_lived_token: The short-lived access token
    
    Returns:
        dict: Contains 'access_token', 'token_type', and 'expires_in'
    
    Raises:
        InstagramAPIError: If the exchange fails
    """
    url = "https://graph.facebook.com/v24.0/oauth/access_token"
    
    params = {
        'grant_type': 'fb_exchange_token',
        'client_id': settings.FACEBOOK_APP_ID,
        'client_secret': settings.FACEBOOK_APP_SECRET,
        'fb_exchange_token': short_lived_token
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Long-lived token exchange failed: {str(e)}")
        raise InstagramAPIError(f"Failed to get long-lived token: {str(e)}")


def get_facebook_pages(access_token: str) -> List[Dict]:
    """
    Get all Facebook Pages the user manages.
    
    Args:
        access_token: User's access token
    
    Returns:
        list: List of pages with 'id', 'name', 'access_token'
    """
    url = f"https://graph.facebook.com/v24.0/me/accounts"
    
    params = {
        'access_token': access_token
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()
        return data.get('data', [])
    except requests.RequestException as e:
        logger.error(f"Failed to get Facebook pages: {str(e)}")
        raise InstagramAPIError(f"Failed to get pages: {str(e)}")


def get_instagram_business_account(page_id: str, page_access_token: str) -> Optional[str]:
    """
    Get Instagram Business Account ID linked to a Facebook Page.
    
    Args:
        page_id: Facebook Page ID
        page_access_token: Page access token
    
    Returns:
        str: Instagram Business Account ID or None
    """
    url = f"https://graph.facebook.com/v24.0/{page_id}"
    
    params = {
        'fields': 'instagram_business_account',
        'access_token': page_access_token
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()
        return data.get('instagram_business_account', {}).get('id')
    except requests.RequestException as e:
        logger.error(f"Failed to get Instagram business account: {str(e)}")
        return None


def get_instagram_account_info(ig_user_id: str, access_token: str) -> Dict:
    """
    Get basic Instagram account information.
    
    Args:
        ig_user_id: Instagram Business Account ID
        access_token: Access token
    
    Returns:
        dict: Contains 'username', 'followers_count', 'media_count'
    """
    url = f"https://graph.facebook.com/v24.0/{ig_user_id}"
    
    params = {
        'fields': 'username,followers_count,media_count',
        'access_token': access_token
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get account info: {str(e)}")
        raise InstagramAPIError(f"Failed to get account info: {str(e)}")


def get_account_insights(
    ig_user_id: str,
    access_token: str,
    metrics: List[str],
    period: str = 'day',
    metric_type: str = None,
    breakdowns: str = None,
    timeframe: str = None,
) -> Dict:
    """
    Get account-level insights (profile views, follower count, etc.).
    
    Args:
        ig_user_id: Instagram Business Account ID
        access_token: Access token
        metrics: List of metrics to fetch (e.g., ['profile_views'])
        period: Time period ('day', 'week', 'days_28', 'lifetime')
        metric_type: Optional metric_type (e.g., 'total_value')
        breakdowns: Optional breakdowns (e.g., 'age,gender')
        timeframe: Optional timeframe (e.g., 'this_month', 'this_week')
    
    Returns:
        dict: Insights data
    """
    url = f"https://graph.facebook.com/v24.0/{ig_user_id}/insights"
    
    params = {
        'metric': ','.join(metrics),
        'period': period,
        'access_token': access_token
    }
    if metric_type:
        params['metric_type'] = metric_type
    if breakdowns:
        params['breakdowns'] = breakdowns
    if timeframe:
        params['timeframe'] = timeframe
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get account insights: {str(e)}")
        raise InstagramAPIError(f"Failed to get insights: {str(e)}")


def get_user_media(ig_user_id: str, access_token: str, limit: int = 100) -> List[Dict]:
    """
    Get user's media (posts).
    
    Args:
        ig_user_id: Instagram Business Account ID
        access_token: Access token
        limit: Number of posts to fetch (max 100)
    
    Returns:
        list: List of media objects with basic info
    """
    url = f"https://graph.facebook.com/v24.0/{ig_user_id}/media"
    
    params = {
        'fields': 'id,caption,media_type,timestamp,permalink,like_count,comments_count',
        'limit': limit,
        'access_token': access_token
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()
        return data.get('data', [])
    except requests.RequestException as e:
        logger.error(f"Failed to get user media: {str(e)}")
        raise InstagramAPIError(f"Failed to get media: {str(e)}")


def get_media_insights(media_id: str, access_token: str, media_type: str = 'IMAGE') -> Dict:
    """
    Get insights for a specific media post with robust fallback.
    
    Tries group fetch first. If that fails (due to one bad metric), 
    it retries metrics individually to get as much data as possible.
    
    Args:
        media_id: Media ID
        access_token: Access token
        media_type: Type of media ('IMAGE', 'VIDEO', 'CAROUSEL_ALBUM')
    
    Returns:
        dict: Media insights data (always returns dict, may be empty)
    """
    url = f"https://graph.facebook.com/v24.0/{media_id}/insights"
    
    # Different metrics for different media types (matches test_instagram_api.py)
    if media_type == 'VIDEO':
        target_metrics = ['impressions', 'reach', 'engagement', 'saved', 'video_views']
    else:
        target_metrics = ['impressions', 'reach', 'engagement', 'saved']
    
    # STEP 1: Attempt group fetch (fastest)
    params = {
        'metric': ','.join(target_metrics),
        'access_token': access_token
    }
    
    try:
        response = requests.get(url, params=params, timeout=30)
        if response.status_code == 200:
            return response.json()
    except:
        pass  # Fail silently, move to fallback
    
    # STEP 2: Fallback - Individual fetch (slower but safer)
    # This allows us to get 'reach' even if 'saved' fails
    logger.warning(f"Group fetch failed for {media_id}, trying individual metrics")
    
    results_data = []
    for metric in target_metrics:
        try:
            params = {'metric': metric, 'access_token': access_token}
            response = requests.get(url, params=params, timeout=30)
            
            if response.status_code == 200:
                data = response.json().get('data', [])
                if data:
                    results_data.extend(data)
        except:
            pass  # Continue trying other metrics
    
    # Return in same format as group fetch
    if results_data:
        return {'data': results_data}
    else:
        logger.warning(f"No insights available for media {media_id}")
        return {'data': []}


def get_follower_demographics(ig_user_id: str, access_token: str, breakdowns: str = 'age,gender') -> Dict:
    """
    Get follower demographics for the IG user.

    Meta supports different `breakdowns` values (e.g. age,gender | country | city).
    Use separate calls per breakdown shape when the API rejects combined dimensions.

    Uses metric_type=total_value per Meta v20+ requirements.
    """
    url = f"https://graph.facebook.com/v24.0/{ig_user_id}/insights"
    params = {
        'metric': 'follower_demographics',
        'period': 'lifetime',
        'metric_type': 'total_value',
        'breakdowns': breakdowns,
        'access_token': access_token
    }
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get follower demographics ({breakdowns}): {str(e)}")
        raise InstagramAPIError(f"Failed to get follower demographics ({breakdowns}): {str(e)}")


def get_audience_demographics(
    ig_user_id: str,
    access_token: str,
    metric: str,
    timeframe: str = 'this_month',
    breakdowns: str = 'age'
) -> Dict:
    """
    Generic audience demographics fetcher for reached/engaged demographics.
    
    Note: timeframe options have changed in v20+. Avoid last_90_days.
    """
    url = f"https://graph.facebook.com/v24.0/{ig_user_id}/insights"
    params = {
        'metric': metric,
        'period': 'lifetime',
        'metric_type': 'total_value',
        'timeframe': timeframe,
        'breakdowns': breakdowns,
        'access_token': access_token
    }
    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get audience demographics ({metric}): {str(e)}")
        raise InstagramAPIError(f"Failed to get audience demographics ({metric}): {str(e)}")






