import datetime
import typing
import time

import pandas as pd

from .binance_api import get_binance_klines
from .coinbase_api import get_product_candles
from .db_helpers import update_klines_to_db

supported_exchanges = ["binanceus", "binancecom", "coinbase"]


def update_kline(
    symbol,
    exchange,
    start_date: typing.Optional[datetime.datetime] = None,
    end_date: typing.Optional[datetime.datetime] = None,
    progress_callback: typing.Optional[typing.Callable[[dict], None]] = None,
):
    if exchange not in supported_exchanges:
        raise ValueError(f"Exchange {exchange} not supported")

    if start_date is None:
        # 30 days ago
        start_date = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
            days=30
        )
    if end_date is None:
        # now
        end_date = datetime.datetime.now(datetime.timezone.utc)

    start_date = start_date.replace(tzinfo=datetime.timezone.utc)
    end_date = end_date.replace(tzinfo=datetime.timezone.utc)
    now = datetime.datetime.now(datetime.timezone.utc)

    if end_date > now:
        end_date = now.replace(second=0, microsecond=0)

    klines = pd.DataFrame()
    curr_date = start_date

    def status_update(status_obj):
        if progress_callback:
            progress_callback(status_obj)

    window_delta = end_date - curr_date
    max_windows = 10
    max_retries = 5

    last_exc = None
    for window_idx in range(max_windows):
        window_start = curr_date + (window_delta * window_idx)
        window_end = end_date + (window_delta * window_idx)

        for attempt in range(1, max_retries + 1):
            try:
                if exchange == "binanceus":
                    klines, status_obj = get_binance_klines(
                        symbol,
                        window_start,
                        window_end,
                        "us",
                        status_update,
                        store_func=update_klines_to_db,
                    )
                elif exchange == "binancecom":
                    klines, status_obj = get_binance_klines(
                        symbol,
                        window_start,
                        window_end,
                        "com",
                        status_update,
                        store_func=update_klines_to_db,
                    )
                elif exchange == "coinbase":
                    klines, status_obj = get_product_candles(
                        symbol, window_start, window_end, status_update, store_func=update_klines_to_db
                    )
                else:
                    raise ValueError(f"Exchange {exchange} not supported")

                db_path = update_klines_to_db(klines, symbol, exchange)
                return db_path
            except Exception as exc:
                last_exc = exc
                # exponential backoff with cap
                sleep_for = min(2 ** attempt, 30)
                time.sleep(sleep_for)

    if last_exc:
        raise last_exc
    raise Exception(f"Download failed for {symbol}")


if __name__ == "__main__":
    # symbol = "BTCUSDT"
    # exchange = "binanceus"
    symbol = "BTC-USD"
    exchange = "coinbase"
    start_date = datetime.datetime(2024, 1, 1)
    end_date = datetime.datetime(2024, 1, 14)
    update_kline(symbol, exchange, start_date, end_date)
