#!/usr/bin/env python3
"""
Custom exception classes for the HKIA Competitive Intelligence system.
Provides specific exception types for better error handling and debugging.
"""

from typing import Optional, Dict, Any


class CompetitiveIntelligenceError(Exception):
    """Base exception for all competitive intelligence operations."""
    
    def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message)
        self.message = message
        self.details = details or {}
    
    def __str__(self) -> str:
        if self.details:
            return f"{self.message} (Details: {self.details})"
        return self.message


class ScrapingError(CompetitiveIntelligenceError):
    """Base exception for scraping-related errors."""
    pass


class ConfigurationError(CompetitiveIntelligenceError):
    """Raised when there are configuration issues."""
    pass


class AuthenticationError(CompetitiveIntelligenceError):
    """Raised when authentication fails."""
    pass


class QuotaExceededError(CompetitiveIntelligenceError):
    """Raised when API quota is exceeded."""
    
    def __init__(self, message: str, quota_used: int, quota_limit: int, reset_time: Optional[str] = None):
        super().__init__(message, {
            'quota_used': quota_used,
            'quota_limit': quota_limit,
            'reset_time': reset_time
        })
        self.quota_used = quota_used
        self.quota_limit = quota_limit
        self.reset_time = reset_time


class RateLimitError(CompetitiveIntelligenceError):
    """Raised when rate limiting is triggered."""
    
    def __init__(self, message: str, retry_after: Optional[int] = None):
        super().__init__(message, {'retry_after': retry_after})
        self.retry_after = retry_after


class ContentNotFoundError(ScrapingError):
    """Raised when expected content is not found."""
    
    def __init__(self, message: str, url: Optional[str] = None, content_type: Optional[str] = None):
        super().__init__(message, {
            'url': url,
            'content_type': content_type
        })
        self.url = url
        self.content_type = content_type


class NetworkError(ScrapingError):
    """Raised when network operations fail."""
    
    def __init__(self, message: str, status_code: Optional[int] = None, response_text: Optional[str] = None):
        super().__init__(message, {
            'status_code': status_code,
            'response_text': response_text[:500] if response_text else None
        })
        self.status_code = status_code
        self.response_text = response_text


class ProxyError(NetworkError):
    """Raised when proxy operations fail."""
    
    def __init__(self, message: str, proxy_url: Optional[str] = None):
        super().__init__(message, {'proxy_url': proxy_url})
        self.proxy_url = proxy_url


class DataValidationError(CompetitiveIntelligenceError):
    """Raised when scraped data fails validation."""
    
    def __init__(self, message: str, field: Optional[str] = None, value: Any = None):
        super().__init__(message, {
            'field': field,
            'value': str(value)[:200] if value is not None else None
        })
        self.field = field
        self.value = value


class StateManagementError(CompetitiveIntelligenceError):
    """Raised when state operations fail."""
    
    def __init__(self, message: str, state_file: Optional[str] = None):
        super().__init__(message, {'state_file': state_file})
        self.state_file = state_file


# YouTube-specific exceptions
class YouTubeAPIError(ScrapingError):
    """Raised when YouTube API operations fail."""
    
    def __init__(self, message: str, error_code: Optional[str] = None, quota_cost: Optional[int] = None):
        super().__init__(message, {
            'error_code': error_code,
            'quota_cost': quota_cost
        })
        self.error_code = error_code
        self.quota_cost = quota_cost


class YouTubeChannelNotFoundError(YouTubeAPIError):
    """Raised when a YouTube channel cannot be found."""
    
    def __init__(self, handle: str):
        super().__init__(f"YouTube channel not found: {handle}", {'handle': handle})
        self.handle = handle


class YouTubeVideoNotFoundError(YouTubeAPIError):
    """Raised when a YouTube video cannot be found."""
    
    def __init__(self, video_id: str):
        super().__init__(f"YouTube video not found: {video_id}", {'video_id': video_id})
        self.video_id = video_id


# Instagram-specific exceptions
class InstagramError(ScrapingError):
    """Base exception for Instagram operations."""
    pass


class InstagramLoginError(AuthenticationError):
    """Raised when Instagram login fails."""
    
    def __init__(self, username: str, reason: Optional[str] = None):
        super().__init__(f"Instagram login failed for {username}", {
            'username': username,
            'reason': reason
        })
        self.username = username
        self.reason = reason


