# Copyright (c) 2025 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
#

import json

from rich import box
from rich.columns import Columns
from rich.console import Console, Group
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich.tree import Tree

from src.api.v4_connections import get_connection, get_connection_raw, get_connections, get_connections_for_org
from src.constants import NO_ACCOUNT_NAME, NO_ORG_NAME
from src.features.organizations import get_organization_data
from src.utils.console import eprint, format_account_stage
from src.utils.data import looks_like_a_connection_id

console = Console(width=None, highlight=False)

# AWS link_health component -> the connection type it validates, for the merged
# Connection & Health panel. Components not listed fall under an "Other" group.
_LINK_HEALTH_COMPONENT_TYPE = {
    "master_payer": "COST",
    "ingest": "COST",
    "reports": "COST",
    "resource_owner": "RESOURCE",
}


# Removed duplicated __format_account_stage function - now using the centralized version in utils.interface


def _fmt_bool(value: bool | None, true_text: str = "Yes", false_text: str = "No") -> str:
    """Format a boolean as a colored string."""
    if value is None:
        return "[dim]N/A[/]"
    return f"[green]{true_text}[/]" if value else f"[red]{false_text}[/]"


def _org_external_id(connections: list[dict] | None) -> str | None:
    """Return the customer STS external ID for an org (constant across its connections).

    The external ID lives on each connection at ``types.{TYPE}.connection_method.external_id``
    and is the same for every connection in an organization, so the first one found is
    representative. Returns None when no connection carries one.
    """
    for conn in connections or []:
        for type_config in (conn.get("types") or {}).values():
            external_id = ((type_config or {}).get("connection_method") or {}).get("external_id")
            if external_id:
                return external_id
    return None


def _build_org_tree_panel(connection: dict) -> Panel | None:
    """Build an AWS Organizations tree panel for a payer connection, or None.

    Reads the ``organization_connections`` plugin metadata on a COST/payer connection and
    renders its OU hierarchy as a Rich Tree with a summary line, wrapped in a panel titled
    by the payer's connection id. Returns None when the connection carries no successful
    plugin metadata. Shared by ``org info`` (one panel per payer, flowed side-by-side).
    """
    metadata = connection.get("connection_metadata")
    if not metadata:
        return None
    org_plugin = (metadata.get("plugins") or {}).get("organization_connections") or {}
    if org_plugin.get("status") != "success":
        return None
    plugin_data = org_plugin.get("data") or {}
    root_data = plugin_data.get("root") or {}
    if not root_data:
        return None
    summary = plugin_data.get("summary") or {}
    org_info = plugin_data.get("organization") or {}

    tree_label = "AWS Organization"
    if org_info:
        parts = [p for p in [org_info.get("id", ""), org_info.get("feature_set", "")] if p]
        if parts:
            tree_label += f": {' / '.join(parts)}"
        if master_email := org_info.get("master_account_email", ""):
            tree_label += f"  [dim]({master_email})[/]"
    tree = Tree(f"[bold]{tree_label}[/]")

    def _count_recursive(node):
        total = len(node.get("accounts") or [])
        for child in node.get("organizational_units") or []:
            total += _count_recursive(child)
        return total

    def _build_tree(parent_branch, node):
        ous = node.get("organizational_units") or []
        direct_accounts = node.get("accounts") or []
        for ou in ous:
            label = f"[bold]{ou.get('name', 'Unknown OU')}[/]  [cyan]{_count_recursive(ou)}[/] accounts"
            for acct in ou.get("accounts") or []:
                if email := acct.get("email"):
                    label += f"  [dim]({email})[/]"
                    break
            _build_tree(parent_branch.add(label), ou)
        if direct_accounts and ous:
            connected_direct = sum(1 for a in direct_accounts if a.get("is_connected"))
            parent_branch.add(f"[dim]{len(direct_accounts)} direct accounts ({connected_direct} connected)[/]")

    _build_tree(tree, root_data)

    total = int(summary.get("total_accounts", 0))
    connected = int(summary.get("connected", summary.get("connected_count", 0)))
    unconnected = int(summary.get("unconnected", summary.get("unconnected_count", 0)))
    rate = float(summary.get("connection_rate_pct", summary.get("connection_rate_percent", 0)))
    ou_count = int(summary.get("organizational_units", 0))
    summary_line = (
        f"\n[bold]{total}[/] accounts  |  [green]{connected} connected[/]  |  "
        f"[red]{unconnected} unconnected[/]  ({rate:.0f}%)  |  {ou_count} OUs"
    )

    org_content = Table.grid(expand=True)
    org_content.add_column()
    org_content.add_row(tree)
    org_content.add_row(summary_line)
    return Panel(
        org_content,
        title=f"[bold]Payer {connection.get('connection_id', '')}[/]",
        title_align="left",
        border_style="dim",
    )


