# -*- mode: python; coding: utf-8 -*-
#
# Copyright (C) 2023 Benjamin Thomas Schwertfeger
# All rights reserved.
# https://github.com/btschwertfeger
#

from logging import DEBUG, INFO, WARNING, basicConfig, getLogger
from typing import Any

from click import FLOAT, INT, STRING, Context, echo, pass_context
from cloup import Choice, HelpFormatter, HelpTheme, Style, group, option, option_group
from cloup.constraints import If, accept_none, require_all


def print_version(ctx: Context, param: Any, value: Any) -> None:  # noqa: ANN401, ARG001
    """Prints the version of the package"""
    if not value or ctx.resilient_parsing:
        return
    from importlib.metadata import (  # noqa: PLC0415 # pylint: disable=import-outside-toplevel
        version,
    )

    echo(version("kraken-infinity-grid"))
    ctx.exit()


def ensure_larger_than_zero(
    ctx: Context,
    param: Any,  # noqa: ANN401
    value: Any,  # noqa: ANN401
) -> Any:  # noqa: ANN401
    """Ensure the value is larger than 0"""
    if value <= 0:
        ctx.fail(f"Value for option '{param.name}' must be larger than 0!")
    return value


def ensure_larger_equal_zero(
    ctx: Context,
    param: Any,  # noqa: ANN401
    value: Any,  # noqa: ANN401
) -> Any:  # noqa: ANN401
    """Ensure the value is larger than 0"""
    if value is not None and value < 0:
        ctx.fail(f"Value for option '{param.name}' must be larger then or equal to 0!")
    return value


@group(
    context_settings={
        "auto_envvar_prefix": "KRAKEN",
        "help_option_names": ["-h", "--help"],
    },
    formatter_settings=HelpFormatter.settings(
        theme=HelpTheme(
            invoked_command=Style(fg="bright_yellow"),
            heading=Style(fg="bright_white", bold=True),
            constraint=Style(fg="magenta"),
            col1=Style(fg="bright_yellow"),
        ),
    ),
    no_args_is_help=True,
)
@option(
    "--version",
    is_flag=True,
    callback=print_version,
    expose_value=False,
    is_eager=True,
)
@option(
    "--api-key",
    required=True,
    help="The Kraken Spot API key",
    type=STRING,
)
@option(
    "--secret-key",
    required=True,
    type=STRING,
    help="The Kraken Spot API secret key",
)
@option(
    "-v",
    "--verbose",
    count=True,
    help="Increase the verbosity of output. Use -vv for even more verbosity.",
)
@option(
    "--dry-run",
    required=False,
    is_flag=True,
    default=False,
    help="Enable dry-run mode which do not execute trades.",
)
@pass_context
def cli(ctx: Context, **kwargs: dict) -> None:
    """
    Command-line interface entry point
    """
    ctx.ensure_object(dict)
    ctx.obj |= kwargs

    verbosity = kwargs.get("verbose", 0)

    basicConfig(
        format="%(asctime)s %(levelname)8s | %(message)s",
        datefmt="%Y/%m/%d %H:%M:%S",
        level=INFO if verbosity == 0 else DEBUG,
    )

    getLogger("requests").setLevel(WARNING)
    getLogger("urllib3").setLevel(WARNING)
    getLogger("websockets").setLevel(WARNING)

    if verbosity > 1:  # type: ignore[operator]
        getLogger("requests").setLevel(DEBUG)
        getLogger("websockets").setLevel(DEBUG)
        getLogger("kraken").setLevel(DEBUG)
    else:
        getLogger("websockets").setLevel(WARNING)
        getLogger("kraken").setLevel(WARNING)


