#!/usr/bin/env python3
"""kis_trader/backtest/optuna_dart.py — DART Optuna TPE."""
from __future__ import annotations

import logging
from datetime import datetime
from typing import Any, Dict, Optional

import optuna
from optuna.samplers import TPESampler

from database import TradeDB
from kis_trader.backtest.optuna_common import (
    announce_optuna_json_path,
    resolve_optuna_storage_url,
)
from kis_trader.backtest.param_search_dart import (
    _dart_grids,
    apply_params_to_db,
    evaluate_dart_param_combo,
)
from kis_trader.engine import dart_engine as de

logger = logging.getLogger("param_search_optuna")
_FAIL = -1e18


def prepare_dart_search_context(start: str, end: str, mode: str) -> Optional[Dict[str, Any]]:
    grids = _dart_grids()
    if mode not in grids:
        logger.error("DART mode: %s (fast/coarse/fine)", mode)
        return None
    db = TradeDB()
    try:
        env_row = db.get_merged_env_snapshot() or {}
    finally:
        db.close()
    return {
        "start": start,
        "end": end,
        "mode": mode,
        "grid": grids[mode],
        "env_row": env_row,
        "base": de.get_dart_defaults_from_db(env_row=env_row),
    }


def run_dart_optuna(
    ctx: Dict[str, Any],
    *,
    n_trials: int,
    storage_url: Optional[str] = None,
    study_name: Optional[str] = None,
    min_trades: int = 1,
    sampler_name: str = "tpe",
    seed: int = 42,
    show_progress: bool = False,
    **_kwargs: Any,
) -> optuna.Study:
    grid: Dict[str, Any] = ctx["grid"]
    storage = storage_url or resolve_optuna_storage_url()
    name = study_name or "dart_%s_%s" % (
        ctx["mode"], datetime.now().strftime("%Y%m%d_%H%M%S"),
    )
    sampler = TPESampler(seed=seed) if sampler_name != "random" else optuna.samplers.RandomSampler(seed=seed)
    study = optuna.create_study(
        study_name=name,
        storage=storage,
        direction="maximize",
        sampler=sampler,
        load_if_exists=True,
    )

    def objective(trial: optuna.Trial) -> float:
        params = dict(ctx["base"])
        for axis, values in grid.items():
            params[axis] = trial.suggest_categorical(axis, list(values))
        res = evaluate_dart_param_combo(
            params, start=ctx["start"], end=ctx["end"], env_row=ctx["env_row"],
        )
        n = int(res.get("trade_count") or 0)
        pnl = float(res.get("total_pnl") or 0)
        trial.set_user_attr("trade_count", n)
        trial.set_user_attr("total_pnl", pnl)
        trial.set_user_attr("win_rate", float(res.get("win_rate") or 0))
        if n < min_trades:
            return _FAIL
        return pnl

    study.optimize(objective, n_trials=n_trials, show_progress_bar=show_progress)
    announce_optuna_json_path("dart", study)
    return study


def apply_best_dart_trial(study: optuna.Study) -> bool:
    try:
        best = study.best_trial
    except Exception:
        return False
    if float(best.value or _FAIL) <= 0:
        logger.warning("DART best PnL<=0 — DB 미적용")
        return False
    apply_params_to_db(dict(best.params))
    return True
