"""
Command-line interface for jbqlab.

Commands
--------
- jbqlab strategies     List available strategies and their parameters
- jbqlab info           Detailed info about a specific strategy
- jbqlab validate       Validate a CSV file for backtesting
- jbqlab backtest       Run a backtest
- jbqlab optimize       Grid-search parameter optimization
- jbqlab report         Generate a markdown report
- jbqlab benchmark      Compare all strategies on the same data
- jbqlab montecarlo     Monte Carlo simulation
- jbqlab portfolio      Portfolio statistics

Strategy parameters are passed with ``--param key=value`` and can be
repeated: ``--param fast=10 --param slow=30``.
"""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Annotated

# Ensure emoji/Unicode characters render correctly on Windows terminals
try:
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    if hasattr(sys.stderr, "reconfigure"):
        sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
    pass

import typer

from jbqlab import __version__
from jbqlab.benchmark import print_benchmark_summary, run_benchmark, save_benchmark
from jbqlab.engine import run_backtest
from jbqlab.optimize import grid_search
from jbqlab.plotting import plot_equity_curve
from jbqlab.report import generate_report
from jbqlab.reporting import format_metrics_table, save_results
from jbqlab.strategies import get_available_strategies, get_strategy_info

app = typer.Typer(
    name="jbqlab",
    help="A local-first quantitative backtesting and strategy analytics toolkit.",
    add_completion=False,
    no_args_is_help=True,
)


def version_callback(value: bool) -> None:
    if value:
        typer.echo(f"jbqlab version {__version__}")
        raise typer.Exit()


@app.callback()
def main(
    version: Annotated[
        bool,
        typer.Option(
            "--version", "-v",
            callback=version_callback,
            is_eager=True,
            help="Show version and exit.",
        ),
    ] = False,
) -> None:
    """jbqlab - Quantitative backtesting toolkit."""


def _parse_strategy_params(raw: list[str] | None) -> dict[str, str]:
    """Parse --param key=value strings into a dict."""
    if not raw:
        return {}
    params: dict[str, str] = {}
    for item in raw:
        if "=" not in item:
            typer.secho(
                f"Invalid --param '{item}'. Use key=value format (e.g. --param fast=10).",
                fg=typer.colors.RED,
            )
            raise typer.Exit(code=1) from None
        key, _, val = item.partition("=")
        params[key.strip()] = val.strip()
    return params


@app.command()
def strategies() -> None:
    """List all available strategies and their parameters."""
    available = get_available_strategies()
    typer.echo("\n📊 Available Strategies\n")
    typer.echo("=" * 60)
    for strat in available:
        typer.echo(f"\n🔹 {strat.name}")
        typer.echo(f"   {strat.description}")
        if strat.params:
            typer.echo("\n   Parameters:")
            for p in strat.params:
                req = " (required)" if p.required else f" (default: {p.default})"
                typer.echo(f"     --param {p.name}=<{p.param_type.__name__}>: {p.description}{req}")
        else:
            typer.echo("   Parameters: None")
    typer.echo("\n" + "=" * 60)
    typer.echo(f"\nTotal: {len(available)} strategies available\n")


@app.command()
def info(
    strategy_name: Annotated[str, typer.Argument(help="Name of the strategy.")],
) -> None:
    """Show detailed info about a specific strategy."""
    try:
        strat_info = get_strategy_info(strategy_name)
    except ValueError as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None
    typer.echo(f"\n📊 Strategy: {strat_info.name}\n")
    typer.echo(f"Description: {strat_info.description}\n")
    if strat_info.params:
        typer.echo("Parameters:")
        for p in strat_info.params:
            req = "required" if p.required else f"default={p.default}"
            typer.echo(f"  --param {p.name}=<{p.param_type.__name__}> ({req})")
            typer.echo(f"      {p.description}")
    else:
        typer.echo("Parameters: None")
    typer.echo()


@app.command(name="validate")
def validate_data(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file to validate.")],
    ohlcv: Annotated[bool, typer.Option("--ohlcv", help="Apply OHLCV consistency checks.")] = False,
    quality: Annotated[bool, typer.Option("--quality", help="Show data quality report.")] = False,
) -> None:
    """Validate a CSV file for backtesting compatibility."""
    from jbqlab.data import load_csv
    from jbqlab.validation import check_data_quality, validate_dataframe, validate_ohlcv

    typer.echo(f"\n🔍 Validating {csv_path}\n")
    try:
        df = load_csv(csv_path)
        result = validate_ohlcv(df) if ohlcv else validate_dataframe(df)
        typer.echo(str(result))
        typer.echo("")
        if quality:
            quality_report = check_data_quality(df)
            typer.echo(quality_report.summary)
            typer.echo("")
        if result.is_valid:
            typer.secho("✅ Data is valid for backtesting!\n", fg=typer.colors.GREEN, bold=True)
        else:
            typer.secho("❌ Data has issues that need fixing.\n", fg=typer.colors.RED, bold=True)
            raise typer.Exit(code=1) from None
    except typer.Exit:
        raise
    except Exception as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


