"""
lux_apis.py

Async client for the Spectra gaming API. Provides a WebSocket listener for real-time
replay streaming with auto-reconnect, HTTP functions for fetching squadron leaderboards,
and data transformation utilities to convert API responses into the local format.
"""

# Standard Library Imports
import asyncio
import json
import logging
import os
from typing import Any, Awaitable, Callable, Dict, List, Optional

# Third-Party Library Imports
import aiohttp
from dotenv import load_dotenv
from websockets.asyncio.client import connect as wsconnect

from spectra_ws_payload import SpectraPayloadError, decode_spectra_ws_payload

# Load environment variables
load_dotenv()

logger = logging.getLogger(__name__)

# Global replay queue for WebSocket messages
_replay_queue: asyncio.Queue = asyncio.Queue()

# Constants from environment
WS_URL = os.getenv("SPECTRA_WS_SQB_URL", "")
API_KEY = os.getenv("SPECTRA_API_KEY", "")
SPECTRA_API_URL = os.getenv("SPECTRA_API_URL", "")
LEADERBOARD_PATH = "/v1/game/leaderboard"
REPLAY_SORT_PATH = "/v1/replays/sort"

def normalize_ws_message(data: Any) -> Optional[List[Dict[str, Any]]]:
    """
    Normalize WebSocket message to list of replay dicts.
    Handles: full replay data, array of replays, or wrapped containers.

    Returns:
        List of replay dicts, or None if format is unknown
    """
    # Case 1: Already a list of full replay objects
    if isinstance(data, list) and data and isinstance(data[0], dict):
        return data

    # Case 2: Single replay object (WS sends '_id', not 'id')
    if isinstance(data, dict) and ('_id' in data or 'id' in data) and ('teams' in data or 'players' in data):
        return [data]

    # Case 3: Wrapped in container (like fetch_replays response)
    if isinstance(data, dict) and 'completed' in data:
        return data['completed']

    logger.warning(f"Unknown WS message format: {type(data)}")
    return None


def get_replay_queue() -> asyncio.Queue:
    """Get the global replay queue for external access."""
    return _replay_queue


async def ws_replay_listener(callback: Callable[[List[Dict[str, Any]]], Awaitable[None]]) -> None:
    """
    Maintain persistent WebSocket connection to Spectra replay endpoint.
    Queues incoming replays for sequential processing by the callback.
    Auto-reconnects on disconnect with exponential backoff.

    Args:
        callback: Async function to call with normalized replay data
    """
    auth_value = API_KEY if API_KEY.startswith("Bearer ") else f"Bearer {API_KEY}"
    headers = {"Authorization": auth_value}
    reconnect_delay = 1

    # Start queue processor as background task
    processor_task = asyncio.create_task(_process_replay_queue(callback))

    async def _connect_and_listen(url: str, label: str):
        logger.info(f"WS attempting connect → {url}")
        async with wsconnect(url, additional_headers=headers, max_size=32 * 1024 * 1024) as ws:
            logger.info(f"WebSocket connected to {label}")
            async for message in ws:
                try:
                    data = decode_spectra_ws_payload(message)
                    replays = normalize_ws_message(data)
                    if replays:
                        await _replay_queue.put(replays)
                except SpectraPayloadError as e:
                    logger.warning(f"Invalid Spectra WS frame: {e}")
                except Exception as e:
                    logger.error(f"Error processing WS message: {e}")

    try:
        while True:
            primary_url = WS_URL
            primary_label = "Spectra"
            try:
                await _connect_and_listen(primary_url, primary_label)
            except Exception as e:
                logger.error(f"WebSocket error ({primary_label}): {e}")

            # Reconnect with exponential backoff
            logger.info(f"Reconnecting in {reconnect_delay}s...")
            await asyncio.sleep(reconnect_delay)
            reconnect_delay = min(reconnect_delay * 2, 30)  # Cap at 30s
    finally:
        processor_task.cancel()


async def _process_replay_queue(callback: Callable[[List[Dict[str, Any]]], Awaitable[None]]) -> None:
    """
    Process queued replays in batches.
    Waits for at least one message, then drains queue and processes all at once.
    """
    while True:
        try:
            # Wait for at least one message
            replays = await _replay_queue.get()
            batch = list(replays)
            _replay_queue.task_done()

            # Drain any others that accumulated while processing
            while not _replay_queue.empty():
                try:
                    more = _replay_queue.get_nowait()
                    batch.extend(more)
                    _replay_queue.task_done()
                except asyncio.QueueEmpty:
                    break

            if batch:
                try:
                    await callback(batch)
                except Exception as e:
                    logger.error(f"Error in replay callback: {e}")

        except asyncio.CancelledError:
            logger.info("Replay queue processor cancelled")
            break
        except Exception as e:
            logger.error(f"Error in queue processor: {e}")


