import sys

if "/service_account_records" not in list(sys.path):
    sys.path.append("/service_account_records")

from decimal import Decimal

from api_services.bank_actions.wag_account_record_parser import (
    parse_comment_with_name,
    parse_comment_with_name_iban_description,
)
from databases import (
    AccountRecords,
    BuildIbans,
    BuildDecisionBook,
    Build,
    BuildLivingSpace,
    People,
    OccupantTypes,
)
from api_validations.validations_request import ListOptions
from api_library.date_time_actions.date_functions import system_arrow
from databases.sql_models.building.build import BuildParts
from databases.sql_models.building.decision_book import BuildDecisionBookPayments
from databases.sql_models.others.enums import ApiEnumDropdown


account_list = ListOptions(
    page=1,
    size=1000,
    order_field="bank_date",
    order_type="desc",
)


def account_records_find_decision_book():
    created_ibans, iban_build_dict = [], {}
    AccountRecords.filter_attr = account_list
    account_records_list: list[AccountRecords] = AccountRecords.filter_all(
        AccountRecords.build_decision_book_id == None,
        AccountRecords.remainder_balance < AccountRecords.currency_value,
        system=True,
    ).data
    for account_record in account_records_list:
        if account_record.iban not in created_ibans:
            found_iban: BuildIbans = BuildIbans.filter_one(
                BuildIbans.iban == account_record.iban, system=True
            ).data
            if not found_iban:
                try:
                    create_build_ibans = BuildIbans.find_or_create(
                        iban=account_record.iban,
                        start_date=str(system_arrow.now().shift(days=-1)),
                    )
                    create_build_ibans.save_and_confirm()
                    created_ibans.append(account_record.iban)
                except Exception as e:
                    print("Exception of find_decision_book ln:55", e)
            if not getattr(found_iban, "build_id", None):  # It is in database already
                iban_build_dict["iban"] = account_record.iban
                iban_build_dict["build_id"] = None
            else:
                found_decision_book = BuildDecisionBook.filter_one(
                    BuildDecisionBook.build_id == found_iban.build_id,
                    BuildDecisionBook.expiry_starts <= account_record.bank_date,
                    BuildDecisionBook.expiry_ends > account_record.bank_date,
                ).data
                if found_decision_book:
                    account_record.update(
                        build_decision_book_id=found_decision_book.id,
                        build_decision_book_uu_id=str(found_decision_book.uu_id),
                        build_id=found_decision_book.build_id,
                        build_uu_id=str(found_decision_book.build_uu_id),
                    )
                    account_record.save()
    return


def account_save_search_result(account_record, similarity_result):
    found_company = similarity_result.get("company", None)
    found_customer, part, build = (
        similarity_result.get("living_space", None),
        None,
        None,
    )
    if found_customer:
        part = BuildParts.filter_by_one(
            id=found_customer.build_parts_id, human_livable=True
        ).data
    if part:
        build = Build.filter_by_one(id=part.build_id).data

    account_record.similarity = similarity_result.get("similarity", 0.00)
    account_record.found_from = similarity_result.get("found_from", None)
    account_record.company_id = getattr(found_company, "id", None)
    account_record.company_uu_id = getattr(found_company, "uu_id", None)
    account_record.build_parts_id = getattr(part, "id", None)
    account_record.build_parts_uu_id = getattr(part, "uu_id", None)

    if not account_record.build_id and build:
        account_record.build_id = getattr(build, "id", None)
        account_record.build_uu_id = getattr(build, "uu_id", None)

    # account_record.send_person_id = getattr(found_send_person, "id", None)
    # account_record.send_person_uu_id = getattr(found_send_person, "uu_id", None)
    account_record.living_space_id = getattr(found_customer, "id", None)
    account_record.living_space_uu_id = getattr(found_customer, "uu_id", None)
    account_record.save()