@app.command()
def backtest(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file with price data.")],
    strategy: Annotated[str, typer.Option("--strategy", "-s", help="Strategy name (see `jbqlab strategies`).")] = "buy_and_hold",
    param: Annotated[list[str] | None, typer.Option("--param", "-p", help="Strategy param as key=value. Repeat for multiple.")] = None,
    out: Annotated[Path, typer.Option("--out", "-o", help="Output directory.")] = Path("results"),
    cost: Annotated[float, typer.Option("--cost", help="Transaction cost fraction (0.001 = 0.1%).")] = 0.001,
    slippage: Annotated[float, typer.Option("--slippage", help="Slippage fraction (0.0005 = 0.05%).")] = 0.0005,
    capital: Annotated[float, typer.Option("--capital", help="Initial capital.")] = 100_000.0,
    no_plot: Annotated[bool, typer.Option("--no-plot", help="Skip the equity curve plot.")] = False,
    quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress output except errors.")] = False,
) -> None:
    """Run a backtest on price data.

    \b
    Examples:
      jbqlab backtest data.csv --strategy buy_and_hold
      jbqlab backtest data.csv --strategy sma_crossover --param fast=10 --param slow=30
      jbqlab backtest data.csv --strategy rsi --param period=14 --param oversold=30
      jbqlab backtest data.csv --strategy macd --param fast_period=12 --param slow_period=26
      jbqlab backtest data.csv --strategy bollinger_bands --param window=20 --param num_std=2.5
    """
    strategy_params = _parse_strategy_params(param)
    if not quiet:
        typer.echo(f"\n🚀 Running backtest: {strategy} on {csv_path}\n")
    try:
        result = run_backtest(
            data=csv_path,
            strategy=strategy,
            transaction_cost=cost,
            slippage=slippage,
            initial_capital=capital,
            **strategy_params,
        )
        saved_files = save_results(result, out)
        if not no_plot:
            plot_path = out / "equity_curve.png"
            plot_equity_curve(result, plot_path)
            saved_files["plot"] = plot_path
        if not quiet:
            typer.echo(format_metrics_table(result.metrics))
            typer.echo("\n📁 Saved files:")
            for file_type, path in saved_files.items():
                typer.echo(f"   {file_type}: {path}")
            typer.secho("\n✅ Backtest completed successfully!\n", fg=typer.colors.GREEN, bold=True)
    except FileNotFoundError as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None
    except ValueError as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


