"""Visualization module for creating charts.

The API is inspired by Plotly Express for familiarity, but with a simpler interface.

SUPPORTED PARAMETERS (use these):
    - data, x, y, color, title, template/theme, width, height

NOT SUPPORTED (do NOT use these - they will be ignored or error):
    - facet_row, facet_col (no subplots)
    - animation_frame (no animations)
    - hover_name, hover_data, custom_data (no hover customization)
    - line_dash, line_shape, markers, symbol (no line styling)
    - log_x, log_y, range_x, range_y (no axis customization)
    - color_discrete_sequence, color_discrete_map (no color mapping)
    - labels dict (use x_axis_title/y_axis_title instead)
    - orientation, category_orders, error_x, error_y
"""

from __future__ import annotations

import json
import math
import re
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    import pandas as pd

from finsdk.models import ChartResult, ChartMetadata


# Type alias for data input
DataInput = Any  # DataFrame, dict, list[dict], or file path


def visualize(
    data: DataInput,
    *,
    x: str | None = None,
    y: str | list[str] | None = None,
    color: str | None = None,
    title: str | None = None,
    template: str | None = None,
    theme: str | None = None,  # Alias for template
    x_axis_title: str | None = None,
    y_axis_title: str | None = None,
    width: int = 900,
    height: int = 600,
    chart_type: str = "line",
    trendline: bool = False,
    **kwargs: Any,
) -> ChartResult:
    """
    Create a chart from data. API inspired by Plotly Express.

    SUPPORTED PARAMETERS:
        data: DataFrame, dict, list of dicts, or path to CSV/JSON
        x: Column name for x-axis (default: first column)
        y: Column name(s) for y-axis (default: numeric columns)
        color: Column name to group by (creates separate series per unique value)
        title: Chart title
        template: Theme name ("clean", "executive", "midnight", "corporate", "dark")
        theme: Alias for template
        width: Chart width in pixels (default: 900)
        height: Chart height in pixels (default: 600)
        chart_type: "line", "bar", "scatter", "area", "pie" (default: "line")
        trendline: Add linear trendline (default: False)

    NOT SUPPORTED (will be ignored):
        facet_row, facet_col, animation_frame, hover_data, line_dash,
        markers, log_x, log_y, color_discrete_sequence, labels dict

    Returns:
        ChartResult with url, png, svg, python URLs

    Examples:
        # Simple line chart
        >>> chart = finsdk.visualize(df, x="date", y="price", title="Stock Price")

        # Multiple y columns
        >>> chart = finsdk.visualize(df, x="date", y=["open", "close"])

        # Group by category (like px.line with color parameter)
        >>> chart = finsdk.visualize(df, x="date", y="price", color="symbol")

        # Bar chart with theme
        >>> chart = finsdk.visualize(df, x="category", y="sales", chart_type="bar", template="executive")
    """
    from finsdk._client import call_tool

    # Handle template/theme (template takes precedence, matching Plotly naming)
    effective_theme = template or theme or "clean"

    # Format data into records
    records = _to_records(data)
    records = _clean_records(records)

    if not records:
        raise ValueError("No data to visualize")

    # Determine x and y columns
    columns = list(records[0].keys())
    x_col = x or columns[0]

    if y is None:
        # Use all numeric columns except x and color as y series
        exclude = {x_col}
        if color:
            exclude.add(color)
        y_cols = [c for c in columns if c not in exclude and _is_numeric_column(records, c)]
        if not y_cols:
            y_cols = [c for c in columns if c not in exclude][:1]
    elif isinstance(y, str):
        y_cols = [y]
    else:
        y_cols = list(y)

    # Build series based on whether color grouping is used
    series = []

    if color:
        # Group by color column (like px.line with color parameter)
        # Get unique values for the color column
        color_values = sorted(set(r.get(color) for r in records if r.get(color) is not None))

        for color_val in color_values:
            # Filter records for this color value
            filtered = [r for r in records if r.get(color) == color_val]
            x_values = [r.get(x_col) for r in filtered]

            for y_col in y_cols:
                # Series name includes color value if multiple y columns
                if len(y_cols) > 1:
                    name = f"{color_val} - {y_col}"
                else:
                    name = str(color_val)

                series.append({
                    "name": name,
                    "x": x_values,
                    "y": [r.get(y_col) for r in filtered],
                })
    else:
        # No grouping - one series per y column
        x_values = [r.get(x_col) for r in records]
        for y_col in y_cols:
            series.append({
                "name": y_col,
                "x": x_values,
                "y": [r.get(y_col) for r in records],
            })

    # Build tool arguments
    tool_args: dict[str, Any] = {
        "chart_type": chart_type,
        "series": series,
        "theme": effective_theme,
        "width": width,
        "height": height,
        "refine": False,  # CRITICAL: Disable auto-refinement to avoid LLM calls
    }

    if title:
        tool_args["title"] = title
    if x_axis_title:
        tool_args["x_axis_title"] = x_axis_title
    if y_axis_title:
        tool_args["y_axis_title"] = y_axis_title
    if trendline:
        tool_args["trendline"] = trendline

    # Add any extra kwargs (but filter out unsupported Plotly params)
    unsupported = {
        "facet_row", "facet_col", "facet_col_wrap", "facet_row_spacing", "facet_col_spacing",
        "animation_frame", "animation_group",
        "hover_name", "hover_data", "custom_data",
        "line_dash", "line_shape", "markers", "symbol", "line_group",
        "log_x", "log_y", "range_x", "range_y",
        "color_discrete_sequence", "color_discrete_map",
        "line_dash_sequence", "line_dash_map", "symbol_sequence", "symbol_map",
        "labels", "orientation", "category_orders",
        "error_x", "error_x_minus", "error_y", "error_y_minus",
        "text", "render_mode", "subtitle",
    }
    for key, value in kwargs.items():
        if key not in unsupported:
            tool_args[key] = value

    response = call_tool("create_visualization", **tool_args)

    # Parse response to get chart info
    return _parse_chart_response(response)


