"""Flow SDK exception hierarchy.

Comprehensive error handling with actionable recovery guidance. All Flow
exceptions inherit from FlowError and provide structured error information
including suggestions and error codes for programmatic handling.

Exception Hierarchy:
    FlowError (base)
    ├── AuthenticationError - API key and auth issues
    ├── ResourceNotFoundError - Missing resources
    │   └── TaskNotFoundError - Missing tasks
    ├── ValidationError - Configuration/parameter errors
    ├── APIError - API communication failures
    │   └── ValidationAPIError - 422 validation errors
    ├── NetworkError - Connection issues
    ├── ProviderError - Provider-specific failures
    └── (others...)

Usage Guidelines:
    - Always include suggestions for user recovery
    - Preserve original error context with cause
    - Use specific subclasses over generic FlowError
    - Include error codes for common failures
"""


class FlowError(Exception):
    """Base exception for all Flow SDK errors.

    Provides structured error information with consistent formatting,
    actionable recovery suggestions, and machine-readable error codes.
    All Flow exceptions should inherit from this base class.

    Args:
        message: Primary error description. Should be clear and specific
            about what went wrong. Avoid technical jargon where possible.
        suggestions: Optional list of actionable steps to resolve the error.
            Each suggestion should be a complete sentence starting with a verb.
        error_code: Optional machine-readable code for programmatic handling.
            Format: CATEGORY_NUMBER (e.g., AUTH_001, NETWORK_003).

    Attributes:
        message: The error message.
        suggestions: List of recovery suggestions.
        error_code: Unique error identifier.

    Error Code Categories:
        - AUTH_*: Authentication and authorization
        - RESOURCE_*: Resource not found or unavailable
        - VALIDATION_*: Input validation failures
        - NETWORK_*: Network and connectivity issues
        - PROVIDER_*: Provider-specific errors
        - TASK_*: Task execution failures

    Example:
        >>> raise FlowError(
        ...     "Failed to connect to GPU cluster",
        ...     suggestions=[
        ...         "Check your internet connection",
        ...         "Verify the API endpoint is correct",
        ...         "Ensure your API key has not expired",
        ...         f"Check {self._get_status_url()} for outages" if self._get_status_url() else "Check the provider status page for outages"
        ...     ],
        ...     error_code="NETWORK_001"
        ... )
    """

    def __init__(self, message: str, suggestions: list = None, error_code: str = None):
        self.message = message
        self.suggestions = suggestions or []
        self.error_code = error_code
        super().__init__(self._format_message())

    def _format_message(self) -> str:
        """Format error with suggestions."""
        msg = self.message

        if self.suggestions:
            msg += "\n\nSuggestions:"
            for i, suggestion in enumerate(self.suggestions, 1):
                msg += f"\n  {i}. {suggestion}"

        if self.error_code:
            msg += f"\n\nError code: {self.error_code}"

        return msg


class AuthenticationError(FlowError):
    """Authentication or authorization failure.

    Raised when API authentication fails or user lacks required permissions.
    This is typically the first error users encounter when setting up Flow.

    Common Causes:
        - Missing API key configuration
        - Invalid or expired API key
        - Insufficient permissions for requested operation
        - Wrong API endpoint for key type
        - Rate limit exceeded

    Error Codes:
        - AUTH_001: No API key provided
        - AUTH_002: Invalid API key format
        - AUTH_003: API key expired or revoked
        - AUTH_004: Insufficient permissions
        - AUTH_005: Rate limit exceeded

    Examples:
        >>> # Missing API key
        >>> raise AuthenticationError(
        ...     "No API key configured",
        ...     suggestions=[
        ...         "Run 'flow init' to configure your API key interactively",
        ...         "Set FLOW_API_KEY environment variable",
        ...         "Create ~/.flow/config.yaml with your API key",
        ...         "Pass api_key to Flow(config=Config(api_key='...'))"
        ...     ],
        ...     error_code="AUTH_001"
        ... )

        >>> # Permission denied
        >>> raise AuthenticationError(
        ...     "API key lacks permission to create instances in project 'prod'",
        ...     suggestions=[
        ...         "Request access to the 'prod' project from your admin",
        ...         "Use a different project with 'flow init --project'",
        ...         "Check your permissions in the provider dashboard"
        ...     ],
        ...     error_code="AUTH_004"
        ... )
    """

    pass


