# 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
#

"""
Connection-specific utilities.

This module provides utilities for working with CloudZero connections:
- Connection type determination
- Feature extraction
- Status calculations
- Common connection operations
"""

from typing import Any

# Connection-filter flag values -> backend boolean (None = flag absent = no filter).
HEALTH_FILTER_VALUES = {"healthy": True, "unhealthy": False}
STATUS_FILTER_VALUES = {"connected": True, "disconnected": False}


def validate_connection_filters(arguments: dict) -> str | None:
    """Return an error message if --health/--status carry invalid values, else None."""
    health = arguments.get("--health")
    if health is not None and health not in HEALTH_FILTER_VALUES:
        return f"Invalid --health value '{health}'. Must be one of: {', '.join(HEALTH_FILTER_VALUES)}"
    status = arguments.get("--status")
    if status is not None and status not in STATUS_FILTER_VALUES:
        return f"Invalid --status value '{status}'. Must be one of: {', '.join(STATUS_FILTER_VALUES)}"
    return None


def health_filter(arguments: dict) -> bool | None:
    """Map the --health flag to the backend ``is_healthy`` boolean (None when absent)."""
    return HEALTH_FILTER_VALUES.get(arguments.get("--health"))


def status_filter(arguments: dict) -> bool | None:
    """Map the --status flag to the backend ``is_connected`` boolean (None when absent)."""
    return STATUS_FILTER_VALUES.get(arguments.get("--status"))


def cur_collision_filter(arguments: dict) -> bool | None:
    """--cur-collision flag -> True (filter to CUR-type collisions), else None."""
    return True if arguments.get("--cur-collision") else None


def resolve_cur_type_filter(arguments: dict, debug: bool = False) -> tuple[str | None, str | None]:
    """Resolve --cur-type to a validated CONST, returning ``(cur_type, error)``.

    CUR-type CONSTs are uppercase and matched case-insensitively; the value is normalized to
    upper-case and validated against the live enum map (``/system/cur-types``) so an unknown
    value fails with the valid set rather than silently returning nothing. Returns
    ``(None, None)`` when the flag is absent. If the enum map can't be fetched, the value
    passes through unvalidated (the backend still validates).
    """
    value = arguments.get("--cur-type")
    if not value:
        return None, None
    const = str(value).strip().upper()
    from src.api.v4_system import get_cur_types

    cur_types = get_cur_types(debug=debug)
    if cur_types and const not in cur_types:
        return None, f"Unknown --cur-type '{value}'. Valid values: {', '.join(sorted(cur_types))}"
    return const, None


def resolve_cur_filters(arguments: dict, debug: bool = False) -> tuple[str | None, bool | None, str | None]:
    """Resolve both CUR filters at once: ``(cur_type, cur_type_collision, error)``.

    Combines :func:`resolve_cur_type_filter` and :func:`cur_collision_filter` for the commands
    that accept both (``connection list``, ``org info``). ``error`` is non-None only when
    ``--cur-type`` is invalid; callers print it and abort.
    """
    cur_type, error = resolve_cur_type_filter(arguments, debug=debug)
    return cur_type, cur_collision_filter(arguments), error


def cur_type_display(cur_type: str | None, cur_types_map: dict[str, str] | None, full: bool = False) -> str:
    """Render a connection's cur_type for display.

    ``full`` (detail view) returns ``CONST — Human Label`` (label from the enum map when known);
    otherwise (list view) returns the bare CONST. A missing cur_type renders a dim dash.
    """
    if not cur_type:
        return "[dim]-[/]"
    if not full:
        return cur_type
    label = (cur_types_map or {}).get(cur_type)
    return f"{cur_type} [dim]— {label}[/]" if label else cur_type


def build_cur_s3_path(bucket: str, prefix: str | None, report_name: str | None = None) -> str:
    """Build a full S3 CUR path. Empty prefix produces double-slash (valid S3)."""
    if not bucket:
        return "N/A"
    prefix = prefix if prefix is not None else ""
    if report_name:
        return f"s3://{bucket}/{prefix}/{report_name}/"
    return f"s3://{bucket}/{prefix}"


