from __future__ import annotations

import logging
import os
import time
from dataclasses import dataclass

from kraken_bot.config import BotConfig, PairConfig
from kraken_bot.kraken import KrakenClient
from kraken_bot.models import Side, Signal
from kraken_bot.risk import RiskSizer
from kraken_bot.state import BotState
from kraken_bot.strategies import build_strategy

LOGGER = logging.getLogger("kraken_bot")


@dataclass(frozen=True)
class PairRunResult:
    symbol: str
    strategy: str
    signal: Signal
    price: float
    executed: bool
    message: str


class TradingBot:
    def __init__(self, config: BotConfig, client: KrakenClient | None = None) -> None:
        self.config = config
        self.client = client or KrakenClient(
            api_key=os.getenv(config.kraken.api_key_env),
            api_secret=os.getenv(config.kraken.api_secret_env),
            base_url=config.kraken.base_url,
            timeout_seconds=config.kraken.timeout_seconds,
        )
        self.risk = RiskSizer(config.risk)
        self.state = BotState(config.state_file)

    def run_once(self) -> list[PairRunResult]:
        results = []
        for pair in self.config.pairs:
            if not pair.enabled:
                continue
            try:
                results.append(self.run_pair(pair))
            except Exception as exc:
                LOGGER.exception("Failed to run %s", pair.symbol)
                results.append(
                    PairRunResult(pair.symbol, pair.strategy, Signal.hold(str(exc)), 0.0, False, str(exc))
                )
        return results

    def run_pair(self, pair: PairConfig) -> PairRunResult:
        strategy = build_strategy(pair.strategy, pair.strategy_params)
        candles = self.client.get_ohlc(pair.symbol, pair.interval, pair.candles)
        if not candles:
            return PairRunResult(pair.symbol, pair.strategy, Signal.hold("No candles returned"), 0.0, False, "No data")

        signal = strategy.generate(candles)
        price = candles[-1].close
        LOGGER.info("%s %s produced %s: %s", pair.symbol, pair.strategy, signal.side, signal.reason)
        if signal.side is Side.HOLD:
            return PairRunResult(pair.symbol, pair.strategy, signal, price, False, "Holding")

        blocked = self._trade_block_reason(pair, signal)
        if blocked:
            return PairRunResult(pair.symbol, pair.strategy, signal, price, False, blocked)

        order = self.risk.plan_order(pair, price)
        if self.config.risk.dry_run:
            message = (
                f"DRY RUN {signal.side.value} {order.base_volume} {pair.symbol} "
                f"~{order.quote_amount} {self.config.risk.quote_currency}"
            )
            return PairRunResult(pair.symbol, pair.strategy, signal, price, False, message)

        response = self.client.add_market_order(pair.symbol, signal.side.value, order.base_volume, validate=False)
        self.state.record_trade(pair.symbol, signal.side.value, signal.reason)
        return PairRunResult(pair.symbol, pair.strategy, signal, price, True, f"Submitted order: {response}")

    def _trade_block_reason(self, pair: PairConfig, signal: Signal) -> str | None:
        state = self.state.get_pair(pair.symbol)
        age = time.time() - state.last_trade_time
        if age < self.config.risk.cooldown_seconds:
            remaining = int(self.config.risk.cooldown_seconds - age)
            return f"Cooldown active for {remaining}s"
        if not self.config.risk.allow_repeated_side and state.last_side == signal.side.value:
            return f"Already last traded {signal.side.value}; waiting for opposite signal"
        return None