def _make_kv_table(title: str, rows: list[tuple[str, str]]) -> Table:
    """Create a compact key-value table with a section title."""
    table = Table(
        box=None,
        show_header=False,
        padding=(0, 1),
        expand=True,
        title=f"[bold]{title}[/]",
        title_style="blue",
        title_justify="left",
    )
    table.add_column("Key", style="dim", justify="right", min_width=16, max_width=22)
    table.add_column("Value", style="white", ratio=1)
    for key, value in rows:
        if value:
            table.add_row(key, value)
    return table


def display_organization_summary(
    org_id: str,
    org_details: dict,
    connections: list[dict] | None = None,
    discovered_count: int = 0,
    shown_count: int | None = None,
) -> None:
    """
    Display organization summary information (reusable across commands).

    Renders all organization fields in a single bordered panel with a two-column
    layout: identity/classification on the left, connections/capabilities on the right.

    Args:
        org_id: Organization ID
        org_details: Dictionary or Pydantic model containing organization details from V4 API
        connections: Optional list of per-connection dicts to enrich summary with health/autolink detail
        discovered_count: Number of AWS accounts discovered but not yet connected
        shown_count: When a connection filter is active, the number of connections actually
            displayed — annotated onto the Connections count so it doesn't read as a total.
    """
    if hasattr(org_details, "model_dump"):
        org_details = org_details.model_dump()

    org_name = org_details.get("cz_organization_name", NO_ORG_NAME)
    connections_summary = org_details.get("connections_summary") or {}
    if hasattr(connections_summary, "model_dump"):
        connections_summary = connections_summary.model_dump()

    # --- Left column: Identity & Classification ---

    identity_rows = [
        ("ID", org_details.get("cz_organization_id")),
        ("Name", org_name),
        ("External ID", _org_external_id(connections)),
        ("Status", _fmt_bool(org_details.get("organization_enabled"), "Enabled", "Disabled")),
        ("Stage", format_account_stage(org_details.get("organization_stage"))),
        ("Type", org_details.get("organization_type")),
        ("Domain", org_details.get("domain")),
        ("Website", org_details.get("website")),
        ("Email Domains", org_details.get("email_domains")),
    ]
    identity_table = _make_kv_table("Identity", identity_rows)

    classification_rows = [
        ("Salesforce ID", org_details.get("salesforce_account_id")),
        ("Group ID", org_details.get("organization_group_id")),
        ("Login", _fmt_bool(org_details.get("login_enabled"))),
        ("Cost Ingest", _fmt_bool(org_details.get("cost_ingest_enabled"))),
    ]
    classification_table = _make_kv_table("Classification", classification_rows)

    timestamps_rows = [
        ("Created", org_details.get("created") or "Unknown"),
        ("Last Updated", org_details.get("last_updated")),
        ("First Seen", org_details.get("first_seen")),
        ("Verified", org_details.get("last_materialized_at")),
    ]
    record_sources = org_details.get("record_sources")
    if record_sources and isinstance(record_sources, list):
        timestamps_rows.append(("Data Sources", ", ".join(record_sources)))
    timestamps_table = _make_kv_table("Timestamps", timestamps_rows)

    left_column = Group(identity_table, Text(""), classification_table, Text(""), timestamps_table)

    # --- Right column: Connections & Capabilities ---

    total_connections = connections_summary.get("total_connections", 0)
    enabled_connections = connections_summary.get("total_enabled_connections", 0)

    conn_count = str(total_connections)
    if enabled_connections and enabled_connections != total_connections:
        conn_count = f"{enabled_connections} enabled / {total_connections} total"
    if shown_count is not None:
        conn_count += f" [yellow](filter active: {shown_count} shown)[/]"

    enabled_providers = connections_summary.get("enabled_providers", [])
    all_providers = connections_summary.get("all_providers", [])
    providers_display = ", ".join(enabled_providers) if enabled_providers else "None"
    if all_providers and all_providers != enabled_providers:
        providers_display += f" [dim](all: {', '.join(all_providers)})[/]"

    type_counts = connections_summary.get("type_counts", {})
    type_display = None
    if isinstance(type_counts, dict) and type_counts:
        type_display = "  ".join(f"{k}: {v}" for k, v in sorted(type_counts.items()))

    spend_display = None
    if total_spend := connections_summary.get("total_billed_spend_30_days"):
        try:
            spend_value = float(total_spend)
            if spend_value > 0:
                spend_display = f"[green]${spend_value:,.2f}[/]"
        except (ValueError, TypeError):
            pass

    connections_rows = [
        ("Connections", conn_count),
        ("Providers", providers_display),
        ("Type Breakdown", type_display),
        ("30-Day Spend", spend_display),
    ]
    if discovered_count:
        connections_rows.append(("Discovered", f"[bright_cyan]{discovered_count}[/] awaiting connection"))
    connections_table = _make_kv_table("Connections", connections_rows)

    # Capabilities with per-connection enrichment
    if connections:
        healthy_count = sum(1 for c in connections if c.get("is_healthy"))
        health_display = f"{healthy_count} / {len(connections)}"
        if healthy_count == len(connections):
            health_display = f"[green]{health_display}[/]"
        elif healthy_count == 0:
            health_display = f"[red]{health_display}[/]"
        else:
            health_display = f"[yellow]{health_display}[/]"

        provider_counts: dict[str, int] = {}
        for c in connections:
            provider = c.get("provider", "Unknown")
            if provider != "Unknown":
                provider_counts[provider] = provider_counts.get(provider, 0) + 1
        provider_breakdown = "  ".join(f"{p}: {n}" for p, n in sorted(provider_counts.items()))

        auto_link_enabled = any(c.get("auto_link_enabled", False) for c in connections)

        capabilities_rows = [
            ("Healthy", health_display),
            ("By Provider", provider_breakdown),
            ("Auto-Link", _fmt_bool(auto_link_enabled)),
        ]
    else:
        auto_link_summary = connections_summary.get("auto_link_enabled")
        capabilities_rows = [
            ("Auto-Link", _fmt_bool(auto_link_summary) if auto_link_summary is not None else None),
        ]
    capabilities_table = _make_kv_table("Capabilities", capabilities_rows)

    right_column = Group(connections_table, Text(""), capabilities_table)

    # --- Assemble into a panel: two columns side by side, stacked on narrow terminals ---

    layout = Table(box=None, show_header=False, padding=(0, 2), expand=True)
    if console.size.width >= 110:
        layout.add_column(ratio=1)
        layout.add_column(ratio=1)
        layout.add_row(left_column, right_column)
    else:
        layout.add_column(ratio=1)
        layout.add_row(left_column)
        layout.add_row(right_column)

    panel = Panel(
        layout,
        title=f"[bold cyan]{org_name}[/]",
        subtitle=f"[dim]{org_id}[/]",
        border_style="cyan",
        expand=True,
        padding=(1, 2),
    )
    console.print(panel)


