# config.py
import logging
import os
from pathlib import Path
from typing import Set, List, Optional, Tuple, Dict, Any

import torch
from dotenv import load_dotenv

logger = logging.getLogger(__name__)

# === .env loader — wrapped in function to avoid module-load side-effects ===

_env_loaded = False

def load_env() -> bool:
    """Load .env file. Safe to call multiple times — returns True on first load."""
    global _env_loaded
    if _env_loaded:
        return True
    env_path = Path('.') / '.env'
    if env_path.exists():
        load_dotenv(dotenv_path=env_path)
        logger.info(f"Configuration loaded from {env_path.resolve()}")
    else:
        logger.warning(f".env not found at {env_path.resolve()}, using defaults.")
    _env_loaded = True
    return True


# === DB Configuration (password always from env, never default) ===

load_env()  # one-time call at module load

DB_CONFIG: Dict[str, Any] = {
    'host': os.getenv("DB_HOST", "nlbotinterface.ru"),
    'port': int(os.getenv("DB_PORT", "3306")),
    'database': os.getenv("DB_NAME", "bitcoin_tickers"),
    'user': os.getenv("DB_USER", "bitcoin"),
    'password': os.environ.get("DB_PASSWORD"),
    'charset': 'utf8mb4',
}


# === Hyperparameters ===

SEQ_LEN: int = 15
BASE_HORIZON: int = 100
MIN_HORIZON: int = 30
MAX_HORIZON: int = 200
BATCH_SIZE: int = 256
EPOCHS: int = 30
LR: float = 1e-4
PATIENCE: int = 15
ENSEMBLE_SEEDS: List[int] = [42, 123, 456]
DEVICE: str = "cuda" if torch.cuda.is_available() else "cpu"

# === Architecture ===

USE_CONV: bool = True
USE_ATTENTION: bool = True
USE_CONTEXT: bool = True
HIDDEN_DIM: int = 48
CONTEXT_CAPACITY: int = 128
CONTEXT_HIDDEN_DIM: int = 32

# === Calibration ===

CALIB_USE_TEMP: bool = True
CALIB_USE_PLATT: bool = False
CALIB_USE_ISOTONIC: bool = False

# === Ensemble ===

ENSEMBLE_WEIGHT_BY_AUC: bool = True

# === Risk Management ===

USE_KELLY: bool = True
USE_DYNAMIC_SIZING: bool = True
KELLY_FRACTION: float = 0.25
MAX_RISK_PER_TRADE: float = 0.02

# === Uncertainty thresholds ===

UNC_CONF_THRESHOLD: float = 0.04
UNC_MARGIN_MIN: float = 0.06
UNC_MARGIN_MAX: float = 0.14
UNC_MARGIN_MEDIUM: float = 0.20

# === Temperature bounds ===

TEMP_BOUNDS: Tuple[float, float] = (0.5, 2.0)
DRIFT_THRESHOLD: float = 3.2

# === Path constants (use absolute paths from project root) ===

_PROJECT_ROOT = Path(__file__).resolve().parent
MODELS_DIR: Path = _PROJECT_ROOT / "models"
MONITOR_CSV: Path = _PROJECT_ROOT / "predictions_monitor.csv"
PLOTS_DIR: Path = _PROJECT_ROOT / "plots"
MLFLOW_DB: Path = _PROJECT_ROOT / "mlflow.db"
MLFLOW_EXP_NAME: str = "TPSL_LSTM_DUAL"

# === Validated whitelist ===

VALID_INSTRUMENTS: Set[str] = {
    "BITCOIN", "BITCOINC", "EURUSD", "ASTR", "GAZP", "LKOH",
    "MTSS", "NVTK", "PHOR", "PLZL", "ROSN", "SBER", "SNGSP",
    "VTBR", "X5",
}
VALID_TIMEFRAMES: Set[str] = {"H1", "D1", "W1"}

# === Feature engineering defaults ===

DEFAULT_WINDOW: int = 20
DEFAULT_RR_RATIO: float = 3.0
DEFAULT_SLIPPAGE_PCT: float = 0.0005
DEFAULT_SL_MODE: str = "atr_from_close"
DEFAULT_HOLD_ON_TIMEOUT: bool = True
DEFAULT_WINDOW: int = 20

# === Backtest defaults ===

DEFAULT_PROB_THRESHOLDS: List[float] = [0.0, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60]
DEFAULT_MIN_PROB_DIFF: float = 0.00
DEFAULT_MIN_TRAIN_SAMPLES: int = 500
DEFAULT_BACKTEST_STEP: int = 10
DEFAULT_MAX_HOLDS: int = 200
DEFAULT_MAX_ZOOM: int = 20  # progress bar blocks
DEFAULT_PROB_THRESHOLD: float = 0.55  # default probability threshold for decision
DEFAULT_DROPOUT: float = 0.35  # default dropout rate
DEFAULT_NUM_LAYERS: int = 2  # default LSTM layers
DEFAULT_KERNEL_SIZE: int = 3  # default Conv1D kernel size
DEFAULT_NUM_HEADS: int = 2  # default attention heads

# === Training defaults (used by walk_forward and ensemble) ===

TRAIN_LR: float = 1e-5      # lower learning rate for walk-forward
TRAIN_BATCH_SIZE: int = 64
TRAIN_WEIGHT_DECAY: float = 1e-4
TRAIN_GRAD_CLIP: float = 1.0
TRAIN_LR_FACTOR: float = 0.5
TRAIN_LR_PATIENCE: int = 3
VAL_MIN_SAMPLES: int = 10
TRAIN_MIN_SAMPLES: int = 30
