# encoding: utf-8

from typing import List

from old_helper import convert_from_utf_to_localtime
from helper import remove_accents, format_cpf_cnpj
from systools import sys_log_exception
from typing import (
    List,
    Any,
    Dict,
)

from basereport import (
    TableReport,
    ReportColumnDefinition,
    SimpleReport,
    AlignTypes,
)
from report import Presenter, Part
from report.command import NewLineCommand, AlignCommand, RepeatCommand
from utils.report_formatters import set_font, rollback_printer_default_font
from commons.util import LogoImage
from dto import DeliveryBodyDto


class DeliveryPresenter(Presenter):
    def __init__(self, columns):
        self.columns = columns

        self.header_column_definition = [
            ReportColumnDefinition(width=8, after_fill_text=': ', fill_with_char='.'),
            ReportColumnDefinition(width=columns - 8)
        ]

        self.info_value_column_definition = [
            ReportColumnDefinition(width=columns - 8),
            ReportColumnDefinition(width=8)
        ]

        self.delivery_info_column_definition = [
            ReportColumnDefinition(width=13, after_fill_text=': ', fill_with_char='.'),
            ReportColumnDefinition(width=columns - 13)
        ]

        self.header_table_column_definitions = [
            ReportColumnDefinition(
                width=4,
            ),
            ReportColumnDefinition(
                width=columns - 13,
            ),
            ReportColumnDefinition(
                width=9,
                before_text=" ",
                align=AlignTypes.RIGHT,
            ),
        ]

        self.observation_column_definition = [
            ReportColumnDefinition(width=13, after_fill_text=': ', fill_with_char='.'),
            ReportColumnDefinition(width=columns - 13)
        ]

        self.full_line_column_definitions = [
            ReportColumnDefinition(width=columns),
            ReportColumnDefinition(width=0)
        ]

    def present(self, dto):
        try:
            parts = set_font("B")
            parts.extend(self._build_header(dto.header))
            parts.extend(self._build_delivery_information(
                body=dto.body,
                show_customer_phone=dto.show_customer_phone,
            ))
            parts.extend(self._build_items(dto.body, dto.products, dto.cfh_products))
            parts.extend(self._build_order_values(dto.body))
            parts.extend(self._build_payments(dto.body))
            parts.extend(self._build_observation(dto.body))
            parts.extend(self._build_logo())
            parts.extend([Part(rollback_printer_default_font(), [NewLineCommand()])])
        except Exception as _:
            sys_log_exception("Error building Delivery Report")
            raise

        return SimpleReport(parts, self.columns)

    def _build_header(self, header):
        parts = [
            Part(None, [NewLineCommand()]),
            self._get_store_name(header),
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()])
        ]
        parts.extend(self._get_order_number(header))
        parts.extend(self._get_order_date(header))

        return parts

    def _build_items(self, body, products, cfh_products):
        items = [
            Part("$ITEMS_DELIVERY_REPORT", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part(None, [RepeatCommand(self.columns, "#"), NewLineCommand()]),
            TableReport([["$QTY_DELIVERY_REPORT", "$DESCRIPTION_DELIVERY_REPORT", "$VALUE_DELIVERY_REPORT"]],
                        self.header_table_column_definitions),
            Part(None, [RepeatCommand(self.columns, "#"), NewLineCommand()])
        ]

        for item in body.items:
            items.extend(
                    self._build_item(
                        item=item,
                        products=products,
                        cfh_products=cfh_products,
                        level=0,
                    )
            )

        items.append(Part(None, [RepeatCommand(self.columns, "-"), NewLineCommand()]))

        return items

    def _build_item(
        self,
        item,               # type: Dict[Any]
        products,           # type: Dict[Any]
        cfh_products,       # type: Dict[Any]
        level=0,            # type: int
    ):
        # type: (...) -> List[TableReport]

        items = []

        qty = int(item["quantity"])
        item_quantity = unicode(qty)
        item_part_code = unicode(item["partCode"])
        product_name = self._get_product_name(item, products, item_part_code)
        product_type = products[item_part_code][1] if item_part_code in products else ''
        item_name = remove_accents(product_name)
        item_price = float(item["price"])
        item_formatted_price = self._format_price(item_price) if item_price > 0 else ''
        item_line = [item_quantity, item_name, item_formatted_price]
        item_type = item["itemType"] if "itemType" in item else product_type

        increment_level = 0
        if (item_price > 0 or item_type != "OPTION") and item_part_code not in cfh_products:
            increment_level = 1
            column_definition = self._get_column_definition(
                level=level,
                item_formatted_price=item_formatted_price,
            )

            items.append(TableReport([item_line], column_definition))

            if "observation" in item and item["observation"] is not None and item["observation"] != "":
                item_observation = "   " + item[u"observation"]
                items.extend(self._format_line('', item_observation, ''))

        if "parts" in item:
            for sub_item in item["parts"]:
                items.extend(
                    self._build_item(
                        item=sub_item,
                        products=products,
                        cfh_products=cfh_products,
                        level=level+increment_level,
                    )
                )

        return items

    def _get_column_definition(
        self,
        level,                  # type: int
        item_formatted_price,   # type: str
    ):
        # type: (...) -> List[ReportColumnDefinition]

        before_text = (" " * (level * 2)) if level > 0 else ""
        before_text_column_width = (len(before_text) + 4)
        after_text_column_width = 9 if item_formatted_price != '' else 0

        return [
            ReportColumnDefinition(
                width=before_text_column_width,
                before_text=before_text,
            ),
            ReportColumnDefinition(
                width=self.columns - (before_text_column_width + after_text_column_width),
                before_text=" ",
            ),
            ReportColumnDefinition(
                width=after_text_column_width,
                before_text=" ",
            ),
        ]

    @staticmethod
    def _get_product_name(item, products, item_part_code):
        product_name = item.get("name", None)
        if not product_name:
            product_name = products[item_part_code][0] if item_part_code in products else item_part_code
        return product_name

    def _build_order_values(self, body):
        values = []

        item_line = ["$TOTAL_ITEMS_DELIVERY_REPORT",  self._format_price(float(body.sub_total_price))]
        values.append(TableReport([item_line], self.info_value_column_definition))

        delivery_fee = float(body.delivery_fee)
        if delivery_fee > 0:
            item_line = ["$DELIVERY_FEE_DELIVERY_REPORT", self._format_price(float(delivery_fee))]
            values.append(TableReport([item_line], self.info_value_column_definition))

        additional_fee = float(body.additional_fee)
        if additional_fee > 0:
            item_line = ["$DELIVERY_ADDITIONAL_FEE_DELIVERY_REPORT", self._format_price(float(additional_fee))]
            values.append(
                TableReport(
                    lines=[item_line],
                    column_definitions=self.info_value_column_definition,
                )
            )

        if body.vouchers:
            for key, value in body.vouchers.items():
                if float(value) > 0:
                    item_line = ["$" + key + "_DISCOUNT_DELIVERY_REPORT", self._format_price(float(value))]
                    values.append(TableReport([item_line], self.info_value_column_definition))

        item_line = ["$TOTAL_DELIVERY_REPORT", self._format_price(float(body.total_price))]
        values.append(TableReport([item_line], self.info_value_column_definition))

        values.append(Part(None, [RepeatCommand(self.columns, "-"), NewLineCommand()]))

        return values

    def _build_payments(self, body):
        tenders = [
            Part("$PAYMENT_REPORT", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part(None, [RepeatCommand(self.columns, "-"), NewLineCommand()])
        ]

        for tender in body.tenders:
            item_line = [tender["type"], self._format_price(float(tender["value"]))]
            tenders.append(TableReport([item_line], self.info_value_column_definition))

        tenders.append(Part(None, [RepeatCommand(self.columns, "-"), NewLineCommand()]))

        return tenders

    def _build_delivery_information(
        self,
        body,                   # type: DeliveryBodyDto
        show_customer_phone,    # type: bool
    ):
        # type: (...) -> List[Part]

        delivery_information = [
            Part(None, [NewLineCommand()]),
            Part("$INFORMATION_DELIVERY_REPORT", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part(None, [RepeatCommand(self.columns, "-"), NewLineCommand()])
        ]

        delivery_information.extend(self._format_line("$CUSTOMER_NAME_DELIVERY_REPORT",
                                                      body.customer_name,
                                                      self.delivery_info_column_definition))

        if show_customer_phone and (body.partner.lower() != "ifood" or body.need_logistics):
            delivery_information.extend(self._format_line(
                title="$PHONE_DELIVERY_REPORT",
                text=body.customer_phone,
                definition=self.delivery_info_column_definition,
            ))

        delivery_information.extend(self._format_line("$ADDRESS_DELIVERY_REPORT",
                                                      body.address,
                                                      self.delivery_info_column_definition))

        delivery_information.extend(self._format_line("$COMPLEMENT_DELIVERY_REPORT",
                                                      body.address_complement,
                                                      self.delivery_info_column_definition))

        delivery_information.extend(self._format_line("$NEIGHBORHOOD_DELIVERY_REPORT",
                                                      body.address_neighborhood,
                                                      self.delivery_info_column_definition))
        delivery_information.extend(self._format_line("$CITY_DELIVERY_REPORT",
                                                      body.city,
                                                      self.delivery_info_column_definition))
        delivery_information.extend(self._format_line("$POSTAL_CODE_DELIVERY_REPORT",
                                                      body.postal_code,
                                                      self.delivery_info_column_definition))

        delivery_information.extend([
            Part(None, [RepeatCommand(self.columns, "-"), NewLineCommand()]),
            Part(None, [NewLineCommand()])
        ])

        return delivery_information

    def _build_observation(self, body):
        if not body.observation and body.observations == "" and not body.customer_doc:
            return []

        observations = [
            Part("$OBSERVATION_DELIVERY_REPORT", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part(None, [RepeatCommand(self.columns, "-")]),
            Part(None, [NewLineCommand()])
        ]

        if body.observation:
            for observation in body.observation.split(";"):
                observations.extend(self._format_line("", observation.strip(), self.full_line_column_definitions))

        if body.customer_doc:
            title = "$DOCUMENT_DELIVERY_REPORT"
            text = format_cpf_cnpj(body.customer_doc)
            definition = self.observation_column_definition
            observations.extend([Part(None, [NewLineCommand()])])
            observations.extend(self._format_line(title, text, definition))

        if body.observations != "":
            title = ""
            text = body.observations
            definition = self.observation_column_definition
            observations.extend([Part(None, [NewLineCommand()])])
            observations.extend(self._format_line(title, text, definition))

        observations.append(Part(None, [RepeatCommand(self.columns, "-")]))
        return observations

    @staticmethod
    def _build_developer_info():
        developer_info = [
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()]),
            Part("$DEVELOPER_DELIVERY_REPORT", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part("$DEVELOPER_WEB_SITE_DELIVERY_REPORT", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()])
        ]

        return developer_info

    @staticmethod
    def _build_logo():
        logo = LogoImage().get_logo()
        developer_info = [
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()]),
            Part(logo.strip(), [AlignCommand(AlignCommand.Alignment.center)]),
            Part("$DEVELOPER_WEB_SITE_DELIVERY_REPORT_3S", [AlignCommand(AlignCommand.Alignment.center)]),
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()]),
            Part(None, [NewLineCommand()]),
        ]

        return developer_info

    def _format_line(self, title, text, definition):
        if not text:
            return []

        lines = []
        current_string_start = 0
        current_string_end = definition[1].width if title else self.columns

        while len(text) > 0:
            if title and current_string_start == 0:
                lines.append(TableReport([[title, text[:current_string_end]]], definition))
            else:
                lines.append(TableReport([[text[current_string_start:current_string_end]]],
                                         [ReportColumnDefinition(width=self.columns)]))

            if current_string_end > len(text):
                break

            current_string_start = current_string_end
            current_string_end += self.columns

        return lines

    @staticmethod
    def _get_store_name(header):
        return Part(header.store_name.upper(), [AlignCommand(AlignCommand.Alignment.center)])

    def _get_order_number(self, header):
        return self._format_line("$ORDER_DELIVERY_REPORT", header.order_number, self.header_column_definition)

    def _get_order_date(self, header):
        if header.partner.lower() != "delivery":
            created_at = convert_from_utf_to_localtime(header.order_date).strftime("%d/%m/%Y %H:%M:%S")
        else:
            created_at = header.order_date.strftime("%d/%m/%Y %H:%M:%S")

        return self._format_line("$DATE_DELIVERY_REPORT", created_at, self.header_column_definition)

    @staticmethod
    def _format_price(price):
        formatted_value = '{:8,.2f}'.format(price)
        return formatted_value