def account_get_people_and_living_space_info_via_iban() -> dict:
    build_living_space_dict = {}
    account_records_ibans = (
        AccountRecords.select_only(
            AccountRecords.build_decision_book_id != None,
            select_args=[AccountRecords.iban],
            system=True,
        )
        .query.distinct(AccountRecords.iban)
        .all()
    )
    flat_resident = OccupantTypes.filter_by_one(
        system=True, occupant_category_type="FL", occupant_code="FL-RES"
    ).data
    flat_owner = OccupantTypes.filter_by_one(
        system=True, occupant_category_type="FL", occupant_code="FL-OWN"
    ).data
    flat_tenant = OccupantTypes.filter_by_one(
        system=True, occupant_category_type="FL", occupant_code="FL-TEN"
    ).data
    flat_represent = OccupantTypes.filter_by_one(
        system=True, occupant_category_type="FL", occupant_code="FL-REP"
    ).data
    for account_records_iban in account_records_ibans:
        if account_records_iban not in build_living_space_dict:
            build_iban: BuildIbans = BuildIbans.filter_one(
                BuildIbans.iban == account_records_iban[0], system=True
            ).data
            build_parts = BuildParts.filter_by_all(
                system=True, build_id=build_iban.build_id, human_livable=True
            ).data
            living_spaces = BuildLivingSpace.filter_all(
                BuildLivingSpace.build_parts_id.in_(
                    [build_parts.id for build_parts in build_parts]
                ),
                BuildLivingSpace.occupant_type.in_(
                    [flat_resident.id, flat_owner.id, flat_tenant.id, flat_represent.id]
                ),
            ).data
            living_spaces_people = [
                living_space.person_id
                for living_space in living_spaces
                if living_space.person_id
            ]
            people_list = People.filter_all(
                People.id.in_(living_spaces_people), system=True
            ).data
            print("build_parts", build_parts)
            build_living_space_dict[str(account_records_iban[0])] = {
                "people": list(people_list),
                "living_space": list(living_spaces),
                "build_parts": list(build_parts),
            }
    return build_living_space_dict


def account_records_search():
    build_living_space_dict = account_get_people_and_living_space_info_via_iban()
    AccountRecords.filter_attr, found_list = account_list, []
    account_records_list: list[AccountRecords] = AccountRecords.filter_all(
        # AccountRecords.build_decision_book_id != None, system=True
    ).data
    for account_record in account_records_list:
        similarity_result = parse_comment_with_name(
            account_record=account_record, living_space_dict=build_living_space_dict
        )
        fs, ac = similarity_result.get("similarity"), account_record.similarity or 0
        if float(fs) >= 0.8 and float(fs) > float(ac):
            found_list.append(similarity_result)
            account_save_search_result(
                account_record=account_record, similarity_result=similarity_result
            )
        else:
            similarity_result = parse_comment_with_name_iban_description(
                account_record=account_record
            )
            fs, ac = similarity_result.get("similarity"), account_record.similarity or 0
            if float(fs) >= 0.8 and float(fs) > float(ac):
                found_list.append(similarity_result)
                account_save_search_result(
                    account_record=account_record, similarity_result=similarity_result
                )
    print("Account Records Search : ", len(found_list), "/", len(account_records_list))
    return


