from collections import namedtuple
from datetime import datetime


def parse_datetime(datetime_str):
    if datetime_str:
        return datetime.strptime(datetime_str, '%Y-%m-%d %H:%M')


def parse_iso_datetime(datetime_str):
    if datetime_str:
        return datetime.strptime(datetime_str, '%Y-%m-%dT%H:%M:%S.%f')


def check_null(value):
    return value if value != 'null' else None


def int_or_null(value):
    value = check_null(value)
    return int(value) if value else None


def float_or_null(value):
    value = check_null(value)
    return float(value) if value else None


class Pagination(
    namedtuple('pagination', [
        'previous',
        'limit',
        'page',
        'next'
    ])
):
    @classmethod
    def create_from_json(cls, meta):
        if meta:
            return cls(
                previous=int_or_null(meta['previous']),
                limit=int(meta['limit']),
                page=int(meta['page']),
                next=int_or_null(meta['next'])
            )
        return meta


class Ticker(
    namedtuple('ticker', [
        'high',
        'low',
        'ask',
        'bid',
        'last_price',
        'volume',
        'market',
        'timestamp',
        'json',
    ])
):
    @classmethod
    def create_from_json(cls, ticker):
        ticker = ticker[0]
        return cls(
            high=float(ticker['high']),
            low=float(ticker['low']),
            ask=float(ticker['ask']),
            bid=float(ticker['bid']),
            last_price=float(ticker['last_price']),
            volume=float(ticker['volume']),
            market=ticker['market'],
            timestamp=parse_iso_datetime(ticker['timestamp']),
            json=ticker,
        )


class OrderBookEntry(
    namedtuple('book_entry', [
        'price',
        'amount',
        'timestamp',
        'json',
    ])
):
    @classmethod
    def create_from_json(cls, book_entry):
        return cls(
            price=float(book_entry['price']),
            amount=float(book_entry['amount']),
            timestamp=parse_iso_datetime(book_entry['timestamp']),
            json=book_entry,
        )


class OrderBook(
    namedtuple('order_book', [
        'order_book',
        'pagination',
    ])
):
    @classmethod
    def create_from_json(cls, order_book, pagination):
        return cls(
            order_book=[OrderBookEntry.create_from_json(book_entry)
                        for book_entry in order_book],
            pagination=Pagination.create_from_json(pagination),
        )


class TradesEntry(
    namedtuple('trades_entry', [
        'market_taker',
        'timestamp',
        'price',
        'amount',
        'market',
        'json',
    ])
):
    @classmethod
    def create_from_json(cls, trades_entry):
        return cls(
            market_taker=trades_entry['market_taker'],
            timestamp=parse_iso_datetime(trades_entry['timestamp']),
            price=float(trades_entry['price']),
            amount=float(trades_entry['amount']),
            market=trades_entry['market'],
            json=trades_entry,
        )


class Trades(
    namedtuple('trades', [
        'trades',
        'pagination',
    ])
):
    @classmethod
    def create_from_json(cls, trades, pagination):
        return cls(
            trades=[TradesEntry.create_from_json(trades_entry)
                    for trades_entry in trades],
            pagination=Pagination.create_from_json(pagination),
        )


class WalletBalance(
    namedtuple('wallet_balance', [
        'available',
        'balance',
        'wallet',
        'json',
    ])
):
    @classmethod
    def create_from_json(cls, balance):
        return cls(
            available=float(balance['available']),
            balance=float(balance['balance']),
            wallet=balance['wallet'],
            json=balance,
        )


class Balance(
    namedtuple('balance', [
        'ARS',
        'CLP',
        'ETH',
    ])
):
    @classmethod
    def create_from_json(cls, balance):
        return cls(
            ARS=cls.get_wallet_balance(balance, 'ARS'),
            CLP=cls.get_wallet_balance(balance, 'CLP'),
            ETH=cls.get_wallet_balance(balance, 'ETH'),
        )

    @staticmethod
    def get_wallet_balance(balance, wallet: str):
        wallet = [b for b in balance if b['wallet'] == wallet]
        return WalletBalance.create_from_json(wallet[0]) if wallet else None


class OrderAmount(
    namedtuple('wallet_balance', [
        'original',
        'remaining',
        'executed',
    ])
):
    @classmethod
    def create_from_json(cls, amount):
        return cls(
            original=float(amount['original']),
            remaining=float_or_null(amount.get('remaining')),
            executed=float_or_null(amount.get('executed')),
        )


class Order(
    namedtuple('order', [
        'id',
        'status',
        'type',
        'price',
        'amount',
        'execution_price',
        'avg_execution_price',
        'market',
        'created_at',
        'updated_at',
        'executed_at',
        'json',
    ])
):
    @classmethod
    def create_from_json(cls, order):
        return cls(
            # Order ID
            id=order['id'],
            # Order status, 'active' or 'executed'
            status=order['status'],
            # Order type, 'buy' or 'sell'
            type=order['type'],
            # Order limit price
            price=float(order['price']),
            # Order amount
            amount=OrderAmount.create_from_json(order['amount']),
            # Order execution price
            execution_price=float_or_null(
                order.get('execution_price')),
            # Order weighted average execution, 0 if not executed
            avg_execution_price=float_or_null(
                order.get('avg_execution_price')),
            # Market pair
            market=order['market'],
            # Order creation timestamp
            created_at=parse_iso_datetime(order['created_at']),
            # Order update timestamp. Only on active orders
            updated_at=parse_iso_datetime(order.get('created_at')),
            # Order execution timestamp. Only on executed orders
            executed_at=parse_iso_datetime(order.get('executed_at')),
            # Order JSON data
            json=order,
        )


class Orders(
    namedtuple('orders', [
        'orders',
        'pagination',
    ])
):
    @classmethod
    def create_from_json(cls, orders, pagination):
        return cls(
            orders=[Order.create_from_json(order) for order in orders],
            pagination=Pagination.create_from_json(pagination),
        )