def create_chart(
    chart_type: str,
    *,
    data: DataInput | None = None,
    x: list | None = None,
    y: list | None = None,
    labels: list[str] | None = None,
    title: str | None = None,
    x_axis_title: str | None = None,
    y_axis_title: str | None = None,
    template: str | None = None,
    theme: str | None = None,
    width: int = 900,
    height: int = 600,
    trendline: bool = False,
    **kwargs: Any,
) -> ChartResult:
    """
    Create a chart with explicit x/y arrays (lower-level API).

    Use this when you have raw arrays instead of a DataFrame.

    Args:
        chart_type: "line", "bar", "scatter", "area", "pie"
        data: Optional DataFrame (alternative to x/y arrays)
        x: X-axis values as list
        y: Y-axis values as list
        labels: Series labels for legend
        title: Chart title
        x_axis_title: X-axis label
        y_axis_title: Y-axis label
        template: Theme name (or use theme alias)
        theme: Alias for template
        width: Chart width in pixels
        height: Chart height in pixels
        trendline: Add linear trendline

    Returns:
        ChartResult with chart URLs

    Example:
        >>> chart = finsdk.create_chart(
        ...     "bar",
        ...     x=["Q1", "Q2", "Q3", "Q4"],
        ...     y=[100, 150, 120, 180],
        ...     title="Quarterly Revenue",
        ... )
    """
    from finsdk._client import call_tool

    effective_theme = template or theme or "clean"

    # Build series from x/y if provided directly
    series = None
    if x is not None and y is not None:
        series = [{"name": labels[0] if labels else "Series 1", "x": x, "y": y}]

    # Or convert DataFrame to series
    if data is not None:
        records = _to_records(data)
        records = _clean_records(records)
        if records:
            if len(records) <= 1000:
                cols = list(records[0].keys())
                x_col = cols[0]
                y_cols = cols[1:]

                series = []
                x_values = [r[x_col] for r in records]
                for i, y_col in enumerate(y_cols):
                    name = labels[i] if labels and i < len(labels) else y_col
                    series.append({
                        "name": name,
                        "x": x_values,
                        "y": [r.get(y_col) for r in records],
                    })

    # Build tool arguments
    tool_args: dict[str, Any] = {
        "chart_type": chart_type,
        "theme": effective_theme,
        "width": width,
        "height": height,
        "refine": False,
    }

    if series:
        tool_args["series"] = series
    if title:
        tool_args["title"] = title
    if x_axis_title:
        tool_args["x_axis_title"] = x_axis_title
    if y_axis_title:
        tool_args["y_axis_title"] = y_axis_title
    if trendline:
        tool_args["trendline"] = trendline

    tool_args.update(kwargs)

    response = call_tool("create_visualization", **tool_args)
    return _parse_chart_response(response)