class ResourceNotFoundError(FlowError):
    """Raised when a requested resource cannot be found.

    This is the base class for all resource-not-found errors. Specific
    subclasses provide more context for different resource types.

    Common causes:
    - Resource ID is incorrect
    - Resource has been deleted
    - User lacks permission to access the resource
    - Resource exists in a different project or region

    Example:
        >>> raise ResourceNotFoundError(
        ...     "Project 'my-project' not found",
        ...     suggestions=[
        ...         "Check the project name for typos",
        ...         "Ensure you have access to this project",
        ...         "List available projects with 'flow list projects'"
        ...     ]
        ... )
    """

    pass


class TaskNotFoundError(ResourceNotFoundError):
    """Raised when a task cannot be found.

    This typically occurs when:
    - Task ID is incorrect or malformed
    - Task has already completed and been cleaned up
    - Task exists in a different project
    - User lacks permission to view the task

    Example:
        >>> raise TaskNotFoundError(
        ...     "Task 'task-abc123' not found",
        ...     suggestions=[
        ...         "Verify the task ID is correct",
        ...         "Check if the task has already completed",
        ...         "Use 'flow list' to see active tasks",
        ...         "Ensure you're using the correct project"
        ...     ]
        ... )
    """

    pass


class ValidationError(FlowError, ValueError):
    """Raised when configuration or parameters are invalid.

    Inherits from both FlowError and ValueError for compatibility with
    standard Python error handling patterns.

    Common validation errors:
    - Invalid resource specifications (CPU, memory, GPU)
    - Malformed configuration files
    - Type mismatches
    - Out-of-range values
    - Missing required fields

    Args:
        message: Description of what validation failed.
        suggestions: Optional list of how to fix the validation error.
        error_code: Optional error code for specific validation failures.

    Example:
        >>> raise ValidationError(
        ...     "Invalid memory specification: '10GB'",
        ...     suggestions=[
        ...         "Memory must be specified as an integer (in MB)",
        ...         "Example: memory=10240 for 10GB",
        ...         "Valid range: 512-524288 (512MB to 512GB)"
        ...     ]
        ... )
    """

    def __init__(self, message: str, suggestions: list = None, error_code: str = None):
        # Call FlowError's __init__ to handle formatting
        FlowError.__init__(self, message, suggestions, error_code)
        # Also initialize ValueError for compatibility
        ValueError.__init__(self, self._format_message())


class APIError(FlowError):
    """Raised when an API request fails.

    Base class for all API-related errors. Provides access to the HTTP
    status code and response body for debugging.

    Args:
        message: Human-readable error description.
        status_code: HTTP status code from the failed request.
        response_body: Raw response body for debugging.

    Attributes:
        status_code: The HTTP status code (e.g., 400, 500).
        response_body: The raw response body text.

    Example:
        >>> raise APIError(
        ...     "Server error while creating task",
        ...     status_code=500,
        ...     response_body='{"error": "Internal server error"}'
        ... )
    """

    def __init__(self, message: str, status_code: int = None, response_body: str = None):
        super().__init__(message)
        self.status_code = status_code
        self.response_body = response_body


class ValidationAPIError(APIError):
    """API request validation failure (HTTP 422).

    Specialized error for handling FastAPI/Pydantic validation responses.
    Parses structured validation errors and provides field-specific guidance
    for common configuration mistakes.

    Response Format Expected:
        {
            "detail": [
                {
                    "loc": ["body", "instance_type"],
                    "msg": "Invalid instance type",
                    "type": "value_error"
                }
            ]
        }

    Common Field Errors:
        - instance_type: Unknown GPU type or format
        - region: Invalid or unavailable region
        - max_price_per_hour: Price below minimum bid
        - volumes: Invalid mount paths or size
        - ssh_keys: Unknown key IDs
    """

    def __init__(self, response):
        import json

        self.validation_errors = []
        self.response = response

        try:
            error_data = response.json()
            if "detail" in error_data and isinstance(error_data["detail"], list):
                self.validation_errors = error_data["detail"]
        except (json.JSONDecodeError, KeyError):
            # Fallback to raw response if parsing fails
            pass

        message = self._format_message()
        super().__init__(message, status_code=422, response_body=response.text)

    def _format_message(self) -> str:
        """Format validation errors into a readable message."""
        if not self.validation_errors:
            return "Validation failed. The request contained invalid data."

        # Group errors by field
        field_errors = {}
        for error in self.validation_errors:
            # Extract field path (e.g., ["body", "region"] -> "region")
            field_path = error.get("loc", [])
            field = field_path[-1] if field_path else "unknown"

            # Skip 'body' prefix for cleaner messages
            if len(field_path) > 1 and field_path[0] == "body":
                field = ".".join(str(part) for part in field_path[1:])

            error_type = error.get("type", "validation_error")
            error_msg = error.get("msg", "Invalid value")

            if field not in field_errors:
                field_errors[field] = []

            # Format specific error types
            if error_type == "missing":
                field_errors[field].append("Field is required")
            elif error_type == "value_error":
                field_errors[field].append(error_msg)
            else:
                field_errors[field].append(f"{error_msg}")

        # Build the message
        lines = ["Validation failed:"]
        for field, errors in field_errors.items():
            for error in errors:
                lines.append(f"  - {field}: {error}")

        # Add helpful context for common fields from centralized constants
        from flow.providers.fcp.core.constants import format_validation_help

        for field in ["region", "disk_interface", "instance_type"]:
            if field in field_errors:
                help_lines = format_validation_help(field)
                if help_lines:
                    lines.append("")
                    lines.extend(help_lines)

        return "\n".join(lines)


