# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
"""Fetch connection configuration and catalog from Airbyte Cloud.

This module provides utilities for fetching source configuration and connection
catalog from Airbyte Cloud using the public API.
"""

from __future__ import annotations

import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import requests
from airbyte import constants
from airbyte.exceptions import PyAirbyteInputError


@dataclass
class ConnectionData:
    """Data fetched from an Airbyte Cloud connection."""

    connection_id: str
    source_id: str
    source_name: str
    source_definition_id: str
    config: dict[str, Any]
    catalog: dict[str, Any]
    stream_names: list[str]


def _get_access_token(
    client_id: str,
    client_secret: str,
) -> str:
    """Get an access token for Airbyte Cloud API."""
    auth_url = f"{constants.CLOUD_API_ROOT}/applications/token"
    response = requests.post(
        auth_url,
        json={
            "client_id": client_id,
            "client_secret": client_secret,
        },
        timeout=30,
    )

    if response.status_code != 200:
        raise PyAirbyteInputError(
            message=f"Failed to authenticate with Airbyte Cloud: {response.status_code}",
            context={"response": response.text},
        )

    return response.json()["access_token"]


def fetch_connection_data(
    connection_id: str,
    client_id: str | None = None,
    client_secret: str | None = None,
) -> ConnectionData:
    """Fetch connection configuration and catalog from Airbyte Cloud.

    Args:
        connection_id: The connection ID to fetch data for.
        client_id: Airbyte Cloud client ID (defaults to env var).
        client_secret: Airbyte Cloud client secret (defaults to env var).

    Returns:
        ConnectionData with config and catalog.

    Raises:
        PyAirbyteInputError: If the API request fails.
    """
    client_id = client_id or os.getenv("AIRBYTE_CLOUD_CLIENT_ID")
    client_secret = client_secret or os.getenv("AIRBYTE_CLOUD_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise PyAirbyteInputError(
            message="Missing Airbyte Cloud credentials",
            context={
                "hint": "Set AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET env vars"
            },
        )

    access_token = _get_access_token(client_id, client_secret)
    api_root = constants.CLOUD_API_ROOT
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }

    # Get connection details
    conn_response = requests.get(
        f"{api_root}/connections/{connection_id}",
        headers=headers,
        timeout=30,
    )

    if conn_response.status_code != 200:
        raise PyAirbyteInputError(
            message=f"Failed to get connection: {conn_response.status_code}",
            context={"connection_id": connection_id, "response": conn_response.text},
        )

    conn_data = conn_response.json()
    source_id = conn_data["sourceId"]

    # Get source details (includes config)
    source_response = requests.get(
        f"{api_root}/sources/{source_id}",
        headers=headers,
        timeout=30,
    )

    if source_response.status_code != 200:
        raise PyAirbyteInputError(
            message=f"Failed to get source: {source_response.status_code}",
            context={"source_id": source_id, "response": source_response.text},
        )

    source_data = source_response.json()

    # Build configured catalog from connection streams
    streams_config = conn_data.get("configurations", {}).get("streams", [])
    stream_names = [s["name"] for s in streams_config]

    # Build Airbyte protocol catalog format
    catalog = _build_configured_catalog(streams_config, source_id, headers, api_root)

    return ConnectionData(
        connection_id=connection_id,
        source_id=source_id,
        source_name=source_data.get("name", ""),
        source_definition_id=source_data.get("definitionId", ""),
        config=source_data.get("configuration", {}),
        catalog=catalog,
        stream_names=stream_names,
    )


def _build_configured_catalog(
    streams_config: list[dict[str, Any]],
    source_id: str,
    headers: dict[str, str],
    api_root: str,
) -> dict[str, Any]:
    """Build a configured catalog from connection stream configuration.

    This creates a catalog in the Airbyte protocol format that can be used
    with connector commands.
    """
    # For now, create a minimal catalog structure
    # A full implementation would fetch the source's discovered catalog
    # and merge it with the connection's stream configuration
    configured_streams = []

    for stream in streams_config:
        stream_name = stream.get("name", "")
        sync_mode = stream.get("syncMode", "full_refresh")

        # Map API sync modes to protocol sync modes
        destination_sync_mode = "append"
        if "incremental" in sync_mode.lower():
            source_sync_mode = "incremental"
        else:
            source_sync_mode = "full_refresh"

        configured_stream = {
            "stream": {
                "name": stream_name,
                "json_schema": {},  # Schema would come from discover
                "supported_sync_modes": ["full_refresh", "incremental"],
            },
            "sync_mode": source_sync_mode,
            "destination_sync_mode": destination_sync_mode,
        }

        cursor_field = stream.get("cursorField")
        if cursor_field:
            configured_stream["cursor_field"] = (
                cursor_field if isinstance(cursor_field, list) else [cursor_field]
            )

        primary_key = stream.get("primaryKey")
        if primary_key:
            configured_stream["primary_key"] = primary_key

        configured_streams.append(configured_stream)

    return {"streams": configured_streams}


def save_connection_data_to_files(
    connection_data: ConnectionData,
    output_dir: Path,
) -> tuple[Path, Path]:
    """Save connection config and catalog to JSON files.

    Args:
        connection_data: The connection data to save.
        output_dir: Directory to save files to.

    Returns:
        Tuple of (config_path, catalog_path).
    """
    output_dir.mkdir(parents=True, exist_ok=True)

    config_path = output_dir / "config.json"
    catalog_path = output_dir / "catalog.json"

    config_path.write_text(json.dumps(connection_data.config, indent=2))
    catalog_path.write_text(json.dumps(connection_data.catalog, indent=2))

    return config_path, catalog_path
