#!/usr/bin/env python

import asyncio
import getopt
import os
import pprint
import sys
import traceback
import uuid
import toml
import pandas as pd
import importlib.util
from datetime import datetime, timedelta, date
from typing import List, Dict, Optional
import pygit2
import pytz
from requests.exceptions import HTTPError
import parsedatetime.parsedatetime as pdt

from liualgotrader.common import config, market_data, trading_data
from liualgotrader.common.database import create_db_connection
from liualgotrader.common.decorators import timeit
from liualgotrader.common.tlog import tlog
from liualgotrader.common.data_loader import TimeScale
from liualgotrader.fincalcs.vwap import add_daily_vwap
from liualgotrader.models.algo_run import AlgoRun
from liualgotrader.models.new_trades import NewTrade
from liualgotrader.models.trending_tickers import TrendingTickers
from liualgotrader.strategies.base import Strategy, StrategyType
from liualgotrader.scanners.base import Scanner
from liualgotrader.scanners.momentum import Momentum
from liualgotrader import backtester
from liualgotrader import enhanced_backtest


def dateFromString(s: str) -> date:
    c = pdt.Calendar()
    result, what = c.parse(s)

    dt = None

    # what was returned (see http://code-bear.com/code/parsedatetime/docs/)
    # 0 = failed to parse
    # 1 = date (with current time, as a struct_time)
    # 2 = time (with current date, as a struct_time)
    # 3 = datetime
    if what in (1, 2):
        # result is struct_time
        dt = datetime(*result[:6]).date()
    elif what == 3:
        # result is a datetime
        dt = result.date()

    if dt is None:
        raise ValueError("Don't understand date '" + s + "'")

    return dt


if __name__ == "__main__":
    try:
        config.build_label = pygit2.Repository("../").describe(
            describe_strategy=pygit2.GIT_DESCRIBE_TAGS
        )
    except pygit2.GitError:
        import liualgotrader

        config.build_label = liualgotrader.__version__ if hasattr(liualgotrader, "__version__") else ""  # type: ignore

    if len(sys.argv) == 1:
        backtester.show_usage()
        sys.exit(0)

    config.filename = os.path.basename(__file__)

    folder = (
        config.tradeplan_folder
        if config.tradeplan_folder[-1] == "/"
        else f"{config.tradeplan_folder}/"
    )
    fname = f"{folder}{config.configuration_filename}"
    try:
        conf_dict = toml.load(fname)
        tlog(f"loaded configuration file from {fname}")
    except FileNotFoundError:
        tlog(f"[ERROR] could not locate tradeplan file {fname}")
        sys.exit(0)
    conf_dict = toml.load(config.configuration_filename)
    config.portfolio_value = conf_dict.get("portfolio_value", None)
    if "risk" in conf_dict:
        config.risk = conf_dict["risk"]

    if sys.argv[1] == "batch":
        try:
            strict = False
            opts, args = getopt.getopt(
                sys.argv[2:],
                "b:d:s",
                ["batch-list", "debug=", "strict", "symbol=", "duration="],
            )
            debug_symbols = []
            symbols = None
            duration: int = None
            for opt, arg in opts:
                if opt in ("--batch-list", "-b"):
                    backtester.get_batch_list()
                    break
                elif opt in ("--debug", "-d"):
                    debug_symbols.append(arg)
                elif opt in ("--strict", "-s"):
                    strict = True
                elif opt in ("--symbol"):
                    if not symbols:
                        symbols = [arg]
                    else:
                        symbols.append(arg)
                elif opt in ("--duration"):
                    try:
                        duration = int(arg)
                    except ValueError:
                        print(
                            f"Error, duration parameters must be positive and not {duration}"
                        )
                        sys.exit(0)
                    if duration <= 0:
                        print(
                            f"Error, duration parameters must be positive and not {duration}"
                        )
                        sys.exit(0)

            for arg in args:
                backtester.backtest(
                    arg, conf_dict, debug_symbols, strict, symbols, duration
                )
        except getopt.GetoptError as e:
            print(f"Error parsing options:{e}\n")
            backtester.show_usage()
            sys.exit(0)
    elif sys.argv[1] == "from":
        from_date = dateFromString(sys.argv[2])

        try:
            scanners: Optional[List] = None
            strategies: Optional[List] = None
            to_date = date.today()
            scale = TimeScale["day"]
            opts, args = getopt.getopt(
                sys.argv[3:],
                shortopts=[],
                longopts=[
                    "to=",
                    "scale=",
                    "scanners=",
                    "strats=",
                ],
            )
            for opt, arg in opts:
                if opt in ("--scanners"):
                    scanners = arg.split(",")
                elif opt in ("--strats"):
                    strategies = arg.split(",")
                elif opt in ("--to"):
                    to_date = dateFromString(arg)
                elif opt in ("--scale"):
                    if arg in ("day", "minute"):
                        scale = TimeScale[arg]
                    else:
                        print(
                            f"ERROR: wrong `scale` parameter {arg} passed: not implemented yet"
                        )
                        sys.exit(0)
        except getopt.GetoptError as e:
            print(f"Error parsing options:{e}\n")
            backtester.show_usage()
            sys.exit(0)

        enhanced_backtest.backtest(
            from_date=from_date,
            to_date=to_date,
            scale=scale,
            config=conf_dict,
            scanners=scanners,
            strategies=strategies,
        )
    else:
        print(f"Error parsing {sys.argv[1:]}\n")
        backtester.show_usage()
        sys.exit(0)

    sys.exit(0)