class InstagramProfileNotFoundError(InstagramError):
    """Raised when an Instagram profile cannot be found."""
    
    def __init__(self, username: str):
        super().__init__(f"Instagram profile not found: {username}", {'username': username})
        self.username = username


class InstagramPostNotFoundError(InstagramError):
    """Raised when an Instagram post cannot be found."""
    
    def __init__(self, shortcode: str):
        super().__init__(f"Instagram post not found: {shortcode}", {'shortcode': shortcode})
        self.shortcode = shortcode


class InstagramPrivateAccountError(InstagramError):
    """Raised when trying to access private Instagram account content."""
    
    def __init__(self, username: str):
        super().__init__(f"Cannot access private Instagram account: {username}", {'username': username})
        self.username = username


# HVACRSchool-specific exceptions  
class HVACRSchoolError(ScrapingError):
    """Base exception for HVACR School operations."""
    pass


class SitemapParsingError(HVACRSchoolError):
    """Raised when sitemap parsing fails."""
    
    def __init__(self, sitemap_url: str, reason: Optional[str] = None):
        super().__init__(f"Failed to parse sitemap: {sitemap_url}", {
            'sitemap_url': sitemap_url,
            'reason': reason
        })
        self.sitemap_url = sitemap_url
        self.reason = reason


# Utility functions for exception handling
def handle_network_error(response, operation: str = "network request") -> None:
    """Helper to raise appropriate network errors based on response."""
    if response.status_code == 401:
        raise AuthenticationError(f"Authentication failed during {operation}")
    elif response.status_code == 403:
        raise AuthenticationError(f"Access forbidden during {operation}")
    elif response.status_code == 404:
        raise ContentNotFoundError(f"Content not found during {operation}")
    elif response.status_code == 429:
        retry_after = response.headers.get('Retry-After')
        raise RateLimitError(
            f"Rate limit exceeded during {operation}",
            retry_after=int(retry_after) if retry_after and retry_after.isdigit() else None
        )
    elif response.status_code >= 500:
        raise NetworkError(
            f"Server error during {operation}: {response.status_code}",
            status_code=response.status_code,
            response_text=response.text
        )
    elif not response.ok:
        raise NetworkError(
            f"HTTP error during {operation}: {response.status_code}",
            status_code=response.status_code,
            response_text=response.text
        )


def handle_youtube_api_error(error, operation: str = "YouTube API call") -> None:
    """Helper to raise appropriate YouTube API errors."""
    from googleapiclient.errors import HttpError
    
    if isinstance(error, HttpError):
        error_details = error.error_details[0] if error.error_details else {}
        error_reason = error_details.get('reason', '')
        
        if error.resp.status == 403:
            if 'quotaExceeded' in error_reason:
                raise QuotaExceededError(
                    f"YouTube API quota exceeded during {operation}",
                    quota_used=0,  # Will be filled by quota manager
                    quota_limit=0  # Will be filled by quota manager
                )
            else:
                raise AuthenticationError(f"YouTube API access forbidden during {operation}")
        elif error.resp.status == 404:
            raise ContentNotFoundError(f"YouTube content not found during {operation}")
        else:
            raise YouTubeAPIError(
                f"YouTube API error during {operation}: {error}",
                error_code=error_reason
            )
    else:
        raise YouTubeAPIError(f"Unexpected YouTube error during {operation}: {error}")


def handle_instagram_error(error, operation: str = "Instagram operation") -> None:
    """Helper to raise appropriate Instagram errors."""
    error_str = str(error).lower()
    
    if 'login' in error_str and ('fail' in error_str or 'invalid' in error_str):
        raise InstagramLoginError("unknown", str(error))
    elif 'not found' in error_str or '404' in error_str:
        raise ContentNotFoundError(f"Instagram content not found during {operation}")
    elif 'private' in error_str:
        raise InstagramPrivateAccountError("unknown")
    elif 'rate limit' in error_str or '429' in error_str:
        raise RateLimitError(f"Instagram rate limit exceeded during {operation}")
    else:
        raise InstagramError(f"Instagram error during {operation}: {error}")