class NetworkError(FlowError):
    """Raised when network communication fails.

    This error indicates problems with network connectivity, DNS resolution,
    or connection establishment. It's distinct from API errors which occur
    after a connection is established.

    Common causes:
    - No internet connection
    - DNS resolution failure
    - Firewall blocking connections
    - Proxy configuration issues
    - SSL/TLS certificate problems

    Example:
        >>> raise NetworkError(
        ...     "Failed to connect to FCP API",
        ...     suggestions=[
        ...         "Check your internet connection",
        ...         "Verify firewall settings allow HTTPS traffic",
        ...         "Check proxy configuration if behind a corporate firewall",
        ...         "Try again in a few moments"
        ...     ]
        ... )
    """

    pass


class InvalidResponseError(FlowError):
    """Raised when API returns an invalid or unexpected response.

    This error occurs when:
    - Response is not valid JSON when JSON is expected
    - Response structure doesn't match expected schema
    - Required fields are missing from response
    - Response contains invalid data types

    Example:
        >>> raise InvalidResponseError(
        ...     "Expected JSON response but received HTML",
        ...     suggestions=[
        ...         "This might indicate a temporary API issue",
        ...         "Check the FCP status page for ongoing incidents",
        ...         "Contact support if the problem persists"
        ...     ]
        ... )
    """

    pass


class TimeoutError(FlowError):
    """Raised when a request exceeds the timeout limit.

    Timeouts can occur at different stages:
    - Connection timeout: Failed to establish connection
    - Read timeout: Connected but no response received
    - Task timeout: Task execution exceeded time limit

    Example:
        >>> raise TimeoutError(
        ...     "Request timed out after 30 seconds",
        ...     suggestions=[
        ...         "Check if the service is experiencing high load",
        ...         "Increase timeout with timeout parameter",
        ...         "Retry the operation",
        ...         "Break large operations into smaller chunks"
        ...     ]
        ... )
    """

    pass


class ProviderError(FlowError):
    """Raised when provider configuration or initialization fails.

    This error indicates problems with:
    - Provider configuration validation
    - Provider initialization
    - Provider-specific requirements not met
    - Incompatible provider versions

    Example:
        >>> raise ProviderError(
        ...     "FCP provider requires 'project' to be configured",
        ...     suggestions=[
        ...         "Set project in flow.yaml configuration",
        ...         "Pass project parameter to Flow constructor",
        ...         "Set FCP_DEFAULT_PROJECT environment variable"
        ...     ]
        ... )
    """

    pass


class ConfigParserError(FlowError):
    """Raised when configuration file parsing fails.

    This error occurs when:
    - YAML/JSON syntax is invalid
    - Configuration schema validation fails
    - Circular references in configuration
    - Unsupported configuration version

    Example:
        >>> raise ConfigParserError(
        ...     "Invalid YAML in flow.yaml at line 15",
        ...     suggestions=[
        ...         "Check YAML indentation (use spaces, not tabs)",
        ...         "Validate YAML syntax at yamllint.com",
        ...         "Ensure all strings with special characters are quoted",
        ...         "Remove any duplicate keys"
        ...     ]
        ... )
    """

    pass