class ConnectionUtils:
    """Utilities for working with connections."""

    @staticmethod
    def get_connection_type(connection: dict[str, Any]) -> str:
        """
        Determine connection type from connection data.

        Uses connection_types[] as the single source of truth per API contract.

        Args:
            connection: Connection dictionary

        Returns:
            Connection type string (payer, resource, or unknown)
        """
        conn_types = connection.get("connection_types", [])
        if "COST" in conn_types:
            return "payer"
        if "RESOURCE" in conn_types:
            return "resource"
        return "unknown"

    @staticmethod
    def is_payer_connection(connection: dict[str, Any]) -> bool:
        """
        Check if connection is a payer type.

        Args:
            connection: Connection dictionary

        Returns:
            True if payer connection
        """
        return ConnectionUtils.get_connection_type(connection) == "payer"

    @staticmethod
    def is_resource_connection(connection: dict[str, Any]) -> bool:
        """
        Check if connection is a resource type.

        Args:
            connection: Connection dictionary

        Returns:
            True if resource connection
        """
        return ConnectionUtils.get_connection_type(connection) == "resource"

    @staticmethod
    def get_connection_features(connection: dict[str, Any]) -> list[str]:
        """
        Extract enabled features from connection.

        Args:
            connection: Connection dictionary

        Returns:
            List of enabled feature names
        """
        features = []

        # Check various feature flags
        if connection.get("enable_anomaly_detection"):
            features.append("Anomaly Detection")

        if connection.get("enable_cost_allocation"):
            features.append("Cost Allocation")

        if connection.get("enable_rightsizing"):
            features.append("Rightsizing")

        if connection.get("enable_savings_plans"):
            features.append("Savings Plans")

        if connection.get("enable_reserved_instances"):
            features.append("Reserved Instances")

        return features

    @staticmethod
    def get_connection_id(connection: dict[str, Any]) -> str | None:
        """
        Safely get connection ID from connection data.

        Args:
            connection: Connection dictionary

        Returns:
            Connection ID or None
        """
        return connection.get("connection_id")

    @staticmethod
    def get_connection_name(connection: dict[str, Any]) -> str:
        """
        Get connection display name.

        Args:
            connection: Connection dictionary

        Returns:
            Connection name or 'Unknown'
        """
        return connection.get("connection_name", "Unknown")

    @staticmethod
    def get_organization_info(connection: dict[str, Any]) -> tuple[str, str]:
        """
        Get organization ID and name from connection.

        Args:
            connection: Connection dictionary

        Returns:
            Tuple of (org_id, org_name)
        """
        org_id = connection.get("cz_organization_id", "Unknown")
        org_name = connection.get("cz_organization_name", "Unknown")
        return org_id, org_name

    @staticmethod
    def calculate_spend_percentage(
        connection_id: str,
        spend_data: dict[str, float],
    ) -> tuple[float, float, float]:
        """
        Calculate spend amount and percentage for a connection.

        Args:
            connection_id: Connection ID
            spend_data: Dictionary of connection_id -> spend amount

        Returns:
            Tuple of (connection_spend, total_spend, percentage)
        """
        if not spend_data:
            return 0.0, 0.0, 0.0

        connection_spend = spend_data.get(connection_id, 0.0)
        total_spend = sum(spend_data.values())

        percentage = connection_spend / total_spend * 100 if total_spend > 0 else 0.0

        return connection_spend, total_spend, percentage

    @staticmethod
    def format_cur_features(schema_elements: list[str]) -> str:
        """
        Format CUR schema elements for display.

        Maps abbreviated names to full feature names.

        Args:
            schema_elements: List of schema element names

        Returns:
            Comma-separated string of full feature names
        """
        # Mapping of common abbreviations to full names
        feature_map = {
            "RESOURCES": "RESOURCES",
            "RESOURCE_TAGS": "RESOURCE_TAGS",
            "MANUAL_DISCOUNT_COMPATIBILITY": "MANUAL_DISCOUNT_COMPATIBILITY",
            "SPLIT_COST_ALLOCATION_DATA": "SPLIT_COST_ALLOCATION_DATA",
            "BILLING_ENTITY": "BILLING_ENTITY",
        }

        # Map any abbreviated names to full names
        full_names = []
        for element in schema_elements:
            full_name = feature_map.get(element, element)
            full_names.append(full_name)

        return ", ".join(full_names) if full_names else "None"

    @staticmethod
    def format_resource_collection_details(connection: dict[str, Any]) -> str:
        """
        Analyze resource collection configuration and return display string.

        Args:
            connection: Connection dictionary

        Returns:
            Formatted resource collection string
        """
        rc_configs = connection.get("resource_collector_configurations")
        if not rc_configs:
            return "[dim]Disabled[/]"

        # If configs exist, resource collection is enabled
        # Check CloudWatch Metrics status
        cwm_enabled = rc_configs.get("cwm", False)
        cwm_status = "[green]CWM[/]" if cwm_enabled else "[dim]CWM[/]"

        # Check for probes
        probes = rc_configs.get("probes", [])
        if not probes:
            return cwm_status

        # Format probe list
        probe_list = ", ".join(f"[blue]{p}[/]" for p in probes)
        return f"{cwm_status} {probe_list}"

    @staticmethod
    def format_connection_info_list(payer_accounts: list[str], org_connections: dict[str, Any]) -> list[str]:
        """
        Format connection information for display.

        Args:
            payer_accounts: List of payer account IDs
            org_connections: Dictionary of organization connections

        Returns:
            List of formatted connection info strings
        """
        connections_info = []

        # Track which payer accounts we've processed
        for i, payer_id in enumerate(payer_accounts[:2]):
            if i < 2:  # Only show first two payers
                # Find the provider type for this account ID
                provider_type = ""
                for _conn_id, conn in org_connections.items():
                    if conn.get("connection_id") == payer_id and conn.get("provider"):
                        provider_type = conn.get("provider").lower()
                        break

                provider_str = f"({provider_type})" if provider_type else ""
                connections_info.append(f"{payer_id} {provider_str}")

        if len(payer_accounts) > 2:
            connections_info.append(f"+{len(payer_accounts) - 2} more")

        return connections_info
