import json
from typing import (
    Any,
    Dict,
    List,
    Optional,
)

import persistence
from systools import sys_log_exception

from .. import mb_context

from application.domain import TenderIdEnum


def get_payments_amount_by_type(
    pos_id,             # type: str
    session_id,         # type: str
    payments_amount,    # type: Dict[int, List[Dict]]
):
    # type: (...) -> None

    conn = None

    try:
        conn = persistence.Driver().open(mb_context, str(pos_id))
        _all_non_electronic_payments(
            conn,
            session_id,
            payments_amount,
        )
        _all_electronic_payments(
            conn,
            session_id,
            payments_amount,
        )
    except:  # noqa
        sys_log_exception("Error getting payments amount by type")
        raise
    finally:
        if conn:
            conn.close()


def _all_electronic_payments(
    conn,               # type: Any
    session_id,         # type: str
    payments_amount,    # type: Dict[int, List[Dict]]
):
    # type: (...) -> None

    cursor = conn.select("""
        SELECT
            ot.TenderId, 
            ot.TenderAmount - COALESCE(ot.ChangeAmount, 0) AS Amount,
            ot.TenderDetail
        FROM OrderTender ot
        INNER JOIN Orders o ON o.OrderId = ot.OrderId
        WHERE 
            o.SessionId = '{0}' 
            AND o.StateId = 5 
            AND ot.ElectronicType NOT IN (0, 9) 
            AND ot.OrderId NOT IN (
                SELECT DISTINCT ocp.OrderId 
                FROM OrderCustomProperties ocp
                INNER JOIN Orders o ON ocp.OrderId = o.OrderId
                WHERE ocp.Key = 'DELIVERY_ORDER_MANUAL_CONFIRMED' AND o.SessionId = '{0}'
            )
    """.format(session_id))

    if cursor.rows() > 0:
        for row in cursor:
            tender_id = int(row.get_entry("TenderId"))
            tender_amount = float(row.get_entry("Amount") or 0)
            tender_detail = json.loads(row.get_entry("TenderDetail") or "{}")
            payment = _find_or_insert_payment(
                payments_amount,
                tender_id,
                tender_type=_map_or_none(tender_detail.get("tenderType")),
                card_brand=tender_detail.get("Bandeira"),
            )
            payment["amount"] += tender_amount


def _all_non_electronic_payments(
    conn,               # type: Any
    session_id,         # type: str
    payments_amount,    # type: Dict[int, List[Dict]]
):
    # type: (...) -> None

    cursor = conn.select("""
        SELECT ot.TenderId, SUM(ot.TenderAmount - COALESCE(ot.ChangeAmount, 0)) AS Amount
        FROM OrderTender ot
        INNER JOIN Orders o ON o.OrderId = ot.OrderId
        WHERE 
            SessionId = '{0}' 
            AND StateId = 5 
            AND ot.ElectronicType IN (0, 9)
            AND ot.OrderId NOT IN (
                SELECT DISTINCT ocp.OrderId
                FROM OrderCustomProperties ocp
                INNER JOIN Orders o ON ocp.OrderId = o.OrderId
                WHERE ocp.Key = 'DELIVERY_ORDER_MANUAL_CONFIRMED' AND o.SessionId = '{0}'
            )
        GROUP BY ot.TenderId
    """.format(session_id))

    if cursor.rows() > 0:
        for row in cursor:
            tender_id = int(row.get_entry("TenderId"))
            tender_amount = float(row.get_entry("Amount") or 0)
            payment = _find_or_insert_payment(
                payments_amount,
                tender_id,
            )
            payment["amount"] += tender_amount


def _find_or_insert_payment(payments, tender_id, tender_type=None, card_brand=None):
    payment_list = payments.setdefault(tender_id, [])
    for payment in payment_list:
        same_tender_type = tender_type == payment.get("tenderType")
        same_card_brand = card_brand == payment.get("cardBrand")
        if same_tender_type and same_card_brand:
            return payment

    default_payment = {
        "amount": 0.0,
        "linked": False,
        "tenderType": tender_type,
        "cardBrand": card_brand,
        "tenderId": tender_id,
    }
    payment_list.append(default_payment)
    return default_payment


def _map_or_none(
    tender_type,  # type: Optional[str]
):
    # type: (...) -> Optional[str]

    if tender_type is not None:
        return _map_tender_type(str(tender_type))
    return None


def _map_tender_type(
    tender_type,  # type: str
):
    # type: (...) -> str

    types_map = {
        str(TenderIdEnum.CREDIT_CARD.value): "03",
        str(TenderIdEnum.DEBIT_CARD.value): "04",
    }
    return types_map.get(tender_type, tender_type)
