"""Output formatting utilities for Purrio CLI."""

import csv
import json
from collections.abc import Callable
from datetime import datetime
from io import StringIO
from typing import Any

from rich.console import Console
from rich.table import Table

from .config import OutputFormat
from .types import (
    AssetPosition,
    DisplayValue,
    Fill,
    FundingData,
    OpenOrder,
)


class OutputFormatter:
    """Handle output formatting for different formats."""

    def __init__(self, format: OutputFormat = OutputFormat.TABLE):
        """
        Initialize the output formatter.

        Args:
            format: Output format to use
        """
        self.format = format
        self.console = Console()
        self.error_console = Console(stderr=True)

    def print(
        self,
        data: Any,
        headers: list[str] | None = None,
        title: str | None = None,
    ) -> None:
        """
        Print data in the configured format.

        Args:
            data: Data to print (dict, list, or primitive)
            headers: Optional headers for table/CSV output
            title: Optional title for table output
        """
        if self.format == OutputFormat.JSON:
            self._print_json(data)
        elif self.format == OutputFormat.CSV:
            self._print_csv(data, headers)
        else:
            self._print_table(data, headers, title)

    def _print_json(self, data: Any) -> None:
        """Print data as JSON."""
        if isinstance(data, dict | list):
            output = json.dumps(data, indent=2, default=str)
        else:
            output = json.dumps({"result": data}, indent=2, default=str)
        self.console.print(output)

    def _print_csv(self, data: Any, headers: list[str] | None = None) -> None:
        """Print data as CSV."""
        output = StringIO()
        writer = csv.writer(output)

        if isinstance(data, dict):
            if headers:
                writer.writerow(headers)
                writer.writerow([data.get(h, "") for h in headers])
            else:
                writer.writerow(data.keys())
                writer.writerow(data.values())
        elif isinstance(data, list):
            if data:
                if isinstance(data[0], dict):
                    if not headers:
                        headers = list(data[0].keys())
                    writer.writerow(headers)
                    for row in data:
                        writer.writerow([row.get(h, "") for h in headers])
                else:
                    for row in data:
                        if isinstance(row, list | tuple):
                            writer.writerow(row)
                        else:
                            writer.writerow([row])
        else:
            writer.writerow(["value"])
            writer.writerow([data])

        self.console.print(output.getvalue().rstrip())

    def _print_table(
        self,
        data: Any,
        headers: list[str] | None = None,
        title: str | None = None,
    ) -> None:
        """Print data as a Rich table."""
        table = Table(title=title, show_header=True, header_style="bold magenta")

        if isinstance(data, dict):
            if headers:
                for header in headers:
                    if any(term in header.lower() for term in ["price", "value", "pnl", "fee"]):
                        table.add_column(header, style="green", justify="right")
                    elif any(term in header.lower() for term in ["coin", "side"]):
                        table.add_column(header, style="cyan", justify="center")
                    else:
                        table.add_column(header, style="white")
                table.add_row(*[str(data.get(h, "")) for h in headers])
            else:
                table.add_column("Key", style="cyan")
                table.add_column("Value", style="green", justify="right")
                for key, value in data.items():
                    table.add_row(str(key), str(value))
        elif isinstance(data, list) and data:
            if isinstance(data[0], dict):
                if not headers:
                    headers = list(data[0].keys())

                for header in headers:
                    if any(term in header.lower() for term in ["price", "value", "pnl", "fee", "size"]):
                        table.add_column(header, style="green", justify="right")
                    elif any(term in header.lower() for term in ["coin", "side", "order id"]):
                        table.add_column(header, style="cyan", justify="center")
                    elif "time" in header.lower():
                        table.add_column(header, style="yellow")
                    else:
                        table.add_column(header, style="white")

                for row in data:
                    table.add_row(*[str(row.get(h, "")) for h in headers])
            else:
                table.add_column("Value", style="cyan")
                for item in data:
                    table.add_row(str(item))
        else:
            if data:
                table.add_column("Result", style="cyan")
                table.add_row(str(data))
            else:
                table.add_column("Message", style="yellow")
                table.add_row("No data available")

        self.console.print(table)

    def print_error(self, message: str) -> None:
        """
        Print an error message.

        Args:
            message: Error message to print
        """
        self.error_console.print(f"[red]Error:[/red] {message}")

    def print_success(self, message: str) -> None:
        """
        Print a success message.

        Args:
            message: Success message to print
        """
        self.console.print(f"[green]Success:[/green] {message}")

    def print_warning(self, message: str) -> None:
        """
        Print a warning message.

        Args:
            message: Warning message to print
        """
        self.console.print(f"[yellow]Warning:[/yellow] {message}")