def display_organization_info(arguments, organization_data):
    """Display detailed information about a single organization."""
    # Check if raw JSON output is requested (actual API response) - must be first
    # because raw mode returns full API response structure, not org_id -> org dict
    if arguments.get("--raw"):
        import json

        # organization_data is the full API response when --raw is used
        print(json.dumps(organization_data, indent=2, default=str))
        return

    if len(organization_data.keys()) > 1:
        print("Found multiple organizations matching your query. Please refine your search.")
        for org_id, org_details in organization_data.items():
            # Handle both Pydantic models and dicts
            if hasattr(org_details, "model_dump"):
                org_name = org_details.cz_organization_name or NO_ORG_NAME
            else:
                org_name = org_details.get("cz_organization_name", NO_ORG_NAME)
            print(f"  - {org_name} ({org_id})")
        return

    # Check if formatted dict output is requested
    if arguments.get("--json"):
        from rich.pretty import pprint

        for org_id, org_details in organization_data.items():
            if hasattr(org_details, "model_dump"):
                pprint(org_details.model_dump())
            else:
                pprint(org_details)
        return

    from src.utils.connections import health_filter, resolve_cur_filters, status_filter

    debug = arguments.get("--debug", False)
    is_healthy = health_filter(arguments)
    is_connected = status_filter(arguments)
    cur_type, cur_type_collision, cur_type_error = resolve_cur_filters(arguments, debug=debug)
    if cur_type_error:
        eprint(f"Error: {cur_type_error}")
        return
    # Any active connection filter makes the org's rollup count (total_connections) disagree
    # with the rows shown below — flag it so the summary can annotate the displayed count.
    filters_active = any(v is not None for v in (is_healthy, is_connected, cur_type, cur_type_collision))

    for org_id, org_details in organization_data.items():
        # Fetch per-connection data via dedicated API
        from src.features.connections.list import classify_discovery_stub, display_connections_for_organization

        connections_list = get_connections_for_org(
            org_id,
            is_healthy=is_healthy,
            is_connected=is_connected,
            cur_type=cur_type,
            cur_type_collision=cur_type_collision,
            debug=debug,
        )

        # Build connection dicts for display
        connection_dicts = []
        connection_data = {}
        if connections_list:
            for conn in connections_list:
                conn_dict = conn.model_dump() if hasattr(conn, "model_dump") else conn
                conn_dict["is_connected"] = conn_dict.get("enabled", True) and conn_dict.get("is_healthy", True)
                classify_discovery_stub(conn_dict)
                connection_dicts.append(conn_dict)
                connection_data[conn_dict.get("connection_id", "")] = conn_dict

        # Real connections vs backend-materialized discovery stubs
        real_connections = [c for c in connection_dicts if c.get("account_state") != "discovered"]
        total_discovered = len(connection_dicts) - len(real_connections)

        # Display organization summary with per-connection intelligence. shown_count counts
        # real connections only (not discovered stubs) so it stays comparable to the rollup
        # total_connections it annotates; discovered stubs have their own count line.
        display_organization_summary(
            org_id,
            org_details,
            connections=real_connections or None,
            discovered_count=total_discovered,
            shown_count=len(real_connections) if filters_active else None,
        )

        if connection_data:
            display_connections_for_organization(org_id, organization_data, connection_data, show_header=False)

        # AWS Organization trees — one panel per payer (COST connection), flowed side by
        # side and wrapped to terminal width. The org connections list omits plugin
        # metadata, so each payer's full record is fetched for its organization_connections
        # tree (bounded by the payer count, typically small).
        debug_mode = arguments.get("--debug", False)
        payer_panels = []
        for conn in real_connections:
            if "COST" not in (conn.get("connection_types") or []):
                continue
            record = conn if conn.get("connection_metadata") else None
            if record is None:
                try:
                    fetched = get_connection(conn["connection_id"], debug=debug_mode)
                    record = fetched.model_dump() if hasattr(fetched, "model_dump") else fetched
                except Exception as error:
                    if debug_mode:
                        eprint(f"DEBUG: org tree metadata fetch failed for {conn.get('connection_id')}: {error}")
                    continue
            if panel := _build_org_tree_panel(record):
                payer_panels.append(panel)
        if payer_panels:
            console.print()
            console.print(Columns(payer_panels, expand=False, equal=False))

        if total_discovered:
            connected_count = sum(1 for c in real_connections if c.get("is_connected"))
            disconnected_count = len(real_connections) - connected_count
            console.print(
                f"[green]{connected_count} connected[/] · [red]{disconnected_count} disconnected[/] · "
                f"[bright_cyan]{total_discovered} discovered[/]"
            )
            console.print(
                "[dim]🟢=Healthy 🟡=Partial 🔴=Disconnected 🔵=Discovered"
                " ●=Verified(brighter=newer) ○=Unverified>3d blank=Expiring<6h -=No timestamp ⚠=CUR collision[/dim]"
            )

        # Add link to web UI
        console.print(
            f"\n[cyan]Web Console:[/] [link=https://app.cloudzero.com?organization_id={org_id}]https://app.cloudzero.com?organization_id={org_id}[/link]\n"
        )


