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

import json
import sys
from urllib.parse import urlparse

import httpx
from rich.console import Console
from rich.syntax import Syntax

from src.api.v4_connections import get_connection
from src.constants import ACCOUNT_LINK_ENDPOINT, ACCOUNT_LINK_REACTOR_ID
from src.utils.console import confirm, eprint

console = Console()

# Maps user-facing --cur-format choices to the BillingReportFormat values
# emitted by provision-account's discovery + notification services.
CUR_FORMAT_CHOICES: dict[str, str] = {
    "csv": "aws",
    "parquet": "aws_parquet",
}


def parse_s3_url(s3_url: str) -> tuple[str, str]:
    """Parse an S3 URL into (bucket_name, path).

    Args:
        s3_url: S3 URL in the format s3://bucket-name/path/prefix/

    Returns:
        Tuple of (bucket_name, path)

    Raises:
        ValueError: If the URL is not a valid s3:// URL
    """
    parsed = urlparse(s3_url)
    if parsed.scheme != "s3":
        raise ValueError(f"Expected s3:// URL, got: {s3_url}")
    bucket_name = parsed.netloc
    path = parsed.path.lstrip("/")
    return bucket_name, path


def _find_role_arn(record: dict) -> str | None:
    """Find role_arn from types.COST.connection_method, then types.RESOURCE.connection_method."""
    types = record.get("types") or {}
    for type_key in ("COST", "RESOURCE"):
        connection_method = (types.get(type_key) or {}).get("connection_method") or {}
        role_arn = connection_method.get("role_arn")
        if role_arn:
            return role_arn
    return None


def _find_external_id(record: dict) -> str | None:
    """Find external_id from types.COST.connection_method, then types.RESOURCE.connection_method."""
    types = record.get("types") or {}
    for type_key in ("COST", "RESOURCE"):
        connection_method = (types.get(type_key) or {}).get("connection_method") or {}
        external_id = connection_method.get("external_id")
        if external_id:
            return external_id
    return None


def build_link_payload(connection_id: str, record: dict, arguments: dict) -> dict:
    """Build the JSON payload for the account link API.

    Args:
        connection_id: The cloud account ID
        record: Connection record dict from the API
        arguments: CLI arguments dict

    Returns:
        Complete payload dict for POST to the link endpoint
    """
    role_arn = arguments.get("--role-arn") or _find_role_arn(record)
    external_id = arguments.get("--external-id") or _find_external_id(record)
    cz_account_name = arguments.get("--name") or record.get("connection_name") or connection_id

    # Region resolution: --region > connection_region > us-east-1
    region = arguments.get("--region") or record.get("connection_region") or "us-east-1"

    # CUR path parsing
    cur_path = arguments.get("--cur-path")
    bucket_name = ""
    bucket_path = ""
    is_master_payer = False
    if cur_path:
        bucket_name, bucket_path = parse_s3_url(cur_path)
        is_master_payer = True

    # Mirror provision-account's BillingReportFormat: csv→aws, parquet→aws_parquet.
    cur_format_choice = (arguments.get("--cur-format") or "csv").lower()
    if cur_format_choice not in CUR_FORMAT_CHOICES:
        raise ValueError(
            f"Invalid --cur-format '{cur_format_choice}'. Must be one of: {', '.join(sorted(CUR_FORMAT_CHOICES))}"
        )
    billing_report_format = CUR_FORMAT_CHOICES[cur_format_choice]

    return {
        "data": {
            "links": {
                "master_payer": {
                    "role_arn": role_arn or "",
                },
                "resource_owner": {
                    "role_arn": role_arn or "",
                },
            },
            "discovery": {
                "master_payer_billing_bucket_path": bucket_path,
                "is_resource_owner_account": True,
                "is_master_payer_account": is_master_payer,
                "cloudtrail_trail_arn": "",
                "is_cloudtrail_owner_account": False,
                "master_payer_billing_bucket_name": bucket_name,
                "audit_cloudtrail_bucket_name": "",
                "cloudtrail_sns_topic_arn": "",
                "remote_cloudtrail_bucket": False,
                "is_organization_trail": False,
                "visible_cloudtrail_arns": [],
                "is_organization_master_account": False,
                "audit_cloudtrail_bucket_prefix": "",
                "is_audit_account": False,
            },
            "metadata": {
                "reactor_callback_url": ACCOUNT_LINK_ENDPOINT,
                "external_id": external_id or "",
                "cz_account_name": cz_account_name,
                "cloud_region": region,
                "cloud_account_id": connection_id,
                "reactor_id": ACCOUNT_LINK_REACTOR_ID,
                "billing_report_format": billing_report_format,
            },
        },
        "message_type": "account-link-provisioned",
        "message_source": "cfn",
        "version": "1",
    }


def display_payload(payload: dict) -> None:
    """Display the link payload as pretty-printed JSON."""
    console.print(Syntax(json.dumps(payload, indent=2), "json", theme="monokai"))


def send_link_payload(payload: dict, debug: bool = False) -> dict:
    """POST the link payload to the CloudZero accounts link endpoint.

    The POST is intentionally unauthenticated: this is the same public endpoint
    the CloudFormation custom resource in customer accounts calls during
    onboarding, where no CloudZero credentials exist. The backend validates the
    payload by assuming the submitted role with the submitted external ID.

    Args:
        payload: The complete link payload dict
        debug: Whether to show debug information

    Returns:
        Response JSON dict

    Raises:
        httpx.HTTPStatusError: If the request fails
    """
    if debug:
        console.print(f"[cyan]DEBUG: POST {ACCOUNT_LINK_ENDPOINT}[/]")

    response = httpx.post(ACCOUNT_LINK_ENDPOINT, json=payload, timeout=30)
    response.raise_for_status()
    return response.json()


def functions(arguments: dict) -> None:
    """Entry point for `czops connection link <ID>`."""
    connection_id = arguments.get("<ID>")
    debug = arguments.get("--debug", False)
    dry_run = arguments.get("--dry-run", False)
    force = arguments.get("--force", False)

    console.print(f"\nFetching connection information for [cyan]{connection_id}[/]...")

    try:
        connection_record = get_connection(connection_id, debug=debug)
        record = connection_record.model_dump()
    except Exception as e:
        eprint(f"ERROR: Failed to get connection {connection_id}: {e}")
        sys.exit(1)

    # Validate required fields
    role_arn = arguments.get("--role-arn") or _find_role_arn(record)
    external_id = arguments.get("--external-id") or _find_external_id(record)

    if not role_arn:
        eprint("ERROR: No role_arn found in connection record. Provide --role-arn.")
        sys.exit(1)
    if not external_id:
        eprint("ERROR: No external_id found in connection record. Provide --external-id.")
        sys.exit(1)

    payload = build_link_payload(connection_id, record, arguments)

    console.print()
    display_payload(payload)

    if dry_run:
        console.print("\n[yellow]Dry run — no request sent.[/yellow]")
        return

    if not force:
        if not confirm("Send link request?"):
            console.print("Aborted.")
            return

    console.print("\n[bold]Sending link request...[/bold]")
    try:
        result = send_link_payload(payload, debug=debug)
        console.print("[bold green]Link Response:[/bold green]")
        console.print(Syntax(json.dumps(result, indent=2), "json", theme="monokai"))
    except httpx.HTTPStatusError as e:
        eprint(f"ERROR: Link request failed: {e.response.status_code} {e.response.text}")
        sys.exit(1)
    except Exception as e:
        eprint(f"ERROR: Link request failed: {e}")
        sys.exit(1)
