# -*- coding: utf-8 -*-
import json
import os

from datetime import datetime

import cfgtools
from old_helper import BaseRepository

from utilfunctions import (
    get_tender_info,
    is_electronic_payment,
)

from actions.models import (
    GLAccount,
    TransferType,
    CloseOperator,
)


class AccountRepository(BaseRepository):
    def __init__(self, mb_context):
        super(AccountRepository, self).__init__(mb_context)
        config = cfgtools.read(os.environ["LOADERCFG"])
        self.considers_tef = config.find_value(
            "Customizations.ConsidersTEFOnBordereau",
            "false",
        ).lower() == "true"
        self.bordereau_enabled = config.find_value(
            "Customizations.BordereauEnabled",
            "false",
        ).lower() == "true"

    def insert_close_operator_info(self, operator):
        # type: (CloseOperator) -> float

        from table_actions import considers_tef_on_bordereau

        def inner_func(conn):
            total_bordereau_difference = 0
            initial_fund = self.get_initial_amount(operator.pos_id, operator.session)
            total_cash_in = self.get_cash_in_total_amount(operator.pos_id, operator.session)
            total_cash_out = self.get_cash_out_total_amount(operator.pos_id, operator.session)
            final_cash = initial_fund + total_cash_in - total_cash_out

            if self.bordereau_enabled and considers_tef_on_bordereau:
                _link_tef_tenders(operator)

            _insert_unlinked_tef_tenders(operator)

            query = "DELETE FROM Transfer WHERE SessionId = '{}';".format(operator.session)

            for value in operator.values:
                tender_id = int(value["tenderId"])
                sold_amount = sum(p["amount"] for p in operator.payments.get(tender_id, []))
                sold_amount += value.get("additionalSkim", 0.0)

                declared_amount = value["amount"]
                if declared_amount is not None:
                    tender_difference = _get_total_difference(
                        tender_id,
                        declared_amount,
                        sold_amount,
                        final_cash,
                    )
                    total_bordereau_difference += abs(tender_difference)
                else:
                    declared_amount = sold_amount

                envelope = _get_envelope(tender_id, operator)
                gl_account = GLAccount(manager=operator.manager_id, envelope=envelope)
                gl_account_json = json.dumps(gl_account, default=lambda o: o.__dict__, sort_keys=True)

                query += """
                        INSERT INTO Transfer 
                        VALUES ('{}', {}, '{}', {}, '{}', {}, {}, '{}', '{}');
                    """.format(
                        operator.period,
                        operator.pos_id,
                        operator.session,
                        TransferType.DECLARED_AMOUNT,
                        "DECLARED_AMOUNT",
                        declared_amount,
                        tender_id,
                        datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
                        gl_account_json
                    )

                query += """
                        INSERT INTO Transfer 
                        VALUES ('{}', {}, '{}', {}, '{}', {}, {}, '{}', '{}');
                    """.format(
                        operator.period,
                        operator.pos_id,
                        operator.session,
                        TransferType.TRANSFER_SKIM,
                        "TRANSFER_SKIM",
                        sold_amount,
                        tender_id,
                        datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
                        ""
                    )

            conn.query(query)

            return total_bordereau_difference

        return self.execute_with_transaction(inner_func, db_name=str(operator.pos_id))

    def update_justification(self, pos_id, session, justification):
        def inner_func(conn):
            conn.query("""UPDATE Transfer
                          SET GLAccount = REPLACE(GLAccount, '"justification": ""', '"justification": {}')
                          WHERE SessionId = '{}' AND GLAccount IS NOT NULL AND GLAccount != '' AND Type IN (5,6)"""
                       .format(self._fix_str_to_json_format(justification), session))

        self.execute_with_transaction(inner_func, db_name=str(pos_id))

    @staticmethod
    def _fix_str_to_json_format(
        string,  # type: str
    ):
        # type: (...) -> str

        return json.dumps(string.replace("'", "\""))

    def get_closing_operator_sessions(self, pos_id, user, start_date, end_date):
        def inner_func(conn):
            sessions = []
            query = """SELECT SessionId
                       FROM Transfer
                       WHERE SessionId LIKE '%user={},%' and Period >= '{}' and Period <= '{}'
                       GROUP BY SessionId
                       ORDER BY Timestamp""".format(user, start_date, end_date)

            cursor = conn.select(query)
            if cursor.rows() > 0:
                for row in cursor:
                    sessions.append(row.get_entry("SessionId"))

            return sessions

        return self.execute_with_connection(inner_func, db_name=str(pos_id))

    def get_initial_amount(self, pos_id, session_id):
        return self.get_transfer_total_amount(pos_id, session_id, TransferType.INITIAL_AMOUNT)

    def get_cash_in_total_amount(self, pos_id, session_id):
        return self.get_transfer_total_amount(pos_id, session_id, TransferType.TRANSFER_CASH_IN)

    def get_cash_out_total_amount(self, pos_id, session_id):
        return self.get_transfer_total_amount(pos_id, session_id, TransferType.TRANSFER_CASH_OUT)

    def get_transfer_total_amount(self, pos_id, session_id, transfer_type):
        def inner_func(conn):
            query = """
                        SELECT amount FROM transfer
                        WHERE Type = '{}' AND SessionId = '{}'
                    """.format(transfer_type, session_id)
            cursor = conn.select(query)
            if cursor.rows() > 0:
                total_cash_in = 0
                for row in cursor:
                    total_cash_in += float(row.get_entry(0) or 0.0)

                return total_cash_in

            return 0

        return self.execute_with_connection(inner_func, db_name=str(pos_id))


def _get_envelope(tender_id, operator):
    if tender_id == 0:
        return operator.envelope

    return ""


def _get_total_difference(
    tender_id,      # type: int
    amount,         # type: float
    sold_amount,    # type: float
    final_cash,     # type: float
):
    # type: (...) -> float

    if tender_id == 0:
        return (amount - sold_amount) - final_cash

    return amount - sold_amount


def _link_tef_tenders(
    operator,  # type: CloseOperator
):
    # type: (...) -> None

    for value in operator.values:
        if not value.get("linkWithTef"):
            continue

        custom_attr = value.get("customAttr", {})
        sitef_code = custom_attr.get("sitefCode")
        if not sitef_code:
            continue

        sitef_codes = str(sitef_code).split(";")
        for payment_tender_id, payment_list in operator.payments.items():
            payment_tender = get_tender_info(payment_tender_id)
            if not is_electronic_payment(payment_tender):
                continue

            for payment in payment_list:
                payment_sitef_code = str(payment.get("cardBrand", ""))
                if payment_sitef_code not in sitef_codes:
                    continue

                if "additionalSkim" not in value:
                    value["additionalSkim"] = 0

                value["additionalSkim"] += payment["amount"]
                payment["amount"] = 0.0
                payment["linked"] = True


def _insert_unlinked_tef_tenders(
    operator,  # type: CloseOperator
):
    # type: (...) -> None

    values_tender_ids = [value["tenderId"] for value in operator.values]
    for tender_id, payment_list in operator.payments.items():
        if tender_id in values_tender_ids:
            continue

        tender = get_tender_info(tender_id)
        if not is_electronic_payment(tender):
            continue

        operator.values.append({
            "amount": None,
            "tenderId": tender_id,
            "linkWithTef": False,
            "customAttr": {},
        })