class ResourceNotAvailableError(FlowError):
    """Requested resource is temporarily unavailable.

    Distinct from ResourceNotFoundError - the resource type exists
    but no capacity is currently available.

    Common Scenarios:
        - No GPU instances available in region
        - Spot instance capacity exhausted
        - Volume attachment limit reached
        - Quota exceeded

    Example:
        >>> raise ResourceNotAvailableError(
        ...     "No H100 instances available in us-east-1",
        ...     suggestions=[
        ...         "Try a different region with 'flow run --region us-west-2'",
        ...         "Use a different GPU type like A100",
        ...         "Enable cross-region search with '--any-region'",
        ...         "Check availability in the provider capacity dashboard"
        ...     ],
        ...     error_code="RESOURCE_002"
        ... )
    """

    pass


class QuotaExceededError(FlowError):
    """Resource quota limit exceeded.

    Raised when user hits project or account-level resource limits.

    Quota Types:
        - GPU hours per month
        - Concurrent instances
        - Storage volume count/size
        - API request rate

    Example:
        >>> raise QuotaExceededError(
        ...     "GPU quota exceeded: 1000/1000 hours used this month",
        ...     suggestions=[
        ...         "Request quota increase from your provider",
        ...         "Terminate idle instances with 'flow list --running | flow cancel'",
        ...         "Upgrade to a higher tier plan",
        ...         "Wait until next billing cycle (7 days remaining)"
        ...     ],
        ...     error_code="QUOTA_001"
        ... )
    """

    pass


class VolumeError(FlowError):
    """Storage volume operation failed.

    Base class for volume-related errors including creation,
    attachment, and data transfer failures.

    Example:
        >>> raise VolumeError(
        ...     "Cannot attach volume 'vol-123' - already in use",
        ...     suggestions=[
        ...         "Detach volume from current instance first",
        ...         "Create a new volume with 'flow volume create'",
        ...         "Use read-only mount for shared access"
        ...     ],
        ...     error_code="VOLUME_003"
        ... )
    """

    pass


class TaskExecutionError(FlowError):
    """Task failed during execution.

    Raised when a task fails after successful submission.
    Includes exit code and last log lines for debugging.

    Attributes:
        exit_code: Process exit code if available
        last_logs: Final log output lines

    Example:
        >>> raise TaskExecutionError(
        ...     "Task failed with exit code 137 (Out of Memory)",
        ...     suggestions=[
        ...         "Increase instance memory with --instance-type",
        ...         "Reduce batch size or model size",
        ...         "Enable gradient checkpointing",
        ...         "Check logs with 'flow logs task-123'"
        ...     ],
        ...     error_code="TASK_002"
        ... )
    """

    def __init__(self, message: str, exit_code: int = None, last_logs: str = None, **kwargs):
        super().__init__(message, **kwargs)
        self.exit_code = exit_code
        self.last_logs = last_logs


# Standard Python exceptions used by Flow:
# - ValueError: Invalid parameter values
# - TypeError: Wrong parameter types
# - KeyError: Missing configuration keys
# - FileNotFoundError: Missing files
# - PermissionError: Insufficient file permissions
# - TimeoutError: Operation timeouts (built-in)
# - ConnectionError: Network issues (built-in)


class FlowOperationError(FlowError):
    """Structured error for failed operations with full context.

    Wraps operation failures with additional context about what was
    being attempted, which resource was involved, and the underlying
    cause. Useful for debugging complex multi-step operations.

    Args:
        operation: Description of attempted operation (e.g., \"volume attachment\")
        resource_id: Identifier of resource being operated on
        cause: Original exception that caused the failure
        suggestions: Optional recovery suggestions

    Attributes:
        operation: The failed operation name
        resource_id: The resource identifier
        cause: Original exception

    Example:
        >>> try:
        ...     attach_volume(vol_id, instance_id)
        ... except VolumeInUseError as e:
        ...     raise FlowOperationError(
        ...         operation=\"volume attachment\",
        ...         resource_id=vol_id,
        ...         cause=e,
        ...         suggestions=[
        ...             \"Check if volume is already attached\",
        ...             \"Ensure volume and instance are in same region\"
        ...         ]
        ...     ) from e
    """

    def __init__(
        self,
        operation: str,
        resource_id: str,
        cause: Exception,
        suggestions: list = None,
        error_code: str = None,
    ):
        self.operation = operation
        self.resource_id = resource_id
        self.cause = cause

        message = f"{operation} failed for {resource_id}"
        if cause:
            message += f": {str(cause)}"

        # Inherit suggestions from cause if not provided
        if suggestions is None and hasattr(cause, "suggestions"):
            suggestions = cause.suggestions

        super().__init__(message, suggestions=suggestions, error_code=error_code)
