"""Visualization module for creating charts with natural language prompts."""

from __future__ import annotations

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

import httpx

from finsdk._config import get_api_key, get_api_url
from finsdk.models import ChartResult, ChartMetadata

if TYPE_CHECKING:
    import pandas as pd


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


def visualize(
    data: DataInput,
    prompt: str,
    *,
    title: str | None = None,
    theme: str = "clean",
    interactive: bool = False,
) -> ChartResult:
    """
    Create a visualization from data and a natural language prompt.

    This is the main entry point for creating charts. Describe what you want
    in plain English and the API will create an appropriate visualization.

    Args:
        data: Input data - DataFrame, dict, list of dicts, or path to CSV/JSON.
        prompt: Natural language description of the visualization.
                Examples:
                - "Show trends over time"
                - "Line chart with date on x-axis, revenue on y-axis"
                - "col_a is revenue in USD, col_b is region. Bar chart by region."
        title: Optional chart title.
        theme: Chart theme ("clean", "corporate", "executive", "midnight", "dark", "light").
        interactive: If True, prompt for clarification when column meanings are unclear.
                     Default is False (safe for non-interactive environments).

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

    Example:
        >>> import finsdk
        >>> df = finsdk.stock_data.fetch("AAPL", period="1y")
        >>> chart = finsdk.visualize(df, "Stock price over time")
        >>> print(chart.url)
        https://savvy-mcp.com/c/abc123
    """
    api_key = get_api_key()
    api_url = get_api_url()

    # Format data into records
    records, column_descriptions = _format_data(data)

    # Check for unclear columns and optionally prompt
    if interactive:
        unclear = _needs_clarification(records, column_descriptions)
        if unclear:
            user_descriptions = _prompt_for_descriptions(unclear)
            column_descriptions.update(user_descriptions)

    # Build request payload
    payload = {
        "data": records,
        "prompt": prompt,
        "column_descriptions": column_descriptions,
        "include_metadata": False,
    }

    if title:
        payload["title"] = title
    if theme:
        payload["theme"] = theme

    # Make API request
    with httpx.Client(timeout=60.0) as client:
        response = client.post(
            f"{api_url}/v1/visualize",
            json=payload,
            headers={"X-API-Key": api_key},
        )
        response.raise_for_status()
        result = response.json()

    return ChartResult.from_response(result)


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,
    theme: str = "clean",
    width: int = 900,
    height: int = 600,
    trendline: bool = False,
    **kwargs: Any,
) -> ChartResult:
    """
    Create a chart with explicit parameters (for more control).

    Args:
        chart_type: Type of chart ("line", "bar", "scatter", "area", "pie", etc.).
        data: Optional DataFrame or data records.
        x: X-axis values (use if not providing data).
        y: Y-axis values (use if not providing data).
        labels: Series labels for legend.
        title: Chart title.
        x_axis_title: X-axis label.
        y_axis_title: Y-axis label.
        theme: Chart theme.
        width: Chart width in pixels.
        height: Chart height in pixels.
        trendline: Add linear trendline (for scatter/line charts).
        **kwargs: Additional chart options passed to the API.

    Returns:
        ChartResult with chart URLs.

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

    # 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
    data_id = None
    if data is not None:
        records, _ = _format_data(data)
        if records:
            # Use direct data if small enough
            if len(records) <= 1000:
                # Convert to series format
                cols = list(records[0].keys())
                x_col = cols[0]  # Assume first column is x
                y_cols = cols[1:]  # Rest are y series

                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": theme,
        "width": width,
        "height": height,
    }

    if series:
        tool_args["series"] = series
    if data_id:
        tool_args["data_id"] = data_id
    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
    tool_args.update(kwargs)

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

    # Parse response to get chart info
    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:
        >>> import finsdk
        >>> metadata = finsdk.get_chart_metadata("abc12345")
        >>> print(metadata.title)
        "Revenue by Region"
    """
    from finsdk._client import call_tool

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

    # Parse the response
    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 _format_data(data: DataInput) -> tuple[list[dict[str, Any]], dict[str, str]]:
    """Format input data into records with column metadata."""
    records = _to_records(data)
    records = _clean_records(records)
    column_descriptions = _infer_column_descriptions(records)
    return records, column_descriptions


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 _infer_column_descriptions(records: list[dict[str, Any]]) -> dict[str, str]:
    """Infer column descriptions from data."""
    if not records:
        return {}

    descriptions = {}
    sample = records[0]

    for col, value in sample.items():
        if isinstance(value, (int, float)):
            descriptions[col] = "numeric"
        elif isinstance(value, str):
            if _looks_like_date(value):
                descriptions[col] = "date"
            else:
                descriptions[col] = "text"
        elif isinstance(value, bool):
            descriptions[col] = "boolean"
        else:
            descriptions[col] = "unknown"

    return descriptions


def _looks_like_date(value: str) -> bool:
    """Check if a string looks like a date."""
    patterns = [
        r"\d{4}-\d{2}-\d{2}",  # 2024-01-15
        r"\d{2}/\d{2}/\d{4}",  # 01/15/2024
        r"\d{2}-\d{2}-\d{4}",  # 15-01-2024
        r"Q[1-4]\s*\d{4}",  # Q1 2024
        r"\d{4}",  # 2024
    ]
    for pattern in patterns:
        if re.match(pattern, value):
            return True
    return False


def _needs_clarification(
    records: list[dict[str, Any]], descriptions: dict[str, str]
) -> list[str]:
    """Check if any columns need clarification."""
    return [col for col, desc in descriptions.items() if desc == "unknown"]


def _prompt_for_descriptions(unclear_columns: list[str]) -> dict[str, str]:
    """Interactively prompt for column descriptions."""
    descriptions = {}
    print("\nSome columns need clarification:")
    for col in unclear_columns:
        desc = input(f"  What does '{col}' represent? ").strip()
        if desc:
            descriptions[col] = desc
    return descriptions


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

    # Parse URLs from markdown
    chart_id = ""
    url = ""
    png = ""
    svg = ""
    python_url = ""
    bundle = ""

    # Extract chart ID and URLs from markdown format
    # Example: **Chart ID:** `abc123`
    id_match = re.search(r"Chart ID[:\s]*`?([a-f0-9]+)`?", text)
    if id_match:
        chart_id = id_match.group(1)

    # Extract URLs
    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,
    )
