# encoding: utf-8

import datetime
import json
import time
from cStringIO import StringIO
from typing import (
    Dict,
    List,
    Optional,
)
from xml.etree import cElementTree as eTree

from sysactions import get_user_information

from domain import TransferType
from model import Account
from old_helper import convert_from_utf_to_localtime
from repository import (
    AccountRepository,
    ProductRepository,
)
from utils import center, join, double_line_separator, add_amount_line, empty_line, add_table_line, add_key_value_line
from utils.report_formatters import remove_accents


DATE_TIME_FMT = "%d/%m/%Y %H:%M:%S"


class CloseOperatorReport(object):
    def __init__(
        self,
        product_repository,  # type: ProductRepository
        account_repository,  # type: AccountRepository
        store_id,            # type: str
        bordereau_enabled,   # type: bool
    ):
        # type: (...) -> None
        self.product_repository = product_repository
        self.account_repository = account_repository
        self.store_id = store_id
        self.bordereau_enabled = bordereau_enabled

    def generate_close_operator_report(self, pos_id, operator_id, period, session_id, cash_report, authorizer):
        report = StringIO()
        report.write(self._close_operator_report_header(int(pos_id), operator_id, period, authorizer, session_id))
        self._close_operator_report_body(report, session_id, cash_report)

        return report.getvalue()

    def _close_operator_report_header(self, pos_id, operator_id, period, authorizer, session_id):
        title = center("Logout Operador")
        current_datetime = time.strftime(DATE_TIME_FMT)
        formatted_store_id = self.store_id
        fiscal_date = "%02d/%02d/%04d" % (int(period[6:8]), int(period[4:6]), int(period[:4]))
        separator = double_line_separator()

        user_information = get_user_information(operator_id)
        operator_name = operator_id
        if user_information is not None:
            operator_name = eTree.XML(get_user_information(operator_id)).find(".//user").get("LongName")[:15]

        authorizer_text = ""
        if authorizer is None:
            authorizer = self.account_repository.get_authorizer(session_id)
            if authorizer is not None:
                authorizer_text = "Autorizador..: {:s}".format(authorizer)
        else:
            user_information = get_user_information(authorizer)
            authorizer_name = authorizer
            if user_information is not None:
                authorizer_name = eTree.XML(get_user_information(authorizer)).find(".//user").get("LongName")[:15]
                authorizer_text = "Autorizador..: {:s}".format(authorizer_name)

        return ("""
%(separator)s
%(title)s
Loja.........: %(formatted_store_id)s
Data/hora....: %(current_datetime)s
Data fiscal..: %(fiscal_date)s
Operador.....: %(operator_name)s (POS # %(pos_id)02d)
%(authorizer_text)s
%(separator)s""" % join(globals(), locals()))

    def _close_operator_report_body(self, report, session_id, cash_report):
        tenders_description = self.product_repository.get_tenders_descripton()
        transfers = self.account_repository.get_all_transfers(session_id)
        declared_amounts = [
            transfer for transfer in transfers
            if transfer.account_type == TransferType.DECLARED_AMOUNT
        ]

        report.write(center("RESUMOS"))
        report.write(cash_report)
        self._write_cash_in_and_cash_out(
            transfers,
            declared_amounts,
            report
        )

        report.write(double_line_separator())

        if self.bordereau_enabled:
            self._write_declared_amounts(
                transfers,
                declared_amounts,
                tenders_description,
                report,
            )
        else:
            self._write_simple_summary(
                declared_amounts,
                report,
            )

    def _write_cash_in_and_cash_out(
        self,
        transfers,         # type: List[Account]
        declared_amounts,  # type: List[Account]
        report,            # type: StringIO
    ):
        # type: (...) -> None

        initial_transfer = self._get_total_transfer_by_type(transfers, TransferType.INITIAL_AMOUNT)
        total_cash_in = self._get_total_transfer_by_type(transfers, TransferType.TRANSFER_CASH_IN)
        total_cash_out = self._get_total_transfer_by_type(transfers, TransferType.TRANSFER_CASH_OUT)
        total_cash = self._get_total_transfer_by_type(
            transfers,
            TransferType.TRANSFER_SKIM,
            tender_id=0
        )

        report.write(center("SUPRIMENTOS/SANGRIAS"))
        report.write(add_amount_line("Fundo Inicial", None, initial_transfer))

        in_out_types = (TransferType.TRANSFER_CASH_IN, TransferType.TRANSFER_CASH_OUT)
        all_cash_in_and_out = [transfer for transfer in transfers if transfer.account_type in in_out_types]

        for cash in all_cash_in_and_out:
            cash_type = "Suprimento {}h"
            if cash.account_type == TransferType.TRANSFER_CASH_OUT:
                cash_type = "Sangria    {}h"

            date = datetime.datetime.strptime(cash.operation_date, "%Y-%m-%d %H:%M:%S")
            date = convert_from_utf_to_localtime(date)
            hour = str(date.hour).zfill(2) + ":" + str(date.minute).zfill(2)
            report.write(add_amount_line(cash_type.format(hour), None, cash.amount))

        drawer_amount = initial_transfer + total_cash + total_cash_in - total_cash_out
        declared_cash_amount = [transfer for transfer in declared_amounts if transfer.tender_id == 0]
        declared_cash_amount = 0.0 if len(declared_cash_amount) == 0 else declared_cash_amount[0].amount
        report.write(add_amount_line("Gaveta", None, drawer_amount))
        report.write(add_amount_line("Declarado", None, declared_cash_amount))
        report.write(add_amount_line("Diferenca", None, self._invert_sign(drawer_amount - declared_cash_amount)))

    def _write_declared_amounts(
        self,
        transfers,              # type: List[Account]
        declared_amounts,       # type: List[Account]
        tenders_description,    # type: Dict[str, str]
        report,                 # type: StringIO
    ):
        # type: (...) -> None

        report.write(center("BORDEREAU"))
        header = ["Pagamento", "Declarado", "Diferenca"]
        columns_height = [18, 10, 10]
        align = ['left', 'right', 'right']
        format_type = ['string', 'float', 'float']
        character_to_add = [".:", " ", " "]
        report.write(add_table_line(header, columns_height, align))
        total_in = 0
        total_declared = 0
        gl_account = None
        missing_tender_ids = None
        for declared in declared_amounts:
            sold_amount = self._get_total_transfer_by_type(
                transfers,
                TransferType.TRANSFER_SKIM,
                tender_id=declared.tender_id,
            )

            current_amount = sold_amount
            if declared.tender_id == 0:
                initial_transfer = self._get_total_transfer_by_type(transfers, TransferType.INITIAL_AMOUNT)
                total_cash_in = self._get_total_transfer_by_type(transfers, TransferType.TRANSFER_CASH_IN)
                total_cash_out = self._get_total_transfer_by_type(transfers, TransferType.TRANSFER_CASH_OUT)

                current_amount += initial_transfer + total_cash_in - total_cash_out
                gl_account = declared.glaccount

            if declared.is_undeclared:
                difference = 0
            else:
                total_in += current_amount
                total_declared += declared.amount
                difference = self._invert_sign(current_amount - declared.amount)

            tender_id = str(declared.tender_id)
            declared_amount = current_amount if declared.is_undeclared else declared.amount
            if tender_id in tenders_description:
                formatted_tender = remove_accents(
                    tenders_description[tender_id]
                )
                line = [formatted_tender, declared_amount, difference]
                report.write(add_table_line(line, columns_height, align, format_type, character_to_add))
            else:
                if missing_tender_ids:
                    missing_tender_ids['declared_amount'] += declared_amount
                    missing_tender_ids['difference'] += difference
                else:
                    missing_tender_ids = {
                        'declared_amount': declared_amount,
                        'difference': difference,
                    }

        if missing_tender_ids:
            line = [
                'Outros',
                missing_tender_ids['declared_amount'],
                missing_tender_ids['difference']
            ]
            report.write(
                add_table_line(
                    line,
                    columns_height,
                    align,
                    format_type,
                    character_to_add
                )
            )

        report.write(empty_line())
        gl_account = {} if not gl_account else json.loads(gl_account)
        if gl_account:
            diff_total = self._invert_sign(total_in - total_declared)
            report.write(add_amount_line("Diferenca total", None, diff_total))
            report.write(add_key_value_line("Numero Envelope..: ", gl_account["envelope"]))
            justification = remove_accents(gl_account["justification"])
            report.write(add_key_value_line("Justificativa....: ", justification))

    @staticmethod
    def _write_simple_summary(
        declared_amounts,   # type: List[Account]
        report,             # type: StringIO
    ):
        # type: (...) -> None

        gl_account = {}
        for declared in declared_amounts:
            if declared.tender_id == 0:
                gl_account = json.loads(declared.glaccount or "{}")
                break

        report.write(empty_line())
        report.write(add_key_value_line("Numero Envelope..: ", gl_account.get("envelope", "")))
        report.write(add_key_value_line("Justificativa....: ", gl_account.get("justification", "")))

    @staticmethod
    def _get_total_transfer_by_type(
        transfers,       # type: List[Account]
        account_type,    # type: TransferType
        tender_id=None,  # type: Optional[int]
    ):
        # type: (...) -> float

        if tender_id is None:
            total = sum(
                transfer.amount for transfer in transfers
                if transfer.account_type == account_type
            )
        else:
            total = sum(
                transfer.amount for transfer in transfers
                if transfer.account_type == account_type and transfer.tender_id == tender_id
            )
        return total

    @staticmethod
    def _invert_sign(value):
        return -value if value != 0 else value