@cli.command(
    context_settings={
        "auto_envvar_prefix": "KRAKEN_RUN",
        "help_option_names": ["-h", "--help"],
    },
    formatter_settings=HelpFormatter.settings(
        theme=HelpTheme(
            invoked_command=Style(fg="bright_yellow"),
            heading=Style(fg="bright_white", bold=True),
            constraint=Style(fg="magenta"),
            col1=Style(fg="bright_yellow"),
        ),
    ),
)
@option(
    "--strategy",
    type=Choice(choices=("cDCA", "GridHODL", "GridSell", "SWING"), case_sensitive=True),
    help="The strategy to run.",
    required=True,
)
@option(
    "--name",
    required=True,
    type=STRING,
    help="""
    The name of the instance. Can be any name that is used to differentiate
    between instances of the kraken-infinity-grid.
    """,
)
@option(
    "--base-currency",
    required=True,
    type=STRING,
    help="The base currency.",
)
@option(
    "--quote-currency",
    required=True,
    type=STRING,
    help="The quote currency.",
)
@option(
    "--amount-per-grid",
    required=True,
    type=FLOAT,
    help="The quote amount to use per interval.",
)
@option(
    "--interval",
    required=True,
    type=FLOAT,
    default=0.04,
    callback=ensure_larger_than_zero,
    help="The interval between orders.",
)
@option(
    "--n-open-buy-orders",
    required=True,
    type=INT,
    default=3,
    callback=ensure_larger_than_zero,
    help="""
    The number of concurrent open buy orders e.g., ``5``. The number of
    always open buy positions specifies how many buy positions should be
    open at the same time. If the interval is defined to 2%, a number of 5
    open buy positions ensures that a rapid price drop of almost 10% that
    can be caught immediately.
    """,
)
@option(
    "--telegram-token",
    required=False,
    type=STRING,
    help="The Telegram token to use.",
)
@option(
    "--telegram-chat-id",
    required=False,
    type=STRING,
    help="The telegram chat ID to use.",
)
@option(
    "--exception-token",
    required=False,
    type=STRING,
    help="The telegram token to use for exceptions.",
)
@option(
    "--exception-chat-id",
    required=False,
    type=STRING,
    help="The telegram chat ID to use for exceptions.",
)
@option(
    "--max-investment",
    required=False,
    type=FLOAT,
    default=10e10,
    callback=ensure_larger_than_zero,
    help="""
    The maximum investment, e.g. 1000 USD that the algorithm will manage.
    """,
)
@option(
    "--userref",
    required=True,
    type=INT,
    callback=ensure_larger_than_zero,
    help="""
    A reference number to identify the algorithm's orders. This can be a
    timestamp or any integer number. Use different userref's for different
    instances!
    """,
)
@option(
    "--fee",
    type=FLOAT,
    required=False,
    callback=ensure_larger_equal_zero,
    help="""
    The fee percentage to respect, e.g. '0.0026' for 0.26 %. This value does not
    change the actual paid fee, instead it used to estimate order sizes. If not
    passed, the highest maker fee will be used.
    """,
)
@option(
    "--skip-price-check",
    is_flag=True,
    default=False,
    help="""
    Skip checking if there was a price update in the last 10 minutes. By default,
    the bot will exit if no recent price data is available. This might be useful
    for assets that aren't traded that often.
    """,
)
@option(
    "--sqlite-file",
    type=STRING,
    help="SQLite file to use as database.",
)
@option(
    "--db-name",
    type=STRING,
    default="kraken_infinity_grid",
    help="The database name.",
)
@option_group(
    "PostgreSQL Database Options",
    option(
        "--db-user",
        type=STRING,
        help="PostgreSQL DB user",
    ),
    option(
        "--db-password",
        type=STRING,
        help="PostgreSQL DB password",
    ),
    option(
        "--db-host",
        type=STRING,
        help="PostgreSQL DB host",
    ),
    option(
        "--db-port",
        type=STRING,
        help="PostgreSQL DB port",
    ),
    constraint=If(
        "sqlite_file",
        then=accept_none,
        else_=require_all,
    ),
)
@pass_context
def run(ctx: Context, **kwargs: dict) -> None:
    """Run the trading algorithm using the specified options."""
    # pylint: disable=import-outside-top-level
    import asyncio  # noqa: PLC0415

    from kraken_infinity_grid.gridbot import KrakenInfinityGridBot  # noqa: PLC0415

    db_config = {
        "sqlite_file": kwargs.pop("sqlite_file"),
        "db_user": kwargs.pop("db_user"),
        "db_password": kwargs.pop("db_password"),
        "db_host": kwargs.pop("db_host"),
        "db_port": kwargs.pop("db_port"),
        "db_name": kwargs.pop("db_name"),
    }
    ctx.obj |= kwargs

    async def main() -> None:
        gridbot = KrakenInfinityGridBot(
            key=ctx.obj.pop("api_key"),
            secret=ctx.obj.pop("secret_key"),
            dry_run=ctx.obj.pop("dry_run"),
            config=kwargs,
            db_config=db_config,
        )
        await gridbot.run()

    asyncio.run(main())