def format_positions(
    positions: list[AssetPosition], mark_prices: dict[str, float] | None = None
) -> list[dict[str, Any]]:
    """
    Format position data for display.

    Args:
        positions: Raw position data from API
        mark_prices: Optional dict of coin to mark price

    Returns:
        Formatted position data
    """
    formatted = []
    for pos in positions:
        position = pos.get("position", {})
        coin = position.get("coin", "N/A")

        szi = position.get("szi", 0)
        try:
            size_value = float(szi)
            side = "LONG" if size_value > 0 else "SHORT" if size_value < 0 else "N/A"
            size_abs = abs(size_value)
        except (ValueError, TypeError):
            side = "N/A"
            size_abs = 0
        entry_price_str = _format_price_string(position.get("entryPx"))

        mark_px = None
        if mark_prices and coin in mark_prices:
            mark_px = mark_prices[coin]
        if not mark_px:
            mark_px_value = position.get("markPx")
            if mark_px_value is not None:
                try:
                    mark_px = float(str(mark_px_value))
                except (ValueError, TypeError):
                    mark_px = None
            else:
                mark_px = None
        mark_price_str = _format_price_string(mark_px)

        liquidation_price_str = _format_price_string(position.get("liquidationPx"))

        unrealized_pnl = position.get("unrealizedPnl", 0)
        try:
            pnl_value = float(unrealized_pnl)
            pnl_str = (
                f"+${_format_number(pnl_value, 2)}" if pnl_value >= 0 else f"-${_format_number(abs(pnl_value), 2)}"
            )
        except (ValueError, TypeError):
            pnl_str = "N/A"

        roe = position.get("returnOnEquity", 0)
        try:
            roe_value = float(roe) * 100 if roe else 0
            roe_str = f"+{roe_value:.2f}%" if roe_value >= 0 else f"{roe_value:.2f}%"
        except (ValueError, TypeError):
            roe_str = "N/A"

        formatted_pos = {
            "Coin": coin,
            "Side": side,
            "Size": _format_number(size_abs, 6),
            "Entry Price": entry_price_str,
            "Mark Price": mark_price_str,
            "Liq Price": liquidation_price_str,
            "Position Value": f"${_format_number(position.get('positionValue', 0), 2)}",
            "Unrealized PnL": pnl_str,
            "ROE": roe_str,
            "Leverage": f"{_format_number(position.get('leverage', {}).get('value', 0), 1)}x",
        }
        formatted.append(formatted_pos)
    return formatted


def format_orders(orders: list[OpenOrder], coin_resolver: Callable[[str], str] | None = None) -> list[dict[str, Any]]:
    """
    Format order data for display.

    Args:
        orders: Raw order data from API
        coin_resolver: Optional function to resolve coin IDs to names

    Returns:
        Formatted order data
    """
    formatted = []
    for order in orders:
        coin = order.get("coin", "N/A")
        if coin.startswith("@") and coin_resolver:
            coin = coin_resolver(coin)

        formatted_order = {
            "Order ID": order.get("oid", "N/A"),
            "Coin": coin,
            "Side": "Buy" if order.get("side", "") == "B" else "Sell",
            "Size": _format_number(order.get("sz", 0), 6),
            "Price": f"${_format_number(order.get('limitPx', 0), 2)}",
            "Status": order.get("orderStatus", "Unknown"),
            "Time": _format_timestamp(order.get("timestamp")),
        }
        formatted.append(formatted_order)
    return formatted