def get_chart_metadata(
    chart_id: str,
    *,
    include_details: bool = False,
) -> ChartMetadata:
    """
    Get metadata for a chart including sources and lineage.

    Args:
        chart_id: The chart ID (8-char hex, e.g., "abc12345")
        include_details: Include chart details (title, type, theme)

    Returns:
        ChartMetadata with title, sources, lineage, etc.

    Example:
        >>> metadata = finsdk.get_chart_metadata("abc12345")
        >>> print(metadata.title)
    """
    from finsdk._client import call_tool

    response = call_tool(
        "get_metadata",
        object_id=chart_id,
        object_type="chart",
        format="json",
        include_details=include_details,
    )

    text = ""
    if "content" in response:
        for item in response.get("content", []):
            if item.get("type") == "text":
                text = item.get("text", "")
                break

    try:
        result = json.loads(text) if text else {}
    except json.JSONDecodeError:
        result = {}

    details = result.get("details", {})

    return ChartMetadata.from_dict({
        "chart_id": result.get("object_id", chart_id),
        "title": details.get("title"),
        "chart_type": details.get("chart_type"),
        "description": details.get("description"),
        "sources": result.get("sources", []),
        "lineage": result.get("lineage", []),
        "created_at": result.get("created_at"),
    })


# =============================================================================
# Data formatting helpers
# =============================================================================


def _to_records(data: DataInput) -> list[dict[str, Any]]:
    """Convert various data formats to list of dicts."""
    # DataFrame
    if hasattr(data, "to_dict"):
        return data.to_dict(orient="records")

    # CSV/JSON file path
    if isinstance(data, str):
        if data.endswith(".csv"):
            import pandas as pd

            df = pd.read_csv(data)
            return df.to_dict(orient="records")
        elif data.endswith(".json"):
            with open(data) as f:
                loaded = json.load(f)
                if isinstance(loaded, list):
                    return loaded
                return [loaded]
        else:
            raise ValueError(f"Unsupported file format: {data}")

    # List of dicts
    if isinstance(data, list):
        return data

    # Single dict - check if column-oriented
    if isinstance(data, dict):
        if all(isinstance(v, list) for v in data.values()):
            import pandas as pd

            df = pd.DataFrame(data)
            return df.to_dict(orient="records")
        return [data]

    raise ValueError(f"Unsupported data type: {type(data)}")


def _clean_records(records: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Clean and normalize records."""
    if not records:
        return records

    cleaned = []
    for record in records:
        clean_record = {}
        for key, value in record.items():
            clean_key = str(key).strip().replace(" ", "_")
            if _is_nan(value):
                clean_record[clean_key] = None
            else:
                clean_record[clean_key] = value
        cleaned.append(clean_record)

    return cleaned


def _is_nan(value: Any) -> bool:
    """Check if value is NaN."""
    if value is None:
        return True
    try:
        return math.isnan(value)
    except (TypeError, ValueError):
        return False


def _is_numeric_column(records: list[dict[str, Any]], col: str) -> bool:
    """Check if a column contains numeric values."""
    for record in records[:10]:  # Check first 10 rows
        val = record.get(col)
        if val is not None and not _is_nan(val):
            return isinstance(val, (int, float))
    return False


def _parse_chart_response(response: dict[str, Any]) -> ChartResult:
    """Parse a chart response from the API."""
    text = ""
    if "content" in response:
        for item in response.get("content", []):
            if item.get("type") == "text":
                text = item.get("text", "")
                break

    chart_id = ""
    url = ""
    png = ""
    svg = ""
    python_url = ""
    bundle = ""

    id_match = re.search(r"Chart ID.*?`([a-f0-9]+)`", text)
    if id_match:
        chart_id = id_match.group(1)

    url_match = re.search(r"\[View Chart\]\(([^)]+)\)", text)
    if url_match:
        url = url_match.group(1)

    png_match = re.search(r"\[PNG\]\(([^)]+)\)", text)
    if png_match:
        png = png_match.group(1)

    svg_match = re.search(r"\[SVG\]\(([^)]+)\)", text)
    if svg_match:
        svg = svg_match.group(1)

    py_match = re.search(r"\[Python\]\(([^)]+)\)", text)
    if py_match:
        python_url = py_match.group(1)

    bundle_match = re.search(r"\[Bundle\]\(([^)]+)\)", text)
    if bundle_match:
        bundle = bundle_match.group(1)

    return ChartResult(
        id=chart_id,
        url=url,
        png=png,
        svg=svg,
        python=python_url,
        bundle=bundle,
    )
