#!/usr/bin/env python3
"""
Instagram Competitive Intelligence Scraper
Extends BaseCompetitiveScraper to scrape competitor Instagram accounts

Python Best Practices Applied:
- Comprehensive type hints with specific exception handling
- Custom exception classes for Instagram-specific errors
- Resource management with proper session handling
- Input validation and data sanitization
- Structured logging with contextual information
- Rate limiting with exponential backoff
"""

import os
import time
import random
import logging
import contextlib
from typing import Any, Dict, List, Optional, cast
from datetime import datetime, timedelta
from pathlib import Path
import instaloader
from instaloader.structures import Profile, Post
from instaloader.exceptions import (
    ProfileNotExistsException, PrivateProfileNotFollowedException,
    LoginRequiredException, TwoFactorAuthRequiredException,
    BadCredentialsException
)

from .base_competitive_scraper import BaseCompetitiveScraper, CompetitiveConfig
from .exceptions import (
    InstagramError, InstagramLoginError, InstagramProfileNotFoundError,
    InstagramPostNotFoundError, InstagramPrivateAccountError,
    RateLimitError, ConfigurationError, DataValidationError,
    handle_instagram_error
)
from .types import (
    InstagramPostItem, Platform, CompetitivePriority
)


class InstagramCompetitiveScraper(BaseCompetitiveScraper):
    """Instagram competitive intelligence scraper using instaloader with proxy support."""
    
    # Competitor account configurations
    COMPETITOR_ACCOUNTS = {
        'ac_service_tech': {
            'username': 'acservicetech',
            'name': 'AC Service Tech',
            'url': 'https://www.instagram.com/acservicetech'
        },
        'love2hvac': {
            'username': 'love2hvac',
            'name': 'Love2HVAC',
            'url': 'https://www.instagram.com/love2hvac'
        },
        'hvac_learning_solutions': {
            'username': 'hvaclearningsolutions',
            'name': 'HVAC Learning Solutions',
            'url': 'https://www.instagram.com/hvaclearningsolutions'
        }
    }
    
    def __init__(self, data_dir: Path, logs_dir: Path, competitor_key: str):
        """Initialize Instagram competitive scraper for specific competitor."""
        if competitor_key not in self.COMPETITOR_ACCOUNTS:
            raise ConfigurationError(
                f"Unknown Instagram competitor: {competitor_key}",
                {'available_competitors': list(self.COMPETITOR_ACCOUNTS.keys())}
            )
        
        competitor_info = self.COMPETITOR_ACCOUNTS[competitor_key]
        
        # Create competitive configuration with more conservative rate limits
        config = CompetitiveConfig(
            source_name=f"Instagram_{competitor_info['name'].replace(' ', '')}",
            brand_name="hkia",
            data_dir=data_dir,
            logs_dir=logs_dir,
            competitor_name=competitor_key,
            base_url=competitor_info['url'],
            timezone=os.getenv('TIMEZONE', 'America/Halifax'),
            use_proxy=True,
            request_delay=5.0,  # More conservative for Instagram
            backlog_limit=50,  # Smaller limit for Instagram
            max_concurrent_requests=1  # Sequential only for Instagram
        )
        
        super().__init__(config)
        
        # Store competitor details
        self.competitor_key = competitor_key
        self.competitor_info = competitor_info
        self.target_username = competitor_info['username']
        
        # Instagram credentials (use HKIA account for competitive scraping)
        self.username = os.getenv('INSTAGRAM_USERNAME')
        self.password = os.getenv('INSTAGRAM_PASSWORD')
        
        if not self.username or not self.password:
            raise ConfigurationError(
                "Instagram credentials not configured",
                {
                    'required_env_vars': ['INSTAGRAM_USERNAME', 'INSTAGRAM_PASSWORD'],
                    'username_provided': bool(self.username),
                    'password_provided': bool(self.password)
                }
            )
        
        # Session file for persistence
        self.session_file = self.config.data_dir / '.sessions' / f'competitive_{self.username}_{competitor_key}.session'
        self.session_file.parent.mkdir(parents=True, exist_ok=True)
        
        # Initialize instaloader with competitive settings
        self.loader = self._setup_competitive_loader()
        self._login()
        
        # Profile metadata cache
        self.profile_metadata = {}
        self.target_profile = None
        
        # Request tracking for aggressive rate limiting
        self.request_count = 0
        self.max_requests_per_hour = 50  # Very conservative for competitive scraping
        self.last_request_reset = time.time()
        
        self.logger.info(f"Instagram competitive scraper initialized for {competitor_info['name']}")

    def _setup_competitive_loader(self) -> instaloader.Instaloader:
        """Setup instaloader with competitive intelligence optimizations."""
        # Use different user agent from HKIA scraper
        competitive_user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
        ]
        
        loader = instaloader.Instaloader(
            quiet=True,
            user_agent=random.choice(competitive_user_agents),
            dirname_pattern=str(self.config.data_dir / 'competitive_intelligence' / self.competitor_key / 'media'),
            filename_pattern=f'{self.competitor_key}_{{date_utc}}_UTC_{{shortcode}}',
            download_pictures=False,  # Don't download media by default
            download_videos=False,
            download_video_thumbnails=False,
            download_geotags=False,
            download_comments=False,
            save_metadata=False,
            compress_json=False,
            post_metadata_txt_pattern='',
            storyitem_metadata_txt_pattern='',
            max_connection_attempts=2,
            request_timeout=30.0
        )
        
        # Configure proxy if available
        if self.competitive_config.use_proxy and self.oxylabs_config['username']:
            proxy_url = f"http://{self.oxylabs_config['username']}:{self.oxylabs_config['password']}@{self.oxylabs_config['endpoint']}:{self.oxylabs_config['port']}"
            loader.context._session.proxies.update({
                'http': proxy_url,
                'https': proxy_url
            })
            self.logger.info("Configured Instagram loader with proxy")
        
        return loader

    def _login(self) -> None:
        """Login to Instagram or load existing competitive session."""
        try:
            # Try to load existing session
            if self.session_file.exists():
                self.loader.load_session_from_file(self.username, str(self.session_file))
                self.logger.info(f"Loaded existing competitive Instagram session for {self.competitor_key}")
                
                # Verify session is valid
                if not self.loader.context or not self.loader.context.is_logged_in:
                    self.logger.warning("Session invalid, logging in fresh")
                    self.session_file.unlink()  # Remove bad session
                    self.loader.login(self.username, self.password)
                    self.loader.save_session_to_file(str(self.session_file))
            else:
                # Fresh login
                self.logger.info(f"Logging in to Instagram for competitive scraping of {self.competitor_key}")
                self.loader.login(self.username, self.password)
                self.loader.save_session_to_file(str(self.session_file))
                self.logger.info("Competitive Instagram login successful")
                
        except (BadCredentialsException, TwoFactorAuthRequiredException) as e:
            raise InstagramLoginError(self.username, str(e))
        except LoginRequiredException as e:
            self.logger.warning(f"Login required for Instagram competitive scraping: {e}")
            # Continue with limited public access
            if not hasattr(self.loader, 'context') or self.loader.context is None:
                self.loader = instaloader.Instaloader()
        except (OSError, ConnectionError) as e:
            raise InstagramError(f"Network error during Instagram login: {e}")
        except Exception as e:
            self.logger.error(f"Unexpected Instagram competitive login error: {e}")
            # Continue without login for public content
            if not hasattr(self.loader, 'context') or self.loader.context is None:
                self.loader = instaloader.Instaloader()

    def _aggressive_competitive_delay(self, min_seconds: float = 15, max_seconds: float = 30) -> None:
        """Aggressive delay for competitive Instagram scraping."""
        delay = random.uniform(min_seconds, max_seconds)
        self.logger.debug(f"Competitive Instagram delay: {delay:.2f} seconds")
        time.sleep(delay)

    def _check_competitive_rate_limit(self) -> None:
        """Enhanced rate limiting for competitive scraping."""
        current_time = time.time()
        
        # Reset counter every hour
        if current_time - self.last_request_reset >= 3600:
            self.request_count = 0
            self.last_request_reset = current_time
            self.logger.info("Reset competitive Instagram rate limit counter")
        
        self.request_count += 1
        
        # Enforce hourly limit
        if self.request_count >= self.max_requests_per_hour:
            self.logger.warning(f"Competitive rate limit reached ({self.max_requests_per_hour}/hour), pausing for 1 hour")
            time.sleep(3600)
            self.request_count = 0
            self.last_request_reset = time.time()
        
        # Extended breaks for competitive scraping
        elif self.request_count % 5 == 0:  # Every 5 requests
            self.logger.info(f"Taking extended competitive break after {self.request_count} requests")
            self._aggressive_competitive_delay(45, 90)  # 45-90 second break
        else:
            # Regular delay between requests
            self._aggressive_competitive_delay()

    def _get_target_profile(self) -> Optional[Profile]:
        """Get the competitor's Instagram profile."""
        if self.target_profile:
            return self.target_profile
        
        try:
            self.logger.info(f"Loading Instagram profile for competitor: {self.target_username}")
            self._check_competitive_rate_limit()
            
            self.target_profile = Profile.from_username(self.loader.context, self.target_username)
            
            # Cache profile metadata
            self.profile_metadata = {
                'username': self.target_profile.username,
                'full_name': self.target_profile.full_name,
                'biography': self.target_profile.biography,
                'followers': self.target_profile.followers,
                'followees': self.target_profile.followees,
                'posts_count': self.target_profile.mediacount,
                'is_private': self.target_profile.is_private,
                'is_verified': self.target_profile.is_verified,
                'external_url': self.target_profile.external_url,
                'profile_pic_url': self.target_profile.profile_pic_url,
                'userid': self.target_profile.userid
            }
            
            self.logger.info(f"Loaded profile: {self.target_profile.full_name}")
            self.logger.info(f"Followers: {self.target_profile.followers:,}")
            self.logger.info(f"Posts: {self.target_profile.mediacount:,}")
            
            if self.target_profile.is_private:
                self.logger.warning(f"Profile {self.target_username} is private - limited access")
            
            return self.target_profile
            
        except ProfileNotExistsException:
            raise InstagramProfileNotFoundError(self.target_username)
        except PrivateProfileNotFollowedException:
            raise InstagramPrivateAccountError(self.target_username)
        except LoginRequiredException as e:
            self.logger.warning(f"Login required to access profile {self.target_username}: {e}")
            raise InstagramLoginError(self.username, "Login required for profile access")
        except (ConnectionError, TimeoutError) as e:
            raise InstagramError(f"Network error loading profile {self.target_username}: {e}")
        except Exception as e:
            self.logger.error(f"Unexpected error loading Instagram profile {self.target_username}: {e}")
            return None

    def discover_content_urls(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
        """Discover post URLs from competitor's Instagram account."""
        profile = self._get_target_profile()
        if not profile:
            self.logger.error("Cannot discover content without valid profile")
            return []
        
        posts = []
        posts_fetched = 0
        limit = limit or 20  # Conservative limit for competitive scraping
        
        try:
            self.logger.info(f"Discovering Instagram posts from {profile.username} (limit: {limit})")
            
            for post in profile.get_posts():
                if posts_fetched >= limit:
                    break
                
                try:
                    # Rate limiting for each post
                    self._check_competitive_rate_limit()
                    
                    post_data = {
                        'url': f"https://www.instagram.com/p/{post.shortcode}/",
                        'shortcode': post.shortcode,
                        'post_id': str(post.mediaid),
                        'date_utc': post.date_utc.isoformat(),
                        'typename': post.typename,
                        'is_video': post.is_video,
                        'caption': post.caption if post.caption else "",
                        'likes': post.likes,
                        'comments': post.comments,
                        'location': post.location.name if post.location else None,
                        'tagged_users': [user.username for user in post.tagged_users] if post.tagged_users else [],
                        'owner_username': post.owner_username,
                        'owner_id': post.owner_id
                    }
                    
                    posts.append(post_data)
                    posts_fetched += 1
                    
                    if posts_fetched % 5 == 0:
                        self.logger.info(f"Discovered {posts_fetched}/{limit} posts")
                        
                except (AttributeError, ValueError) as e:
                    self.logger.warning(f"Data processing error for post {post.shortcode}: {e}")
                    continue
                except Exception as e:
                    self.logger.warning(f"Unexpected error processing post {post.shortcode}: {e}")
                    continue
                
        except InstagramPrivateAccountError:
            # Re-raise private account errors
            raise
        except (ConnectionError, TimeoutError) as e:
            raise InstagramError(f"Network error discovering posts: {e}")
        except Exception as e:
            self.logger.error(f"Unexpected error discovering Instagram posts: {e}")
        
        self.logger.info(f"Discovered {len(posts)} posts from {self.competitor_info['name']}")
        return posts

    def scrape_content_item(self, url: str) -> Optional[Dict[str, Any]]:
        """Scrape individual Instagram post content."""
        try:
            # Extract shortcode from URL
            shortcode = None
            if '/p/' in url:
                shortcode = url.split('/p/')[1].split('/')[0]
            
            if not shortcode:
                raise DataValidationError(
                    "Invalid Instagram URL format",
                    field="url",
                    value=url
                )
            
            self.logger.debug(f"Scraping Instagram post: {shortcode}")
            self._check_competitive_rate_limit()
            
            # Get post by shortcode
            post = Post.from_shortcode(self.loader.context, shortcode)
            
            # Format publication date
            pub_date = post.date_utc
            formatted_date = pub_date.strftime('%Y-%m-%d %H:%M:%S UTC')
            
            # Get hashtags from caption
            hashtags = []
            caption_text = post.caption or ""
            if caption_text:
                hashtags = [tag.strip('#') for tag in caption_text.split() if tag.startswith('#')]
            
            # Calculate engagement rate
            engagement_rate = 0
            if self.profile_metadata.get('followers', 0) > 0:
                engagement_rate = ((post.likes + post.comments) / self.profile_metadata['followers']) * 100
            
            scraped_item = {
                'id': post.shortcode,
                'url': url,
                'title': f"Instagram Post - {formatted_date}",
                'description': caption_text[:500] + '...' if len(caption_text) > 500 else caption_text,
                'author': post.owner_username,
                'publish_date': formatted_date,
                'type': f"instagram_{post.typename.lower()}",
                'is_video': post.is_video,
                'competitor': self.competitor_key,
                'location': post.location.name if post.location else None,
                'hashtags': hashtags,
                'tagged_users': [user.username for user in post.tagged_users] if post.tagged_users else [],
                'media_count': len(post.get_sidecar_nodes()) if post.typename == 'GraphSidecar' else 1,
                'capture_timestamp': datetime.now(self.tz).isoformat(),
                'extraction_method': 'instaloader',
                'social_metrics': {
                    'likes': post.likes,
                    'comments': post.comments,
                    'engagement_rate': round(engagement_rate, 2)
                },
                'word_count': len(caption_text.split()) if caption_text else 0,
                'categories': hashtags[:5],  # Use first 5 hashtags as categories
                'content': f"**Instagram Caption:**\n\n{caption_text}\n\n**Hashtags:** {', '.join(hashtags)}\n\n**Location:** {post.location.name if post.location else 'None'}\n\n**Tagged Users:** {', '.join([user.username for user in post.tagged_users]) if post.tagged_users else 'None'}"
            }
            
            return scraped_item
            
        except DataValidationError:
            # Re-raise validation errors
            raise
        except (AttributeError, ValueError, KeyError) as e:
            self.logger.error(f"Data processing error scraping Instagram post {url}: {e}")
            return None
        except (ConnectionError, TimeoutError) as e:
            raise InstagramError(f"Network error scraping post {url}: {e}")
        except Exception as e:
            self.logger.error(f"Unexpected error scraping Instagram post {url}: {e}")
            return None

    def get_competitor_metadata(self) -> Dict[str, Any]:
        """Get metadata about the competitor Instagram account."""
        profile = self._get_target_profile()
        
        return {
            'competitor_key': self.competitor_key,
            'competitor_name': self.competitor_info['name'],
            'instagram_username': self.target_username,
            'instagram_url': self.competitor_info['url'],
            'profile_metadata': self.profile_metadata,
            'requests_made': self.request_count,
            'is_private_account': self.profile_metadata.get('is_private', False),
            'last_updated': datetime.now(self.tz).isoformat()
        }

    def run_competitor_analysis(self) -> Dict[str, Any]:
        """Run Instagram-specific competitor analysis."""
        self.logger.info(f"Running Instagram competitor analysis for {self.competitor_info['name']}")
        
        try:
            profile = self._get_target_profile()
            if not profile:
                return {'error': 'Could not load competitor profile'}
            
            # Get recent posts for analysis
            recent_posts = self.discover_content_urls(15)  # Smaller sample for Instagram
            
            analysis = {
                'competitor': self.competitor_key,
                'competitor_name': self.competitor_info['name'],
                'profile_metadata': self.profile_metadata,
                'total_recent_posts': len(recent_posts),
                'posting_analysis': self._analyze_posting_patterns(recent_posts),
                'content_analysis': self._analyze_instagram_content(recent_posts),
                'engagement_analysis': self._analyze_engagement_patterns(recent_posts),
                'analysis_timestamp': datetime.now(self.tz).isoformat()
            }
            
            return analysis
            
        except Exception as e:
            self.logger.error(f"Error in Instagram competitor analysis: {e}")
            return {'error': str(e)}

    def _analyze_posting_patterns(self, posts: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Analyze Instagram posting frequency and timing patterns."""
        try:
            if not posts:
                return {}
            
            # Parse post dates
            post_dates = []
            for post in posts:
                try:
                    post_date = datetime.fromisoformat(post['date_utc'].replace('Z', '+00:00'))
                    post_dates.append(post_date)
                except:
                    continue
            
            if not post_dates:
                return {}
            
            # Calculate posting frequency
            post_dates.sort()
            date_range = (post_dates[-1] - post_dates[0]).days if len(post_dates) > 1 else 0
            frequency = len(post_dates) / max(date_range, 1) if date_range > 0 else 0
            
            # Analyze posting times
            hours = [d.hour for d in post_dates]
            weekdays = [d.weekday() for d in post_dates]
            
            # Content type distribution
            video_count = sum(1 for p in posts if p.get('is_video', False))
            photo_count = len(posts) - video_count
            
            return {
                'total_posts_analyzed': len(post_dates),
                'date_range_days': date_range,
                'average_posts_per_day': round(frequency, 2),
                'most_common_hour': max(set(hours), key=hours.count) if hours else None,
                'most_common_weekday': max(set(weekdays), key=weekdays.count) if weekdays else None,
                'video_posts': video_count,
                'photo_posts': photo_count,
                'video_percentage': round((video_count / len(posts)) * 100, 1) if posts else 0,
                'latest_post_date': post_dates[-1].isoformat() if post_dates else None
            }
            
        except Exception as e:
            self.logger.error(f"Error analyzing Instagram posting patterns: {e}")
            return {}

    def _analyze_instagram_content(self, posts: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Analyze Instagram content themes and hashtags."""
        try:
            if not posts:
                return {}
            
            # Collect hashtags
            all_hashtags = []
            captions_with_hashtags = 0
            total_caption_length = 0
            
            for post in posts:
                caption = post.get('description', '')
                hashtags = post.get('hashtags', [])
                
                if hashtags:
                    all_hashtags.extend(hashtags)
                    captions_with_hashtags += 1
                
                total_caption_length += len(caption)
            
            # Find most common hashtags
            hashtag_freq = {}
            for tag in all_hashtags:
                hashtag_freq[tag.lower()] = hashtag_freq.get(tag.lower(), 0) + 1
            
            top_hashtags = sorted(hashtag_freq.items(), key=lambda x: x[1], reverse=True)[:10]
            
            # Analyze locations
            locations = [p.get('location') for p in posts if p.get('location')]
            location_freq = {}
            for loc in locations:
                location_freq[loc] = location_freq.get(loc, 0) + 1
            
            return {
                'total_posts_analyzed': len(posts),
                'posts_with_hashtags': captions_with_hashtags,
                'total_unique_hashtags': len(hashtag_freq),
                'average_hashtags_per_post': len(all_hashtags) / len(posts) if posts else 0,
                'top_hashtags': [{'hashtag': h, 'frequency': f} for h, f in top_hashtags],
                'average_caption_length': total_caption_length / len(posts) if posts else 0,
                'posts_with_location': len(locations),
                'top_locations': list(location_freq.keys())[:5]
            }
            
        except Exception as e:
            self.logger.error(f"Error analyzing Instagram content: {e}")
            return {}

    def _analyze_engagement_patterns(self, posts: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Analyze engagement patterns (likes, comments)."""
        try:
            if not posts:
                return {}
            
            # Extract engagement metrics
            likes = []
            comments = []
            engagement_rates = []
            
            for post in posts:
                social_metrics = post.get('social_metrics', {})
                post_likes = social_metrics.get('likes', 0)
                post_comments = social_metrics.get('comments', 0)
                engagement_rate = social_metrics.get('engagement_rate', 0)
                
                likes.append(post_likes)
                comments.append(post_comments)
                engagement_rates.append(engagement_rate)
            
            if not likes:
                return {}
            
            # Calculate averages and ranges
            avg_likes = sum(likes) / len(likes)
            avg_comments = sum(comments) / len(comments)
            avg_engagement = sum(engagement_rates) / len(engagement_rates)
            
            return {
                'total_posts_analyzed': len(posts),
                'average_likes': round(avg_likes, 1),
                'average_comments': round(avg_comments, 1),
                'average_engagement_rate': round(avg_engagement, 2),
                'max_likes': max(likes),
                'min_likes': min(likes),
                'max_comments': max(comments),
                'min_comments': min(comments),
                'total_likes': sum(likes),
                'total_comments': sum(comments)
            }
    
    def _validate_post_data(self, post_data: Dict[str, Any]) -> bool:
        """Validate Instagram post data structure."""
        required_fields = ['shortcode', 'date_utc', 'owner_username']
        return all(field in post_data for field in required_fields)
    
    def _sanitize_caption(self, caption: str) -> str:
        """Sanitize Instagram caption text."""
        if not isinstance(caption, str):
            return ""
        
        # Remove excessive whitespace while preserving line breaks
        lines = [line.strip() for line in caption.split('\n')]
        sanitized = '\n'.join(line for line in lines if line)
        
        # Limit length
        if len(sanitized) > 2200:  # Instagram's caption limit
            sanitized = sanitized[:2200] + "..."
        
        return sanitized
    
    def cleanup_resources(self) -> None:
        """Cleanup Instagram scraper resources."""
        try:
            # Logout from Instagram session
            if hasattr(self.loader, 'context') and self.loader.context:
                try:
                    self.loader.context.close()
                except Exception as e:
                    self.logger.debug(f"Error closing Instagram context: {e}")
            
            # Clear profile metadata cache
            self.profile_metadata.clear()
            
            self.logger.info(f"Cleaned up Instagram scraper resources for {self.competitor_key}")
            
        except Exception as e:
            self.logger.warning(f"Error during Instagram resource cleanup: {e}")
    
    def __enter__(self):
        """Context manager entry."""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit with resource cleanup."""
        self.cleanup_resources()
    
    def _exponential_backoff_delay(self, attempt: int, base_delay: float = 1.0, max_delay: float = 300.0) -> float:
        """Calculate exponential backoff delay for rate limiting."""
        delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
        return min(delay, max_delay)
    
    def _handle_rate_limit_with_backoff(self, attempt: int = 0, max_attempts: int = 3) -> None:
        """Handle rate limiting with exponential backoff."""
        if attempt >= max_attempts:
            raise RateLimitError("Maximum retry attempts exceeded for Instagram rate limiting")
        
        delay = self._exponential_backoff_delay(attempt)
        self.logger.warning(f"Rate limit hit, backing off for {delay:.2f} seconds (attempt {attempt + 1}/{max_attempts})")
        time.sleep(delay)
            
        except Exception as e:
            self.logger.error(f"Error analyzing engagement patterns: {e}")
            return {}


def create_instagram_competitive_scrapers(data_dir: Path, logs_dir: Path) -> Dict[str, InstagramCompetitiveScraper]:
    """Factory function to create all Instagram competitive scrapers."""
    scrapers = {}
    
    for competitor_key in InstagramCompetitiveScraper.COMPETITOR_ACCOUNTS:
        try:
            scrapers[f"instagram_{competitor_key}"] = InstagramCompetitiveScraper(
                data_dir, logs_dir, competitor_key
            )
        except Exception as e:
            # Log error but continue with other scrapers
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Failed to create Instagram scraper for {competitor_key}: {e}")
    
    return scrapers