def pay_the_registration(
    account_record, receive_enum, debit_enum, is_old_record: bool = False
):
    current_currency_value = float(Decimal(account_record.currency_value)) - float(
        Decimal(account_record.remainder_balance)
    )
    if not current_currency_value > 0:
        return current_currency_value

    process_date = system_arrow.get(account_record.bank_date)
    account_bank_date_year, account_bank_date_month = (
        process_date.date().year,
        process_date.date().month,
    )
    payment_arguments_debit = [
        BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
        BuildDecisionBookPayments.payment_types_id == debit_enum.id,
        BuildDecisionBookPayments.account_records_id == None,
    ]
    if not is_old_record:
        payment_arguments_debit.extend(
            [
                BuildDecisionBookPayments.process_date_y == int(account_bank_date_year),
                BuildDecisionBookPayments.process_date_m
                == int(account_bank_date_month),
            ]
        )
    payments = (
        BuildDecisionBookPayments.filter_all(*payment_arguments_debit)
        .query.order_by(BuildDecisionBookPayments.process_date.asc())
        .all()
    )
    for payment in payments:
        if not current_currency_value > 0:
            return current_currency_value

        payment_arguments_receive = [
            BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
            BuildDecisionBookPayments.payment_plan_time_periods
            == payment.payment_plan_time_periods,
            BuildDecisionBookPayments.payment_types_id == receive_enum.id,
            BuildDecisionBookPayments.build_decision_book_item_id
            == payment.build_decision_book_item_id,
            BuildDecisionBookPayments.decision_book_project_id
            == payment.decision_book_project_id,
            BuildDecisionBookPayments.process_date == payment.process_date,
        ]
        if not is_old_record:
            payment_arguments_receive.extend(
                [
                    BuildDecisionBookPayments.process_date_y
                    == int(account_bank_date_year),
                    BuildDecisionBookPayments.process_date_m
                    == int(account_bank_date_month),
                ]
            )

        payment_received = (
            BuildDecisionBookPayments.filter_all(*payment_arguments_receive).data or []
        )
        sum_of_payment_received = sum(
            [abs(payment.payment_amount) for payment in payment_received]
        )
        net_amount = float(abs(Decimal(payment.payment_amount))) - float(
            abs(Decimal(sum_of_payment_received))
        )
        if not net_amount > 0:
            continue
        if float(abs(current_currency_value)) < float(abs(net_amount)):
            net_amount = float(current_currency_value)
        process_date = system_arrow.get(payment.process_date)
        try:
            created_book_payment = BuildDecisionBookPayments.find_or_create(
                payment_plan_time_periods=payment.payment_plan_time_periods,
                payment_amount=float(abs(net_amount)),
                payment_types_id=receive_enum.id,
                payment_types_uu_id=str(receive_enum.uu_id),
                process_date=str(process_date),
                process_date_m=process_date.date().month,
                process_date_y=process_date.date().year,
                period_time=f"{process_date.year}-{str(process_date.month).zfill(2)}",
                build_parts_id=payment.build_parts_id,
                build_parts_uu_id=str(payment.build_parts_uu_id),
                account_records_id=account_record.id,
                account_records_uu_id=str(account_record.uu_id),
                build_decision_book_item_id=payment.build_decision_book_item_id,
                build_decision_book_item_uu_id=str(
                    payment.build_decision_book_item_uu_id
                ),
                decision_book_project_id=payment.decision_book_project_id,
                decision_book_project_uu_id=str(payment.decision_book_project_uu_id),
            )
            created_book_payment.save_and_confirm()
            created_payment_amount = float(Decimal(created_book_payment.payment_amount))
            remainder_balance = float(
                Decimal(account_record.remainder_balance)
            ) + float(abs(created_payment_amount))
            account_record.update(remainder_balance=remainder_balance)
            account_record.save()
            if current_currency_value >= abs(net_amount):
                current_currency_value -= abs(net_amount)
        except Exception as e:
            print("Exception of decision payment ln:300", e)
    return current_currency_value


def send_accounts_to_decision_payment():
    AccountRecords.filter_attr = account_list
    receive_enum = ApiEnumDropdown.filter_by_one(
        system=True, enum_class="DebitTypes", key="DT-R"
    ).data
    debit_enum = ApiEnumDropdown.filter_by_one(
        system=True, enum_class="DebitTypes", key="DT-D"
    ).data
    account_records_list: list[AccountRecords] = AccountRecords.filter_all(
        AccountRecords.remainder_balance < AccountRecords.currency_value,
        AccountRecords.approved_record == True,
        AccountRecords.receive_debit == receive_enum.id,
    ).data
    for account_record in account_records_list:
        current_currency_value = pay_the_registration(
            account_record, receive_enum, debit_enum
        )
        if current_currency_value > 0:
            pay_the_registration(account_record, receive_enum, debit_enum, True)
        if abs(float(Decimal(account_record.remainder_balance))) == abs(
            float(Decimal(account_record.currency_value))
        ):
            account_record.update(status_id=97)
            account_record.save()
        # # # todo If the payment is more than the amount, then create a new account record with the remaining amount
    return


def account_records_service() -> None:
    print("Account Records Service is running...")
    account_records_find_decision_book()
    account_records_search()
    send_accounts_to_decision_payment()
    print("Account Records Service is finished...")
    return


if __name__ == "__main__":
    account_records_service()