@app.command()
def optimize(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file with price data.")],
    strategy: Annotated[str, typer.Option("--strategy", "-s", help="Strategy to optimise.")] = "sma_crossover",
    metric: Annotated[str, typer.Option("--metric", "-m", help="Metric to maximise (sharpe, total_return, calmar…).")] = "sharpe",
    out: Annotated[Path, typer.Option("--out", "-o", help="Output directory.")] = Path("optimize_results"),
    fast_min: Annotated[int, typer.Option(help="Min fast window [sma_crossover].")] = 5,
    fast_max: Annotated[int, typer.Option(help="Max fast window [sma_crossover].")] = 20,
    fast_step: Annotated[int, typer.Option(help="Fast window step [sma_crossover].")] = 5,
    slow_min: Annotated[int, typer.Option(help="Min slow window [sma_crossover].")] = 20,
    slow_max: Annotated[int, typer.Option(help="Max slow window [sma_crossover].")] = 50,
    slow_step: Annotated[int, typer.Option(help="Slow window step [sma_crossover].")] = 10,
) -> None:
    """Run parameter optimization via grid search.

    \b
    Examples:
      jbqlab optimize data.csv --strategy sma_crossover --metric sharpe
      jbqlab optimize data.csv --strategy mean_reversion --metric calmar
    """
    typer.echo(f"\n🔍 Running parameter optimization for {strategy}\n")

    if strategy == "sma_crossover":
        param_grid = {
            "fast": list(range(fast_min, fast_max + 1, fast_step)),
            "slow": list(range(slow_min, slow_max + 1, slow_step)),
        }
    elif strategy == "mean_reversion":
        param_grid = {"window": [10, 15, 20, 25, 30], "z_entry": [-1.5, -2.0, -2.5, -3.0]}
    elif strategy == "rsi":
        param_grid = {"period": [9, 14, 21], "oversold": [20, 25, 30], "overbought": [65, 70, 75, 80]}
    elif strategy == "bollinger_bands":
        param_grid = {"window": [10, 15, 20, 25], "num_std": [1.5, 2.0, 2.5]}
    elif strategy == "momentum":
        param_grid = {"lookback": [10, 15, 20, 30, 60], "threshold": [0.0, 0.01, 0.02]}
    else:
        typer.secho(
            f"Built-in grids: sma_crossover, mean_reversion, rsi, bollinger_bands, momentum.\n"
            f"For '{strategy}' use the Python SDK: grid_search(data, strategy='{strategy}', param_grid={{...}})",
            fg=typer.colors.YELLOW,
        )
        raise typer.Exit(code=1) from None

    try:
        result = grid_search(data=csv_path, strategy=strategy, param_grid=param_grid, optimize_metric=metric, verbose=True)
        out.mkdir(parents=True, exist_ok=True)
        result.summary_df.to_csv(out / "optimization_results.csv", index=False)
        best_val = result.summary_df[result.optimize_metric].max()
        typer.echo(f"\n🏆 Best parameters: {result.best_params}")
        typer.echo(f"   Best {metric}: {best_val:.4f}")
        typer.echo(f"\n📁 Full results saved to {out / 'optimization_results.csv'}")
        typer.secho("\n✅ Optimization completed!\n", fg=typer.colors.GREEN, bold=True)
    except Exception as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


@app.command()
def report(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file with price data.")],
    strategy: Annotated[str, typer.Option("--strategy", "-s", help="Strategy name.")] = "buy_and_hold",
    param: Annotated[list[str] | None, typer.Option("--param", "-p", help="Strategy param as key=value.")] = None,
    out: Annotated[Path, typer.Option("--out", "-o", help="Output directory.")] = Path("report"),
    title: Annotated[str, typer.Option("--title", "-t", help="Report title.")] = "Backtest Report",
) -> None:
    """Generate a full markdown report from a backtest."""
    strategy_params = _parse_strategy_params(param)
    typer.echo(f"\n📝 Generating report for {strategy} on {csv_path}\n")
    try:
        result = run_backtest(csv_path, strategy=strategy, **strategy_params)
        out.mkdir(parents=True, exist_ok=True)
        plot_equity_curve(result, out / "equity_curve.png")
        generate_report(result, out / "report.md", title=title)
        save_results(result, out)
        typer.echo(f"📁 Files saved to {out}/")
        typer.echo("   - report.md  - equity_curve.png  - metrics.json  - equity_curve.csv  - positions.csv")
        typer.secho("\n✅ Report generated!\n", fg=typer.colors.GREEN, bold=True)
    except Exception as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


@app.command()
def benchmark(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file with price data.")],
    out: Annotated[Path, typer.Option("--out", "-o", help="Output directory.")] = Path("benchmark_results"),
    no_plots: Annotated[bool, typer.Option("--no-plots", help="Skip equity curve plots.")] = False,
) -> None:
    """Compare all strategies on the same dataset (ranked by Sharpe ratio)."""
    typer.echo(f"\n📊 Running benchmark on {csv_path}\n")
    try:
        results = run_benchmark(csv_path)
        print_benchmark_summary(results)
        save_benchmark(results, out, generate_plots=not no_plots)
        typer.echo(f"\n📁 Full results saved to {out}/")
        typer.secho("\n✅ Benchmark completed!\n", fg=typer.colors.GREEN, bold=True)
    except Exception as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


