# Copyright (c) CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com
#

"""
V4 Connections API Client

This module provides a clean, modern interface for managing connection data using the v4 API
with Okta authentication. This replaces the legacy v2 connections functions with:
- get_connection(connection_id) -> returns ConnectionDetails object
- get_connections(organization_id) -> returns Dict[str, ConnectionDetails]
- get_all_connections() -> returns Dict[str, Dict[str, ConnectionDetails]]
"""

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from rich.console import Console

try:
    from pydantic_core._pydantic_core import ValidationError as PydanticCoreValidationError
except ImportError:
    # Fallback if pydantic_core is not available
    PydanticCoreValidationError = Exception

from src.api.authenticated import get_authenticated, post_authenticated, put_authenticated
from src.exceptions import APIError

console = Console()


# V4 Pydantic Models - Clean, modern API contract matching backend schema
class ConnectionDetails(BaseModel):
    """
    V4 connection details model matching API v4 data contract (2025-10-01 schema).

    Clean v4 interface - no backward compatibility with old field names.
    """

    # Core identification fields (required)
    connection_id: str = Field(..., description="Unique connection identifier")
    cz_organization_id: str = Field(..., description="CloudZero organization UUID")
    connection_name: str | None = Field(None, description="Human-readable connection name")
    normalized_connection_name: str | None = Field(None, description="Lowercased connection_name or connection_id for searching")
    connection_type: str = Field(..., description="Connection type: COST, RESOURCE, METRICS, or DATA")

    # Provider information (required)
    provider: str = Field(..., description="Cloud provider: AWS, GCP, AZURE, SNOWFLAKE, etc.")
    provider_normalized: str = Field(..., description="Normalized provider name")

    # Connection status (required)
    enabled: bool = Field(..., description="Whether connection is enabled for data collection")
    is_healthy: bool = Field(default=True, description="Whether connection health checks pass")
    is_connected: bool | None = Field(None, description="Computed: enabled AND is_healthy")

    # Feature enablement flags (cloud-agnostic)
    billing_enabled: bool = Field(default=False, description="Whether billing/cost data collection is enabled")
    metrics_enabled: bool = Field(default=False, description="Whether metrics collection is enabled")
    resources_enabled: bool = Field(default=False, description="Whether resource tracking is enabled")

    # Connection type flags (cloud-agnostic, required)
    is_cost_connection: bool = Field(..., description="Whether this is a billing/cost connection")
    is_resource_connection: bool = Field(..., description="Whether this is a resource owner connection")

    # AWS-Specific Account Type Flags (legacy support)
    is_master_payer_account: bool = Field(default=False, description="AWS: master payer (billing) account")
    is_resource_owner_account: bool = Field(default=False, description="AWS: resource owner account")
    is_organization_master_account: bool = Field(default=False, description="AWS: Organizations master account")

    # Cloud region information
    cloud_region: str | None = Field(None, description="Primary cloud region (legacy field name)")
    connection_region: str | None = Field(None, description="Primary connection region")
    active_regions: list[str] | None = Field(default=None, description="List of active regions")
    inactive_regions: list[str] | None = Field(default=None, description="List of inactive regions")

    # AWS-Specific Authentication Fields (top-level for backward compatibility)
    role_arn: str | None = Field(None, description="AWS IAM role ARN for cross-account access")
    external_id: str | None = Field(None, description="AWS external ID for role assumption")

    # Auto-linking configuration (top-level)
    auto_link: dict | None = Field(None, description="Auto-linking configuration object")

    # Cost and Usage Report data (top-level for backward compatibility)
    cur_data: dict | None = Field(None, description="Cost and Usage Report S3 configuration")

    # Provider-specific details
    connection_details: dict | None = Field(None, description="Provider-specific configuration (role_arn, external_id, auto_link, cur_data)")

    # Auto-linking configuration
    auto_link_enabled: bool = Field(default=False, description="Whether auto-link is enabled")

    # Cost and billing
    last_30_days_billed_spend: str | None = Field(None, description="Last 30 days spend (decimal as string)")

    # Metadata and tracking
    record_sources: list[str] | None = Field(None, description="Data sources that contributed to this record")
    first_seen: str | None = Field(None, description="First seen timestamp (ISO format)")
    last_seen: str | None = Field(None, description="Last seen timestamp (ISO format)")
    last_updated: str | None = Field(None, description="Last modification timestamp (ISO format)")

    # Internal fields
    ttl: float | None = Field(None, description="DynamoDB TTL (Unix timestamp)")
    ttl_iso8601: str | None = Field(None, description="Human-readable TTL expiration time")

    # Database keys (internal)
    PK: str | None = Field(None, description="Database primary key")
    SK: str | None = Field(None, description="Database sort key")
    version_created: str | None = Field(None, description="Version when record was created")

    @field_validator("active_regions", "inactive_regions", mode="before")
    @classmethod
    def normalize_regions(cls, v):
        """Normalize regions field to always be a list or None."""
        if v is None or v == "":
            return None
        if isinstance(v, str):
            # If it's a string, wrap it in a list
            return [v] if v else None
        if isinstance(v, list):
            return v
        # For any other type, return None
        return None

    @model_validator(mode="after")
    def compute_is_connected(self):
        """Compute is_connected from enabled AND is_healthy if not explicitly set."""
        if self.is_connected is None:
            self.is_connected = self.enabled and self.is_healthy
        return self

    @model_validator(mode="after")
    def compute_normalized_connection_name(self):
        """Compute normalized_connection_name from connection_name or connection_id if not set."""
        if self.normalized_connection_name is None:
            if self.connection_name:
                self.normalized_connection_name = self.connection_name.lower()
            else:
                self.normalized_connection_name = self.connection_id.lower()
        return self

    model_config = ConfigDict(extra="allow")