async def fetch_leaderboard(
    count: int = 200,
    start: int = 0,
    short: bool = False
) -> Optional[List[Dict[str, Any]]]:
    """
    Fetch squadron leaderboard data from Spectra API.

    Args:
        count: Number of clans to fetch (default 200)
        start: Start position in leaderboard (default 0)
        short: If True, exclude player data from response (default False)

    Returns:
        JSON response with leaderboard data, or None on failure
    """
    url = f"{SPECTRA_API_URL}{LEADERBOARD_PATH}"
    # API_KEY may or may not include "Bearer " prefix - handle both cases
    auth_value = API_KEY if API_KEY.startswith("Bearer ") else f"Bearer {API_KEY}"
    headers = {
        "accept": "application/json",
        "count": str(count),
        "start": str(start),
        "short": str(short).lower(),
        "Authorization": auth_value
    }

    try:
        async with aiohttp.ClientSession() as session:
            async with session.post(url, headers=headers) as response:
                if response.status == 200:
                    data = await response.json()
                    return data
                else:
                    logger.error(f"Leaderboard API error: {response.status} - {await response.text()}")
    except Exception as e:
        logger.error(f"Failed to fetch leaderboard: {e}")

    return None


async def fetch_leaderboard_bulk(
    total_count: int = 1000,
    batch_size: int = 200,
    short: bool = False
) -> List[Dict[str, Any]]:
    """
    Fetch multiple batches of leaderboard data.

    Args:
        total_count: Total number of clans to fetch (default 1000)
        batch_size: Clans per request (default 200)
        short: If True, exclude player data (default False)

    Returns:
        Combined list of all clan data from all batches
    """
    all_clans = []
    num_batches = (total_count + batch_size - 1) // batch_size

    for i in range(num_batches):
        start = i * batch_size
        count = min(batch_size, total_count - start)

        data = await fetch_leaderboard(count=count, start=start, short=short)

        if data and isinstance(data, list):
            all_clans.extend(data)
        elif data:
            logger.warning(f"Unexpected response format for batch {i+1}")

    return all_clans


async def fetch_replay_by_id(
    replay_id: str,
    sort_field: str = "sqb"
) -> Optional[Dict[str, Any]]:
    """
    Fetch a single replay by ID from Spectra API.

    Args:
        replay_id: The replay ID to fetch
        sort_field: Game type sort field (default "sqb")

    Returns:
        Raw API response dict, or None on failure
    """
    url = f"{SPECTRA_API_URL}{REPLAY_SORT_PATH}"
    auth_value = API_KEY if API_KEY.startswith("Bearer ") else f"Bearer {API_KEY}"
    headers = {
        "accept": "application/json",
        "Authorization": auth_value,
        "sortField": sort_field,
        "id": replay_id,
    }

    try:
        async with aiohttp.ClientSession() as session:
            async with session.post(url, headers=headers) as response:
                if response.status == 200:
                    return await response.json()
                else:
                    logger.error(f"fetch_replay_by_id error: {response.status} - {await response.text()}")
    except Exception as e:
        logger.error(f"Failed to fetch replay {replay_id}: {e}")

    return None


async def test_leaderboard():
    """Test function to fetch leaderboard data and print results."""
    print(f"Fetching leaderboard from {SPECTRA_API_URL}...")
    print(f"API Key configured: {'Yes' if API_KEY else 'No'}")
    if API_KEY:
        # Show first/last few chars for debugging without exposing full key
        masked = f"{API_KEY[:15]}...{API_KEY[-8:]}" if len(API_KEY) > 25 else "[short key]"
        print(f"API Key format: {masked}")
    print()

    # Fetch 100 clans with player data
    print("=== Fetching 100 clans in batches of 25 ===")
    all_data = await fetch_leaderboard_bulk(total_count=100, batch_size=25, short=False)
    print(f"Total clans fetched: {len(all_data)}")

    # Write to JSON file
    output_file = "leaderboard_test_output.json"
    with open(output_file, "w") as f:
        json.dump(all_data, f, indent=2, default=str)
    print(f"Data written to {output_file}")


async def test_fetch_replay_by_id():
    """Test function to fetch a single replay by ID and print results."""
    test_id = input("Enter replay ID to fetch: ").strip()
    if not test_id:
        print("No ID provided, aborting.")
        return

    print(f"Fetching replay {test_id} from {SPECTRA_API_URL}...")
    data = await fetch_replay_by_id(test_id)
    if data:
        output_file = f"replay_{test_id}_output.json"
        with open(output_file, "w") as f:
            json.dump(data, f, indent=2, default=str)
        print(f"Data written to {output_file}")
    else:
        print("No data returned.")


if __name__ == "__main__":
    # Setup for direct execution
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).parent.parent))

    # Re-load env vars since module import was skipped
    from dotenv import load_dotenv
    load_dotenv()
    API_KEY = os.getenv("SPECTRA_API_KEY", "")

    logging.basicConfig(level=logging.INFO)

    mode = sys.argv[1] if len(sys.argv) > 1 else "replay"
    if mode == "replay":
        asyncio.run(test_fetch_replay_by_id())