def display_connection_info(arguments, connection_id, organization_data=None):
    """Display detailed information about a single connection."""
    # Variables to track connection discovery
    connection_data = None
    found = False
    org_id = None
    org_name = NO_ORG_NAME

    # Debug output
    debug_mode = arguments.get("--debug", False)
    if debug_mode:
        eprint(f"DEBUG: Looking up connection info for ID or name '{connection_id}'")

    # Handle --raw: fetch and display the unmodified API response body
    if arguments.get("--raw"):
        try:
            with console.status("[cyan]Fetching raw connection data..."):
                raw_data = get_connection_raw(connection_id, debug=debug_mode)
            print(json.dumps(raw_data, indent=2, default=str))
        except ValueError as e:
            console.print(f"[red]Error:[/] {e}")
        except Exception as e:
            console.print(f"[red]Error:[/] Failed to fetch connection: {e}")
        return

    # Direct lookup of connection using get_connection API
    with console.status("[cyan]Finding connection info..."):
        # First try direct connection lookup by ID - this is the efficient way
        if debug_mode:
            eprint("DEBUG: Fetching connection data directly with get_connection")
        try:
            connection_v4 = get_connection(connection_id, debug=debug_mode)
            if connection_v4:
                # Successfully retrieved connection directly
                found = True
                org_id = connection_v4.cz_organization_id
                # Store the actual connection data for later use
                connection_data = {"direct_connection": connection_v4.model_dump()}

                if debug_mode:
                    eprint(f"DEBUG: Successfully retrieved connection {connection_id}")
                    eprint("DEBUG: Connection found directly using get_connection API")
                    eprint(f"DEBUG: Organization ID: {org_id}")

                # If we have organization data, try to get the org name
                if organization_data and org_id and org_id in organization_data:
                    org_name = organization_data[org_id].get("cz_organization_name", NO_ORG_NAME)
            else:
                connection_data = None
                if debug_mode:
                    eprint("DEBUG: get_connection returned None - connection not found or no access")
        except Exception as e:
            connection_data = None
            if debug_mode:
                eprint(f"DEBUG: Exception in get_connection: {e}")

        # Fallback: Try name-based search using get_connections if direct lookup failed
        if not found:
            if debug_mode:
                eprint(
                    f"DEBUG: Direct lookup failed, trying name-based search with get_connections(name='{connection_id}')"
                )

            try:
                matching_connections = get_connections(name=connection_id, enabled_only=False, debug=debug_mode)

                if debug_mode:
                    eprint(f"DEBUG: Name search returned {len(matching_connections)} results")

                if len(matching_connections) == 1:
                    # Exactly one match - display it
                    connection_v4 = matching_connections[0]
                    found = True
                    org_id = connection_v4.cz_organization_id
                    connection_data = {"direct_connection": connection_v4.model_dump()}

                    if debug_mode:
                        eprint(f"DEBUG: Found single matching connection: {connection_v4.connection_id}")

                    # If we have organization data, try to get the org name
                    if organization_data and org_id and org_id in organization_data:
                        org_name = organization_data[org_id].get("cz_organization_name", NO_ORG_NAME)

                elif len(matching_connections) > 1:
                    # Multiple matches - show list and ask user to be more specific
                    console.print(f"\n[yellow]Multiple connections found matching '[cyan]{connection_id}[/cyan]'[/]\n")
                    console.print(
                        f"Found [bold]{len(matching_connections)}[/] connections. Please specify the exact connection ID:\n"
                    )

                    # Create a table to display matches
                    table = Table(show_header=True, header_style="bold cyan", box=None, padding=(0, 2))
                    table.add_column("Connection ID", style="yellow")
                    table.add_column("Name", style="bright_white")
                    table.add_column("Provider", style="cyan")
                    table.add_column("Organization ID", style="dim")

                    for conn in matching_connections:
                        conn_dict = conn.model_dump()
                        table.add_row(
                            conn_dict.get("connection_id", "Unknown"),
                            conn_dict.get("connection_name") or NO_ACCOUNT_NAME,
                            conn_dict.get("provider", "Unknown"),
                            conn_dict.get("cz_organization_id", "Unknown"),
                        )

                    console.print(table)
                    console.print("\n[cyan]💡 To view a specific connection:[/]")
                    console.print("  [white]czops connection info <CONNECTION_ID>[/]\n")
                    return
                else:
                    # No matches at all
                    if debug_mode:
                        eprint("DEBUG: Name search returned no results")

            except Exception as search_error:
                if debug_mode:
                    eprint(f"DEBUG: Exception during name-based search: {search_error}")

    if not found or not connection_data:
        # If the whole dataset is unavailable (materialization not recorded), say so rather than
        # implying this specific connection is missing — the outage hits every lookup.
        from src.features.organizations import backend_data_available

        if not backend_data_available(debug=debug_mode):
            console.print("\n[bold yellow]⚠️  CloudZero data is temporarily unavailable[/]")
            console.print(
                "The backend has no materialized connection data right now (a data refresh is in "
                "progress or has failed), so every connection lookup returns 'not found'."
            )
            console.print("\n[cyan]What to do:[/]")
            console.print("  • Retry in a few minutes — this is usually transient while data refreshes.")
            console.print("  • If it persists, report in #cz-tools (mention 'materialization').")
        else:
            console.print(f"[red]Error:[/] Connection '{connection_id}' not found or you don't have access to it.")
        return

    # Get the connection details - handle both direct connection format and legacy format
    connection = None

    # First, check if we have a direct connection (from get_connection API)
    if "direct_connection" in connection_data:
        connection = connection_data["direct_connection"]
        if debug_mode:
            eprint("DEBUG: Using direct connection data")
    # Fallback: check legacy format
    elif org_id and "data" in connection_data:
        connection = connection_data.get("data", {}).get(org_id, {}).get(connection_id)
        if debug_mode:
            eprint("DEBUG: Using legacy format connection data from org-specific lookup")
    # Last fallback: try to find in any organization (there should only be one)
    elif "data" in connection_data:
        for org_data in connection_data.get("data", {}).values():
            if connection_id in org_data:
                connection = org_data[connection_id]
                if debug_mode:
                    eprint("DEBUG: Found connection in legacy format from fallback search")
                break

    if not connection:
        console.print("[red]Error:[/] Connection info not available.")
        if debug_mode:
            eprint(f"DEBUG: Connection data structure: {connection_data}")
            eprint(f"DEBUG: Could not find connection {connection_id} in any format")
        return

    # Get org info - fetch organization data if not provided
    if organization_data is None and org_id:
        # Fetch organization data using the org_id from connection
        try:
            organization_data = get_organization_data(org_id, {"info": True})
        except Exception:
            organization_data = None

    org_name = (
        NO_ORG_NAME
        if organization_data is None
        else organization_data.get(org_id, {}).get("cz_organization_name", NO_ORG_NAME)
    )

    # --raw is handled at the top of this function (bypasses all lookup logic)

    # Check if formatted JSON output is requested (human-readable)
    if arguments.get("--json"):
        connection_with_org = {"organization_id": org_id, "cz_organization_name": org_name, "connection": connection}
        print(json.dumps(connection_with_org, indent=2))
        return

    # --- Data Extraction ---
    # Per-type config (types.COST, types.RESOURCE, ...) — the v4 contract nests all
    # operational detail under types.{TYPE}.data / .connection_method; merge into one
    # flat view (first type wins per key).
    types_config = connection.get("types") or {}
    conn_details: dict = {}
    for type_name in ["COST", "RESOURCE", "METRICS", "DATA"]:
        tc = types_config.get(type_name, {})
        for source in (tc.get("data", {}), tc.get("connection_method", {})):
            for k, v in source.items():
                if k not in conn_details and v is not None:
                    conn_details[k] = v

    def get_field(key, default=None):
        """Get field from connection_details first, then top-level."""
        val = conn_details.get(key)
        if val is not None:
            return val
        val = connection.get(key)
        if val is not None:
            return val
        return default

    def fmt(val):
        """Format a value for display."""
        if val is None or val == "":
            return "[dim]--[/]"
        if isinstance(val, bool):
            return "[green]Yes[/]" if val else "[red]No[/]"
        if isinstance(val, list):
            if not val:
                return "[dim]--[/]"
            if len(val) <= 5:
                return ", ".join(str(item) for item in val)
            return f"{len(val)} items"
        return str(val)

    def fmt_status(val, true_text, false_text):
        """Format a boolean as a colored status label."""
        return f"[green]{true_text}[/]" if val else f"[red]{false_text}[/]"

    def fmt_ts(val):
        """Format an ISO timestamp to shorter readable form."""
        if not val:
            return "[dim]--[/]"
        s = str(val)
        if "T" in s:
            date_part = s.split("T")[0]
            time_part = s.split("T")[1].split(".")[0].split("+")[0]
            return f"{date_part} {time_part} UTC"
        return s

    def make_panel(title: str, rows: list[tuple[str, str]], border: str = "dim") -> Panel:
        """Create a titled panel with key-value rows."""
        tbl = Table(box=None, show_header=False, padding=(0, 1), expand=True)
        tbl.add_column("K", style="bold blue", justify="right", no_wrap=True, width=18)
        tbl.add_column("V", style="white", overflow="fold", no_wrap=False)
        for label, value in rows:
            tbl.add_row(label, value)
        return Panel(tbl, title=f"[bold]{title}[/]", title_align="left", border_style=border, expand=True)

    def two_column_row(left: Panel, right: Panel):
        """Print two panels side by side."""
        grid = Table.grid(expand=True)
        grid.add_column(ratio=1)
        grid.add_column(ratio=1)
        grid.add_row(left, right)
        console.print(grid)

    # --- Header ---
    conn_name = connection.get("connection_name", NO_ACCOUNT_NAME)
    actual_conn_id = connection.get("connection_id", connection_id)

    # Build coalesced type description from connection_types[]
    conn_types = connection.get("connection_types", [])
    conn_type_display = ", ".join(conn_types) if conn_types else "Unknown"

    console.print(
        Panel(
            f"[bold cyan]{conn_name}[/] ([cyan]{actual_conn_id}[/]) in [bold blue]{org_name}[/]",
            title="Connection Info",
            title_align="left",
            border_style="cyan",
        )
    )

    # =====================================================================
    # Row 1: Overview + Status
    # =====================================================================
    home_region = get_field("connection_region")
    active_regions = connection.get("active_regions") or []

    overview_rows = [
        ("Connection ID", actual_conn_id),
        ("Name", conn_name),
        ("Organization", org_name),
        ("Org ID", str(org_id) if org_id else "[dim]--[/]"),
        ("Provider", connection.get("provider", "Unknown")),
        ("Types", conn_type_display),
        ("Region", fmt(home_region)),
        ("Active Regions", fmt(active_regions)),
        ("Inactive Regions", fmt(connection.get("inactive_regions"))),
        ("Provisioning", fmt(get_field("provisioning_process"))),
        ("Record Sources", fmt(connection.get("record_sources"))),
    ]

    status_rows = [
        ("Connected", fmt_status(connection.get("is_connected", False), "Yes", "No")),
        ("Enabled", fmt_status(connection.get("enabled", True), "Yes", "No")),
        ("Healthy", fmt_status(connection.get("is_healthy", False), "Yes", "No")),
        ("Cost Connection", fmt_status("COST" in conn_types, "Yes", "No")),
        ("Resource Connection", fmt_status("RESOURCE" in conn_types, "Yes", "No")),
        ("Billing", fmt_status(connection.get("billing_enabled", False), "Yes", "No")),
        ("Metrics", fmt_status(connection.get("metrics_enabled", False), "Yes", "No")),
        ("Resources", fmt_status(connection.get("resources_enabled", False), "Yes", "No")),
        ("Auto-Link", fmt_status(connection.get("auto_link_enabled", False), "Enabled", "Disabled")),
    ]

    two_column_row(make_panel("Overview", overview_rows), make_panel("Status", status_rows))

    # =====================================================================
    # Connection & Health (per-type config + its AWS link-health components)
    # =====================================================================
    # link_health components (AWS per-role check results) grouped under the type they
    # validate (_LINK_HEALTH_COMPONENT_TYPE); any component not belonging to an active
    # type falls into "Other".
    link_health = conn_details.get("link_health") or {}

    if types_config or link_health:

        def _component_row(name: str, check: dict) -> tuple[str, str, str, str]:
            messages = check.get("messages") or {}
            details = "; ".join(str(v) for v in messages.values()) if messages else "[dim]--[/]"
            status = fmt_status(check.get("is_healthy", False), "Healthy", "Unhealthy")
            return (f"  [dim]└[/] {name}", status, details, fmt_ts(check.get("timestamp")))

        def _component_table(rows: list[tuple[str, str, str, str]]) -> Table:
            grid = Table.grid(padding=(0, 2))
            grid.add_column(width=20)  # component
            grid.add_column(width=11)  # status
            grid.add_column(overflow="fold")  # details
            grid.add_column(width=22)  # last checked
            for row in rows:
                grid.add_row(*row)
            return grid

        sections: list = []
        used_components: set[str] = set()
        for type_name in ["COST", "RESOURCE", "METRICS", "DATA"]:
            if type_name not in types_config:
                continue
            tc = types_config[type_name]
            method = tc.get("connection_method") or {}
            sources = tc.get("record_sources") or []
            enabled = "[green]Enabled[/]" if tc.get("enabled") else "[red]Disabled[/]"
            role = method.get("role_arn") or "--"
            src = ", ".join(sources) if sources else "--"
            sections.append(
                Text.from_markup(f"[bold white]{type_name}[/] — {enabled} · [dim]{role}[/]  [dim]\\[{src}][/]")
            )
            comp_rows = [
                _component_row(name, link_health[name])
                for name, mapped in _LINK_HEALTH_COMPONENT_TYPE.items()
                if mapped == type_name and isinstance(link_health.get(name), dict)
            ]
            used_components.update(
                name
                for name, mapped in _LINK_HEALTH_COMPONENT_TYPE.items()
                if mapped == type_name and name in link_health
            )
            if comp_rows:
                sections.append(_component_table(comp_rows))

        other_rows = [
            _component_row(name, check)
            for name, check in link_health.items()
            if name not in used_components and isinstance(check, dict)
        ]
        if other_rows:
            sections.append(Text.from_markup("[bold dim]Other[/]"))
            sections.append(_component_table(other_rows))

        if sections:
            console.print(
                Panel(
                    Group(*sections),
                    title="[bold]Connection & Health[/]",
                    title_align="left",
                    border_style="dim",
                    expand=True,
                )
            )

    # =====================================================================
    # Row 2: Billing & Cost + Auto-Link
    # =====================================================================
    cur_data_obj = conn_details.get("cur_data")
    role_arn = conn_details.get("role_arn")
    external_id = conn_details.get("external_id")
    cur_configs = conn_details.get("cur_configurations") or {}

    billing_rows = []
    if role_arn:
        billing_rows.append(("Role ARN", str(role_arn)))
    if external_id:
        billing_rows.append(("External ID", str(external_id)))
    if cur_data_obj and isinstance(cur_data_obj, dict):
        if report_name := cur_data_obj.get("report_name"):
            billing_rows.append(("Active CUR", report_name))
        if bucket_name := cur_data_obj.get("bucket_name"):
            bucket_path = cur_data_obj.get("bucket_path", "")
            billing_rows.append(("CUR Bucket", f"s3://{bucket_name}/{bucket_path}" if bucket_path else bucket_name))
    if cur_configs:
        billing_rows.append(("Total CURs", f"{len(cur_configs)} configured"))

    # Billing-connections enrichment (cz-ops-services v1.18.0+): CUR identity and
    # ingest health stamped on COST connections; fields absent when never matched.
    # CUR Type shows the CONST + full label (from /system/cur-types); a collision warns.
    if cur_type := connection.get("cur_type"):
        from src.api.v4_system import get_cur_types
        from src.utils.connections import cur_type_display

        billing_rows.append(("CUR Type", cur_type_display(cur_type, get_cur_types(debug=debug_mode), full=True)))
    if connection.get("cur_type_collision"):
        billing_rows.append(
            (
                "CUR Collision",
                "[yellow]⚠ Multiple active connections with different CUR types on this account[/]",
            )
        )
    if cur_version := connection.get("cur_version"):
        billing_rows.append(("CUR Version", cur_version))
    if report_format := connection.get("billing_report_format"):
        billing_rows.append(("Report Format", report_format))
    if paused := connection.get("paused_state"):
        billing_rows.append(("Pause State", "[green]unpaused[/]" if paused == "unpaused" else f"[yellow]{paused}[/]"))
    if connection.get("earliest_ingest_month") or connection.get("latest_ingest_month"):
        window = f"{connection.get('earliest_ingest_month') or '?'} → {connection.get('latest_ingest_month') or '?'}"
        billing_rows.append(("Ingest Window", window))
    if ingest_status := connection.get("ingest_status"):
        billing_rows.append(("Ingest Status", ingest_status))
    if ingest_seen := connection.get("ingest_last_seen"):
        billing_rows.append(("Ingest Last Seen", fmt_ts(ingest_seen)))
    if ingest_success := connection.get("ingest_last_success"):
        billing_rows.append(("Ingest Last Success", fmt_ts(ingest_success)))
    if (bc_id := connection.get("billing_connection_id")) and bc_id != connection.get("connection_id"):
        billing_rows.append(("Billing Conn ID", bc_id))

    auto_link_config = conn_details.get("auto_link") or {}
    # auto_link_enabled is at root of types.COST.data, not inside the auto_link dict
    auto_link_enabled = conn_details.get("auto_link_enabled", False) or connection.get("auto_link_enabled", False)

    al_rows = [("Status", fmt_status(auto_link_enabled, "Enabled", "Disabled"))]
    if auto_link_config:
        if al_role := auto_link_config.get("role_name"):
            al_rows.append(("Role Name", al_role))
        if al_updated := auto_link_config.get("updated_at"):
            al_rows.append(("Last Updated", fmt_ts(al_updated)))
        linked = auto_link_config.get("linked") or []
        failures = auto_link_config.get("link_failure") or []
        ignored = auto_link_config.get("ignore") or []
        al_rows.append(("Linked", f"[green]{len(linked)}[/] accounts"))
        al_rows.append(("Failures", f"[red]{len(failures)}[/] accounts" if failures else "[green]0[/]"))
        al_rows.append(("Ignored", f"{len(ignored)} accounts"))

    has_billing = bool(billing_rows)
    has_autolink = len(al_rows) > 1 or auto_link_enabled  # more than just the status row

    if has_billing and has_autolink:
        two_column_row(make_panel("Billing & Cost", billing_rows), make_panel("Auto-Link", al_rows))
    elif has_billing:
        console.print(make_panel("Billing & Cost", billing_rows))
    elif has_autolink:
        console.print(make_panel("Auto-Link", al_rows))

    # =====================================================================
    # Access Verification
    # =====================================================================
    access_rows = []
    if access_status := connection.get("access_check_status"):
        status_color = "green" if access_status == "success" else "red" if access_status == "failed" else "yellow"
        access_rows.append(("Status", f"[{status_color}]{access_status.title()}[/{status_color}]"))
    if last_check := connection.get("last_access_check"):
        access_rows.append(("Last Check", fmt_ts(last_check)))
    if last_success := connection.get("last_successful_access"):
        access_rows.append(("Last Success", fmt_ts(last_success)))
    if access_error := connection.get("access_check_error"):
        access_rows.append(("Error", f"[red]{access_error}[/]"))

    if access_rows:
        console.print(make_panel("Access Verification", access_rows))

    # =====================================================================
    # CUR Configurations (full width table, only if multiple CURs)
    # =====================================================================
    if cur_configs and len(cur_configs) > 1:
        active_cur_name = cur_data_obj.get("report_name") if cur_data_obj and isinstance(cur_data_obj, dict) else None

        cur_table = Table(
            box=box.SIMPLE_HEAVY,
            show_header=True,
            header_style="bold cyan",
            expand=True,
            padding=(0, 1),
        )
        cur_table.add_column("Report Name", style="white")
        cur_table.add_column("Format")
        cur_table.add_column("Compression")
        cur_table.add_column("S3 Bucket")
        cur_table.add_column("Time Unit")
        cur_table.add_column("Active", width=8)

        for cur_name, cur_cfg in cur_configs.items():
            is_active = cur_name == active_cur_name
            cur_table.add_row(
                f"[bold]{cur_name}[/]" if is_active else cur_name,
                cur_cfg.get("Format", "?"),
                cur_cfg.get("Compression", "?"),
                cur_cfg.get("S3Bucket", "?"),
                cur_cfg.get("TimeUnit", "?"),
                "[green]Yes[/]" if is_active else "[dim]No[/]",
            )

        console.print(
            Panel(cur_table, title="[bold]CUR Configurations[/]", title_align="left", border_style="dim", expand=True)
        )

    # =====================================================================
    # Footer: Timestamps + Web Console
    # =====================================================================
    console.print(
        f"\n[dim]First Seen:[/] {fmt_ts(connection.get('first_seen'))}  "
        f"[dim]Last Seen:[/] {fmt_ts(connection.get('last_seen'))}  "
        f"[dim]Last Updated:[/] {fmt_ts(connection.get('last_updated'))}  "
        f"[dim]Verified:[/] {fmt_ts(connection.get('last_materialized_at'))}"
    )
    console.print(
        f"[cyan]Web Console:[/] https://app.cloudzero.com?organization_id={org_id}&connection_id={actual_conn_id}\n"
    )


def handle_info_command(arguments, organization_data=None):
    """Main handler for the info command."""
    entity_type = "org" if arguments.get("org") else "connection"
    id_or_name = arguments.get("<ID-or-NAME>")

    # Check if we're dealing with a connection ID
    if entity_type == "connection" or looks_like_a_connection_id(id_or_name):
        if entity_type == "org" and looks_like_a_connection_id(id_or_name):
            console.print(
                "[yellow]Warning:[/] The ID you provided looks like a connection ID, but you used the 'org' command. Switching to connection info."
            )
            entity_type = "connection"

        if entity_type == "connection":
            # Display connection info
            display_connection_info(arguments, id_or_name, organization_data)

    else:
        # run() already fetched the org data for this command, and on failure
        # get_organization_data has already explained why (outage vs not-found). Don't re-fetch
        # or re-report — just render when we have data, otherwise bail quietly.
        if not organization_data:
            return

        display_organization_info(arguments, organization_data)