@app.command(name="montecarlo")
def monte_carlo_cli(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file with price data.")],
    strategy: Annotated[str, typer.Option("--strategy", "-s", help="Strategy to use.")] = "buy_and_hold",
    param: Annotated[list[str] | None, typer.Option("--param", "-p", help="Strategy param as key=value.")] = None,
    simulations: Annotated[int, typer.Option("--simulations", "-n", help="Number of Monte Carlo paths.")] = 10000,
    horizon: Annotated[int, typer.Option("--horizon", "-h", help="Simulation horizon (trading days).")] = 252,
    seed: Annotated[int | None, typer.Option("--seed", help="Random seed for reproducibility.")] = None,
    out: Annotated[Path, typer.Option("--out", "-o", help="Output directory.")] = Path("montecarlo_results"),
) -> None:
    """Run a Monte Carlo simulation on a strategy's return distribution."""
    from jbqlab.montecarlo import monte_carlo_simulation

    strategy_params = _parse_strategy_params(param)
    typer.echo(f"\n🎲 Monte Carlo: {simulations:,} paths x {horizon} days  ({strategy})\n")
    try:
        result = run_backtest(csv_path, strategy=strategy, **strategy_params)
        returns = result.equity_curve.pct_change().dropna()
        mc_result = monte_carlo_simulation(
            returns=returns, n_simulations=simulations, time_horizon=horizon,
            random_seed=seed, save_paths=False,
        )
        typer.echo(mc_result.summary())
        out.mkdir(parents=True, exist_ok=True)
        with open(out / "montecarlo_summary.txt", "w") as f:
            f.write(mc_result.summary())
        stats = {
            "n_simulations": mc_result.n_simulations,
            "time_horizon": mc_result.time_horizon,
            "mean_return": mc_result.mean_final,
            "median_return": mc_result.median_final,
            "std_return": mc_result.std_final,
            "percentile_5": mc_result.percentile_5,
            "percentile_95": mc_result.percentile_95,
            "probability_of_loss": mc_result.prob_loss,
            "mean_max_drawdown": mc_result.max_drawdown_mean,
        }
        with open(out / "montecarlo_stats.json", "w") as f:
            json.dump(stats, f, indent=2)
        typer.echo(f"\n📁 Results saved to {out}/")
        typer.secho("\n✅ Monte Carlo simulation completed!\n", fg=typer.colors.GREEN, bold=True)
    except Exception as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


@app.command(name="portfolio")
def portfolio_cli(
    csv_path: Annotated[Path, typer.Argument(help="Path to the CSV file with price data.")],
    strategy: Annotated[str, typer.Option("--strategy", "-s", help="Strategy to use.")] = "buy_and_hold",
    param: Annotated[list[str] | None, typer.Option("--param", "-p", help="Strategy param as key=value.")] = None,
) -> None:
    """Show detailed portfolio and risk statistics for a strategy."""
    from jbqlab.portfolio import calculate_portfolio_stats

    strategy_params = _parse_strategy_params(param)
    typer.echo(f"\n📊 Portfolio Analysis: {strategy}\n")
    try:
        result = run_backtest(csv_path, strategy=strategy, **strategy_params)
        stats = calculate_portfolio_stats(result.equity_curve)
        typer.echo("=" * 44)
        typer.echo(" Returns")
        typer.echo("-" * 44)
        typer.echo(f"  Total Return      {stats.total_return:>10.2%}")
        typer.echo(f"  CAGR              {stats.cagr:>10.2%}")
        typer.echo(f"  Volatility        {stats.volatility:>10.2%}")
        typer.echo(f"  Sharpe Ratio      {stats.sharpe_ratio:>10.2f}")
        typer.echo(f"  Sortino Ratio     {stats.sortino_ratio:>10.2f}")
        typer.echo(f"  Max Drawdown      {stats.max_drawdown:>10.2%}")
        typer.echo("")
        typer.echo(" Risk")
        typer.echo("-" * 44)
        typer.echo(f"  VaR (95%)         {stats.var_95:>10.2%}")
        typer.echo(f"  CVaR (95%)        {stats.cvar_95:>10.2%}")
        typer.echo(f"  Skewness          {stats.skewness:>10.2f}")
        typer.echo(f"  Kurtosis          {stats.kurtosis:>10.2f}")
        typer.echo("")
        typer.echo(" Trades")
        typer.echo("-" * 44)
        typer.echo(f"  Win Rate          {stats.win_rate:>10.2%}")
        typer.echo(f"  Profit Factor     {stats.profit_factor:>10.2f}")
        typer.echo(f"  Avg Win           {stats.avg_win:>10.2%}")
        typer.echo(f"  Avg Loss          {stats.avg_loss:>10.2%}")
        typer.echo(f"  Best Day          {stats.best_day:>10.2%}")
        typer.echo(f"  Worst Day         {stats.worst_day:>10.2%}")
        typer.echo("=" * 44)
        typer.secho("\n✅ Analysis complete!\n", fg=typer.colors.GREEN, bold=True)
    except Exception as e:
        typer.secho(f"\n❌ Error: {e}\n", fg=typer.colors.RED)
        raise typer.Exit(code=1) from None


if __name__ == "__main__":
    app()