def format_fills(fills: list[Fill], coin_resolver: Callable[[str], str] | None = None) -> list[dict[str, Any]]:
    """
    Format fill data for display.

    Args:
        fills: Raw fill data from API

    Returns:
        Formatted fill data
    """
    formatted = []
    for fill in fills:
        coin = fill.get("coin", "N/A")
        if coin.startswith("@") and coin_resolver:
            coin = coin_resolver(coin)

        formatted_fill = {
            "Time": _format_timestamp(fill.get("time")),
            "Coin": coin,
            "Side": "Buy" if fill.get("side", "") == "B" else "Sell",
            "Size": _format_number(fill.get("sz", 0), 6),
            "Price": f"${_format_number(fill.get('px', 0), 2)}",
            "Fee": f"${_format_number(fill.get('fee', 0), 4)}",
            "Realized PnL": f"${_format_number(fill.get('closedPnl', 0), 2)}",
        }
        formatted.append(formatted_fill)
    return formatted


def format_orderbook(orderbook: dict[str, Any], depth: int = 10) -> dict[str, list[dict[str, Any]]]:
    """
    Format orderbook data for display.

    Args:
        orderbook: Raw orderbook data from API
        depth: Number of levels to show

    Returns:
        Formatted orderbook data
    """
    levels = orderbook.get("levels", [])
    if not levels or len(levels) < 2:
        return {"bids": [], "asks": []}

    bids = []
    asks = []

    for i, bid in enumerate(levels[0][:depth]):
        price = float(bid.get("px", 0))
        size = float(bid.get("sz", 0))
        total_value = price * size
        bids.append(
            {
                "Level": i + 1,
                "Price": f"${_format_number(price, 2)}",
                "Size": _format_number(size, 6),
                "Total": f"${_format_number(total_value, 2)}",
            }
        )

    for i, ask in enumerate(levels[1][:depth]):
        price = float(ask.get("px", 0))
        size = float(ask.get("sz", 0))
        total_value = price * size
        asks.append(
            {
                "Level": i + 1,
                "Price": f"${_format_number(price, 2)}",
                "Size": _format_number(size, 6),
                "Total": f"${_format_number(total_value, 2)}",
            }
        )

    return {"bids": bids, "asks": asks}


def format_funding(funding: list[FundingData]) -> list[dict[str, Any]]:
    """
    Format funding rate data for display.

    Args:
        funding: Raw funding rate data from API

    Returns:
        Formatted funding rate data
    """
    formatted = []
    for entry in funding:
        formatted_entry = {
            "Time": _format_timestamp(entry.get("time")),
            "Coin": entry.get("coin", "N/A"),
            "Funding Rate": _format_percentage(entry.get("fundingRate", 0), 4),
            "Premium": _format_percentage(entry.get("premium", 0), 4),
        }
        formatted.append(formatted_entry)
    return formatted


def _format_number(value: DisplayValue, decimals: int) -> str:
    """
    Format a number for display.

    Args:
        value: Number to format
        decimals: Number of decimal places

    Returns:
        Formatted number string
    """
    if value is None:
        return "N/A"
    try:
        num = float(value)
        if abs(num) < 1e-10:
            return "0"
        formatted = f"{num:,.{decimals}f}".rstrip("0").rstrip(".")
        return formatted if formatted != "" else "0"
    except (ValueError, TypeError):
        return str(value)


def _format_timestamp(timestamp: DisplayValue) -> str:
    """
    Format timestamp for display.

    Args:
        timestamp: Timestamp in milliseconds or string

    Returns:
        Formatted datetime string
    """
    if timestamp is None:
        return "N/A"
    try:
        if isinstance(timestamp, str):
            timestamp = int(timestamp)
        dt = datetime.fromtimestamp(timestamp / 1000)
        return dt.strftime("%Y-%m-%d %H:%M:%S")
    except (ValueError, TypeError, OSError):
        return str(timestamp)


def _format_percentage(value: DisplayValue, decimals: int = 2) -> str:
    """
    Format percentage for display.

    Args:
        value: Value to format as percentage
        decimals: Number of decimal places

    Returns:
        Formatted percentage string
    """
    if value is None:
        return "N/A"
    try:
        num = float(value) * 100
        return f"{num:,.{decimals}f}%"
    except (ValueError, TypeError):
        return str(value)


def _format_price_string(price_value: DisplayValue) -> str:
    """
    Format price value with dynamic decimal precision.

    Args:
        price_value: Price value to format

    Returns:
        Formatted price string with $ prefix
    """
    if not price_value:
        return "N/A"
    try:
        price_float = float(price_value)
        decimals = 6 if price_float < 1 else 4 if price_float < 100 else 2
        return f"${_format_number(price_value, decimals)}"
    except (ValueError, TypeError):
        return "N/A"
