from typing import (
    Optional,
    List,
    Union,
    Dict,
)

from order_api.dto import SaleLine as SaleLineApi
from table_manager_api.dto import Table


class PreAccountSeatTotals(object):
    def __init__(
        self,
        sale_lines: List[SaleLineApi],
        table: Table,
        seat: Union[int, str],
        table_sub_total: float,
        table_tip: float,
        seat_quantities: int,
        table_payment: float,
    ) -> None:

        self.sale_lines = sale_lines
        self.table = table
        self.seat = seat

        self.seat_tip: Optional[int] = 0
        self.seat_total: Optional[int] = 0
        self.seat_payment: Optional[int] = 0
        self.total_seat_discount: Optional[int] = 0
        self.seat_total: Optional[int] = 0
        self.discounts_groups_sale_lines: Dict[str, float] = {}

        self._not_mapped_payment = table.promotional_tenders - sum(table.detailed_payments.values())
        self._sub_total_seats_amount = table.sub_total_amount - table_sub_total or 1
        self._table_tip_per_seat = table_tip / seat_quantities
        self._table_payment_per_seat = table_payment / seat_quantities

    def build_totals(
        self,
        seat_sub_total: float,
        seat_quantities: int,
        table_discount_per_seats: Dict[str, float],
        table_per_seat: float,
        tip_percentage: float,
    ) -> None:

        original_related_line_ids = self._get_original_related_line_ids(
            grouped_sale_lines=self.sale_lines,
        )

        self.total_seat_discount = self._get_total_seat_discount(
            table=self.table,
            original_related_line_ids=original_related_line_ids,
            seat_quantities=seat_quantities,
            table_discount_per_seats=table_discount_per_seats,
        )

        self.seat_payment = self._get_seat_payments_amount(
            table=self.table,
            seat=self.seat,
            seat_sub_total=seat_sub_total,
        )

        self.seat_tip = self._get_seat_tip_amount(
            table=self.table,
            seat=self.seat,
            seat_sub_total=seat_sub_total,
            tip_percentage=tip_percentage,
        )
        self.seat_total = seat_sub_total + table_per_seat - self.total_seat_discount + self.seat_tip - self.seat_payment

    def _get_total_seat_discount(
        self,
        table: Table,
        original_related_line_ids: List[str],
        seat_quantities: int,
        table_discount_per_seats: Dict[str, float],
    ) -> float:

        total_seat_discount = 0
        for key in table.discount_groups.keys():
            self.discounts_groups_sale_lines[key] = 0
            seat_discount = 0
            for discount_line_number in table.discount_groups[key]:
                discount_line_number = str(discount_line_number)
                if discount_line_number in original_related_line_ids:
                    seat_discount += table.discount_groups[key].get(discount_line_number, 0)

            table_discount_per_seat = table_discount_per_seats.get(key, 0) / seat_quantities
            seat_discount = round(seat_discount + table_discount_per_seat, 2)
            self.discounts_groups_sale_lines[key] = seat_discount
            total_seat_discount += seat_discount

        return total_seat_discount

    def _get_seat_payments_amount(
        self,
        table: Table,
        seat: Union[int, str],
        seat_sub_total: float,
    ) -> float:

        seat_payment = self._not_mapped_payment * (seat_sub_total / self._sub_total_seats_amount)
        seat_payment += table.detailed_payments[seat] if seat in table.detailed_payments.keys() else 0.0
        seat_payment = round(seat_payment + self._table_payment_per_seat, 2)
        return seat_payment

    def _get_seat_tip_amount(
        self,
        table: Table,
        seat: Union[int, str],
        seat_sub_total: float,
        tip_percentage: float,
    ) -> float:

        seat_tip_excluded = table.detailed_tip_excluded[seat] if seat in table.detailed_tip_excluded.keys() else 0.0
        seat_tip = (tip_percentage * (seat_sub_total - seat_tip_excluded)) / 100
        seat_tip = round(seat_tip + self._table_tip_per_seat, 2)
        return seat_tip

    @staticmethod
    def _get_original_related_line_ids(
        grouped_sale_lines: List[SaleLineApi],
    ) -> List[str]:
        original_related_line_ids: List[str] = []
        for grouped_sale_line in grouped_sale_lines:
            original_related_line_ids.extend(grouped_sale_line.original_related_line_ids or [])

        return original_related_line_ids
