# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Entropy Markets](https://entropy.trade/) support is available at:
#   [Docs](https://docs.entropy.trade/)
#   [Discord](https://discord.gg/67jySBhxrg)
#   [Twitter](https://twitter.com/entropymarkets)
#   [Github](https://github.com/blockworks-foundation)
#   [Email](mailto:hello@blockworks.foundation)

import argparse
import entropy
import typing

from decimal import Decimal

from .element import Element
from ...modelstate import ModelState


DEFAULT_SPREAD_RATIO = Decimal("0.01")
DEFAULT_POSITION_SIZE_RATIO = Decimal("0.01")


# # 🥭 RatiosElement class
#
# Ignores any input `Order`s (so probably best at the head of the chain). Builds orders using a spread
# ratio and a position size ratio.
#
class RatiosElement(Element):
    def __init__(
        self,
        order_type: entropy.OrderType,
        expire_seconds: typing.Optional[Decimal],
        match_limit: int,
        spread_ratios: typing.Sequence[Decimal],
        position_size_ratios: typing.Sequence[Decimal],
        from_bid_ask: bool,
    ) -> None:
        super().__init__()
        self.order_type: entropy.OrderType = order_type
        self.expire_seconds: typing.Optional[Decimal] = expire_seconds
        self.match_limit: int = match_limit
        self.spread_ratios: typing.Sequence[Decimal] = spread_ratios
        self.position_size_ratios: typing.Sequence[Decimal] = position_size_ratios
        self.from_bid_ask: bool = from_bid_ask

        if len(self.spread_ratios) == 0:
            raise Exception(
                "No spread ratios specified. Try the --ratios-spread parameter?"
            )

        if len(self.position_size_ratios) == 0:
            raise Exception(
                "No position-size ratios specified. Try the --ratios-position-size parameter?"
            )

        if len(self.spread_ratios) != len(self.position_size_ratios):
            raise Exception(
                "List of spread ratios and position size ratios must be the same length."
            )

    @staticmethod
    def add_command_line_parameters(parser: argparse.ArgumentParser) -> None:
        parser.add_argument(
            "--ratios-spread",
            type=Decimal,
            action="append",
            help="ratio to apply to the mid-price to create the BUY and SELL price (can be specified multiple times but every occurrance must have a matching --position-size-ratio occurrance)",
        )
        parser.add_argument(
            "--ratios-position-size",
            type=Decimal,
            action="append",
            help="ratio to apply to the available collateral to create the position size (can be specified multiple times but every occurrance must have a matching --spread-ratio occurrance)",
        )
        parser.add_argument(
            "--ratios-from-bid-ask",
            action="store_true",
            default=False,
            help="calculate ratios from bid or ask, not mid price (default: False, which will use the mid price)",
        )

    @staticmethod
    def from_command_line_parameters(args: argparse.Namespace) -> "RatiosElement":
        order_type: entropy.OrderType = args.order_type
        expire_seconds: typing.Optional[Decimal] = args.expire_seconds
        match_limit: int = args.match_limit or entropy.Order.DefaultMatchLimit
        spread_ratios: typing.Sequence[Decimal] = args.ratios_spread or [
            DEFAULT_SPREAD_RATIO
        ]
        position_size_ratios: typing.Sequence[Decimal] = args.ratios_position_size or [
            DEFAULT_POSITION_SIZE_RATIO
        ]
        from_bid_ask: bool = args.ratios_from_bid_ask
        return RatiosElement(
            order_type,
            expire_seconds,
            match_limit,
            spread_ratios,
            position_size_ratios,
            from_bid_ask,
        )

    def process(
        self,
        context: entropy.Context,
        model_state: ModelState,
        orders: typing.Sequence[entropy.Order],
    ) -> typing.Sequence[entropy.Order]:
        price: entropy.Price = model_state.price
        expiration = entropy.Order.build_absolute_expiration(self.expire_seconds)
        new_orders: typing.List[entropy.Order] = []
        for counter in range(len(self.spread_ratios)):
            position_size_ratio = self.position_size_ratios[counter]
            quote_value_to_risk = (
                model_state.inventory.available_collateral.value * position_size_ratio
            )
            base_position_size = quote_value_to_risk / price.mid_price

            spread_ratio = self.spread_ratios[counter]
            bid_price_base: Decimal = price.mid_price
            ask_price_base: Decimal = price.mid_price
            if self.from_bid_ask:
                bid_price_base = price.top_bid
                ask_price_base = price.top_ask

            bid: Decimal = bid_price_base - (bid_price_base * spread_ratio)
            ask: Decimal = ask_price_base + (ask_price_base * spread_ratio)

            bid_order = entropy.Order.from_values(
                entropy.Side.BUY,
                price=bid,
                quantity=base_position_size,
                order_type=self.order_type,
                expiration=expiration,
                match_limit=self.match_limit,
            )
            ask_order = entropy.Order.from_values(
                entropy.Side.SELL,
                price=ask,
                quantity=base_position_size,
                order_type=self.order_type,
                expiration=expiration,
                match_limit=self.match_limit,
            )
            self._logger.debug(
                f"""Desired orders:
    Bid: {bid_order}
    Ask: {ask_order}"""
            )
            new_orders += [bid_order, ask_order]

        return new_orders

    def __str__(self) -> str:
        spread_ratios = ", ".join(map(str, self.spread_ratios)) or "None"
        position_size_ratios = ", ".join(map(str, self.position_size_ratios)) or "None"
        from_description = "from bid/ask" if self.from_bid_ask else "from mid price"
        return f"« RatiosElement using ratios - spread(s): {spread_ratios} {from_description}, position size(s): {position_size_ratios} »"