class ConnectionsListResponse(BaseModel):
    """V4 response model for connections listing (matches backend schema)."""

    connections: list[ConnectionDetails] = Field(..., description="List of connection details")
    total_count: int = Field(..., description="Total number of connections")


def get_connection(connection_id: str, debug: bool = False) -> ConnectionDetails:
    """
    Get a single connection by ID using v4 API.

    Args:
        connection_id: Cloud account ID to retrieve
        debug: Whether to show debug information

    Returns:
        ConnectionDetails: The connection details

    Raises:
        APIError: If the API request fails
        ValueError: If the connection ID is invalid or not found
    """
    if not connection_id:
        raise ValueError("Connection ID is required")

    if debug:
        console.print(f"[cyan]DEBUG: Fetching connection {connection_id} via V4 API[/]")

    try:
        # Make authenticated request to v4 connection endpoint
        response_data = get_authenticated(endpoint_path=f"connection/{connection_id}", debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")

        # Backend returns ConnectionsListResponse with single connection or empty list
        response = ConnectionsListResponse(**response_data)

        if not response.connections:
            raise ValueError(f"Connection {connection_id} not found")

        if debug:
            console.print(f"[green]DEBUG: Successfully retrieved connection {connection_id}[/]")

        return response.connections[0]

    except APIError:
        # Re-raise API errors as-is
        raise
    except ValueError:
        # Re-raise value errors as-is (e.g., not found)
        raise
    except Exception as e:
        # Check if this is a Pydantic validation error
        if "ValidationError" in str(type(e).__name__) or "ValidationError" in str(type(e)):
            if debug:
                console.print(f"[red]DEBUG: Response validation failed: {e}[/]")
            raise APIError(f"Invalid response format from v4 API: {e}") from e
        # Handle other unexpected errors
        error_msg = f"Failed to get connection {connection_id}: {str(e)}"
        if debug:
            console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e


def get_connections(
    name: str | None = None,
    enabled_only: bool = True,
    connection_type: str | None = None,
    refresh: bool = False,
    debug: bool = False,
) -> list[ConnectionDetails]:
    """
    Get all connections with optional name-based search using v4 API.

    This endpoint searches across ALL organizations and supports:
    - Case-insensitive substring search on connection_name OR connection_id
    - Filtering by connection type(s)
    - Returns all matching connections regardless of organization

    Args:
        name: Optional case-insensitive substring to search for in connection names or IDs
        enabled_only: Only return enabled connections (default: True)
        connection_type: Filter by connection type(s). Comma-separated list.
                        Values: COST, RESOURCE, METRICS, DATA (default: None - all types)
        refresh: Force refresh of connection data (default: False)
        debug: Whether to show debug information

    Returns:
        list[ConnectionDetails]: List of matching connections

    Raises:
        APIError: If the API request fails

    Examples:
        # Search by name
        get_connections(name="production")  # Matches "Production AWS", etc.

        # Search by partial ID
        get_connections(name="123456")  # Matches connection ID starting with 123456

        # Get all enabled connections
        get_connections(enabled_only=True)

        # Filter by connection type
        get_connections(connection_type="COST")  # Only COST connections

        # Filter by multiple types
        get_connections(connection_type="COST,RESOURCE")  # COST and RESOURCE connections
    """
    if debug:
        search_info = f"name='{name}'" if name else "all connections"
        type_info = f", type={connection_type}" if connection_type else ""
        console.print(f"[cyan]DEBUG: Searching connections ({search_info}{type_info}, enabled_only={enabled_only}) via V4 API[/]")

    try:
        # Build query parameters
        params = {}
        if name:
            params["name"] = name
        if enabled_only:
            params["enabled_only"] = "true"
        if connection_type:
            params["connection_type"] = connection_type
        if refresh:
            params["refresh"] = "true"

        # Build endpoint with query parameters
        endpoint = "connections"
        if params:
            query_string = "&".join(f"{k}={v}" for k, v in params.items())
            endpoint = f"{endpoint}?{query_string}"

        # Make authenticated request to v4 connections endpoint
        response_data = get_authenticated(endpoint_path=endpoint, debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")

        # Backend returns ConnectionsListResponse
        try:
            response = ConnectionsListResponse(**response_data)
        except Exception as validation_error:
            # If validation fails, try to parse individual connections and skip bad ones
            if debug:
                console.print(f"[yellow]DEBUG: Validation error parsing connections response: {validation_error}[/]")
                console.print("[yellow]DEBUG: Attempting to parse connections individually...[/]")

            connections_data = response_data.get("connections", [])
            valid_connections = []
            for idx, conn_data in enumerate(connections_data):
                try:
                    connection = ConnectionDetails(**conn_data)
                    valid_connections.append(connection)
                except Exception as conn_error:
                    if debug:
                        conn_id = conn_data.get("connection_id", f"index_{idx}")
                        console.print(f"[yellow]DEBUG: Skipping invalid connection {conn_id}: {conn_error}[/]")
                    continue

            if debug:
                console.print(f"[green]DEBUG: Successfully parsed {len(valid_connections)}/{len(connections_data)} connections[/]")

            return valid_connections

        if debug:
            console.print(f"[green]DEBUG: Successfully retrieved {response.total_count} connections[/]")

        return response.connections

    except APIError:
        # Re-raise API errors as-is
        raise
    except Exception as e:
        error_msg = f"Failed to get connections: {str(e)}"
        if debug:
            console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e


def get_connections_for_org(organization_id: str, debug: bool = False) -> list[ConnectionDetails]:
    """
    Get all connections for a specific organization using v4 API

    Args:
        organization_id: CloudZero organization ID
        debug: Whether to show debug information

    Returns:
        List[ConnectionDetails]: List of connection details

    Raises:
        APIError: If the API request fails
        ValueError: If the organization ID is invalid or not found
    """
    if not organization_id:
        raise ValueError("Organization ID is required")

    if debug:
        console.print(f"[cyan]DEBUG: Fetching connections for organization {organization_id} via V4 API[/]")

    try:
        # Make authenticated request to v4 connections endpoint
        response_data = get_authenticated(endpoint_path=f"organizations/{organization_id}/connections", debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")

        # Backend returns ConnectionsListResponse - try to parse with validation
        try:
            response = ConnectionsListResponse(**response_data)
        except Exception as validation_error:
            # If validation fails, try to parse individual connections and skip bad ones
            if debug:
                console.print(f"[yellow]DEBUG: Validation error parsing connections response: {validation_error}[/]")
                console.print("[yellow]DEBUG: Attempting to parse connections individually...[/]")

            connections_data = response_data.get("connections", [])
            valid_connections = []
            for idx, conn_data in enumerate(connections_data):
                try:
                    connection = ConnectionDetails(**conn_data)
                    valid_connections.append(connection)
                except Exception as conn_error:
                    if debug:
                        conn_id = conn_data.get("connection_id", f"index_{idx}")
                        console.print(f"[yellow]DEBUG: Skipping invalid connection {conn_id}: {conn_error}[/]")
                    continue

            if debug:
                console.print(f"[green]DEBUG: Successfully parsed {len(valid_connections)}/{len(connections_data)} connections[/]")

            return valid_connections

        if debug:
            console.print(f"[green]DEBUG: Successfully retrieved {response.total_count} connections for organization {organization_id}[/]")

        return response.connections

    except APIError:
        # Re-raise API errors as-is - let the caller handle specific error types
        raise
    except Exception as e:
        error_msg = f"Failed to get connections for organization {organization_id}: {str(e)}"
        console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e


def get_all_connections(debug: bool = False) -> list[ConnectionDetails]:
    """
    Get all connections across all organizations using v4 API.

    Args:
        debug: Whether to show debug information

    Returns:
        List[ConnectionDetails]: List of all connections

    Raises:
        APIError: If the API request fails
        ValueError: If the response format is invalid
    """
    if debug:
        console.print("[cyan]DEBUG: Fetching all connections via V4 API[/]")

    try:
        # Make authenticated request to v4 connections endpoint
        response_data = get_authenticated(endpoint_path="connections", debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")

        # Backend returns ConnectionsListResponse
        response = ConnectionsListResponse(**response_data)

        if debug:
            console.print(f"[green]DEBUG: Successfully retrieved {response.total_count} connections[/]")

        return response.connections

    except APIError:
        # Re-raise API errors as-is
        raise
    except Exception as e:
        # Check if this is a Pydantic validation error
        if "ValidationError" in str(type(e).__name__) or "ValidationError" in str(type(e)):
            if debug:
                console.print(f"[red]DEBUG: Response validation failed: {e}[/]")
            raise APIError(f"Invalid response format from v4 API: {e}") from e
        # Handle other unexpected errors
        error_msg = f"Failed to get all connections: {str(e)}"
        if debug:
            console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e


def get_connection_for_shell(connection_id: str, debug: bool = False) -> dict:
    """
    Get connection data formatted for shell access compatibility.

    This function provides backward compatibility with the shell.py module
    by returning connection data in the expected dictionary format.

    Args:
        connection_id: Cloud account ID to retrieve
        debug: Whether to show debug information

    Returns:
        Dict: Connection data formatted for shell access with all required fields

    Raises:
        APIError: If the API request fails
        ValueError: If the connection ID is invalid or not found
    """
    connection = get_connection(connection_id, debug=debug)

    # Convert Pydantic model to dict for backward compatibility
    connection_dict = connection.model_dump()

    if debug:
        console.print(f"[cyan]DEBUG: Converted connection {connection_id} to shell-compatible format[/]")

    return connection_dict


def enable_connection(connection_id: str, debug: bool = False) -> dict:
    """
    Enable a connection by setting is_connected to True.

    Args:
        connection_id: Cloud account ID to enable
        debug: Whether to show debug information

    Returns:
        Dict: API response with success status

    Raises:
        APIError: If the API request fails
        ValueError: If the connection ID is invalid or not found
    """
    if not connection_id:
        raise ValueError("Connection ID is required")

    if debug:
        console.print(f"[cyan]DEBUG: Enabling connection {connection_id} via V4 API[/]")

    try:
        # Make authenticated request to v4 connection enable endpoint
        response_data = post_authenticated(endpoint_path=f"connection/{connection_id}/enable", json_data={"force": False}, debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")
            console.print(f"[green]DEBUG: Successfully enabled connection {connection_id}[/]")

        return response_data

    except APIError:
        # Re-raise API errors as-is
        raise
    except ValueError:
        # Re-raise value errors as-is (e.g., not found)
        raise
    except Exception as e:
        error_msg = f"Failed to enable connection {connection_id}: {str(e)}"
        if debug:
            console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e


def disable_connection(connection_id: str, debug: bool = False) -> dict:
    """
    Disable a connection by setting is_connected to False.

    Args:
        connection_id: Cloud account ID to disable
        debug: Whether to show debug information

    Returns:
        Dict: API response with success status

    Raises:
        APIError: If the API request fails
        ValueError: If the connection ID is invalid or not found
    """
    if not connection_id:
        raise ValueError("Connection ID is required")

    if debug:
        console.print(f"[cyan]DEBUG: Disabling connection {connection_id} via V4 API[/]")

    try:
        # Make authenticated request to v4 connection disable endpoint
        response_data = post_authenticated(endpoint_path=f"connection/{connection_id}/disable", json_data={"force": False}, debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")
            console.print(f"[green]DEBUG: Successfully disabled connection {connection_id}[/]")

        return response_data

    except APIError:
        # Re-raise API errors as-is
        raise
    except ValueError:
        # Re-raise value errors as-is (e.g., not found)
        raise
    except Exception as e:
        error_msg = f"Failed to disable connection {connection_id}: {str(e)}"
        if debug:
            console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e


class CurDataUpdate(BaseModel):
    """CUR (Cost and Usage Report) configuration for updates."""

    bucket_name: str = Field(..., description="S3 bucket name for CUR data")
    bucket_path: str = Field(..., description="S3 bucket path/prefix for CUR data")
    report_name: str = Field(..., description="CUR report name")


class ConnectionUpdateRequest(BaseModel):
    """Request model for connection updates."""

    connection_name: str | None = Field(None, description="Update connection display name")
    is_connected: bool | None = Field(None, description="Update connection status")
    is_cost_connection: bool | None = Field(None, description="Update cost/billing connection status")
    metrics_enabled: bool | None = Field(None, description="Update metrics collection")
    resources_enabled: bool | None = Field(None, description="Update resource tracking")
    cur_data: CurDataUpdate | None = Field(None, description="Update CUR configuration (AWS billing data)")


class ConnectionUpdateResponse(BaseModel):
    """Response model for connection updates."""

    connection_id: str = Field(..., description="Updated connection identifier")
    updated_fields: list[str] = Field(..., description="List of fields that were updated")
    success: bool = Field(..., description="Whether the update was successful")


def update_connection(
    connection_id: str,
    payload: dict | None = None,
    connection_name: str | None = None,
    is_connected: bool | None = None,
    is_cost_connection: bool | None = None,
    metrics_enabled: bool | None = None,
    resources_enabled: bool | None = None,
    cur_bucket_name: str | None = None,
    cur_bucket_path: str | None = None,
    cur_report_name: str | None = None,
    debug: bool = False,
) -> ConnectionUpdateResponse:
    """
    Update connection properties using V4 API.

    Supports both dict payload (for legacy compatibility) and individual parameters.

    Args:
        connection_id: Cloud account ID to update
        payload: Dictionary of fields to update (supports legacy field names)
        connection_name: New connection display name
        is_connected: Update connection status
        is_cost_connection: Update cost/billing connection status
        metrics_enabled: Update metrics collection
        resources_enabled: Update resource tracking
        cur_bucket_name: CUR S3 bucket name (requires all 3 CUR params)
        cur_bucket_path: CUR S3 bucket path (requires all 3 CUR params)
        cur_report_name: CUR report name (requires all 3 CUR params)
        debug: Whether to show debug information

    Returns:
        ConnectionUpdateResponse: Update results

    Raises:
        APIError: If the API request fails
        ValueError: If the connection ID is invalid or no updates provided
    """
    if not connection_id:
        raise ValueError("Connection ID is required")

    # If payload is provided, extract values from it
    if payload:
        # Map legacy field names to parameters
        connection_name = connection_name or payload.get("cz_account_name") or payload.get("connection_name")
        is_connected = is_connected if is_connected is not None else payload.get("is_connected")
        is_cost_connection = (
            is_cost_connection if is_cost_connection is not None else payload.get("is_cost_connection") or payload.get("is_master_payer_account")
        )
        metrics_enabled = metrics_enabled if metrics_enabled is not None else payload.get("metrics_enabled")
        resources_enabled = (
            resources_enabled if resources_enabled is not None else payload.get("resources_enabled") or payload.get("resource_collector_enabled")
        )

        # Extract CUR fields from payload if present
        if not cur_bucket_name and "master_payer_billing_bucket_name" in payload:
            cur_bucket_name = payload.get("master_payer_billing_bucket_name")
            cur_bucket_path = payload.get("master_payer_billing_bucket_path")
            cur_report_name = payload.get("master_payer_billing_report_name")

    # Build CUR data if all three parameters are provided
    cur_data = None
    if cur_bucket_name and cur_bucket_path and cur_report_name:
        cur_data = CurDataUpdate(bucket_name=cur_bucket_name, bucket_path=cur_bucket_path, report_name=cur_report_name)
    elif cur_bucket_name or cur_bucket_path or cur_report_name:
        if debug:
            console.print("[yellow]DEBUG: Incomplete CUR configuration - all three fields required[/]")

    # Build update request
    update_request = ConnectionUpdateRequest(
        connection_name=connection_name,
        is_connected=is_connected,
        is_cost_connection=is_cost_connection,
        metrics_enabled=metrics_enabled,
        resources_enabled=resources_enabled,
        cur_data=cur_data,
    )

    # Remove None values for cleaner request
    update_data = {k: v for k, v in update_request.model_dump().items() if v is not None}

    if not update_data:
        raise ValueError("At least one update field must be provided")

    if debug:
        console.print(f"[cyan]DEBUG: Updating connection {connection_id} via V4 API[/]")
        console.print(f"[cyan]DEBUG: Update data: {update_data}[/]")

    try:
        # Make authenticated request to v4 connection update endpoint
        response_data = put_authenticated(endpoint_path=f"connection/{connection_id}", json_data=update_data, debug=debug)

        if debug:
            console.print(f"[cyan]DEBUG: Raw API response: {response_data}[/]")

        # Parse response using Pydantic model
        response = ConnectionUpdateResponse(**response_data)

        if debug:
            console.print(f"[green]DEBUG: Successfully updated connection {connection_id}[/]")
            console.print(f"[green]DEBUG: Updated fields: {', '.join(response.updated_fields)}[/]")

        return response

    except APIError:
        # Re-raise API errors as-is
        raise
    except Exception as e:
        # Check if this is a Pydantic validation error
        if "ValidationError" in str(type(e).__name__) or "ValidationError" in str(type(e)):
            if debug:
                console.print(f"[red]DEBUG: Response validation failed: {e}[/]")
            raise APIError(f"Invalid response format from v4 API: {e}") from e
        # Handle other unexpected errors
        error_msg = f"Failed to update connection {connection_id}: {str(e)}"
        if debug:
            console.print(f"[red]DEBUG: Unexpected error: {error_msg}[/]")
        raise APIError(error_msg) from e
