import typing

from fastapi import status, HTTPException
from fastapi.responses import JSONResponse

from api_events.events.events.events_bind_services import (
    ServiceBindOccupantEventMethods,
)
from databases import (
    Build,
    BuildParts,
    BuildDecisionBook,
    BuildDecisionBookItems,
    BuildDecisionBookPerson,
    BuildDecisionBookPayments,
    BuildDecisionBookProjects,
    BuildDecisionBookProjectPerson,
    ApiEnumDropdown,
    OccupantTypes,
    Companies,
    BuildLivingSpace,
)

from api_events.events.abstract_class import MethodToEvent, ActionsSchema
from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject
from ApiServices.api_handlers import AlchemyJsonResponse
from api_library.date_time_actions.date_functions import system_arrow

from api_validations.validations_request import (
    InsertBuildDecisionBookItems,
    ListOptions,
)
from databases.sql_models.event.event import Services


class DecisionBookDecisionBookItemsListEventMethods(MethodToEvent):

    event_type = "SELECT"
    __event_keys__ = {
        "eb36de59-8268-4d96-80b6-5d01c12bf0b1": "building_decision_book_items_list",
    }
    __event_validation__ = {"eb36de59-8268-4d96-80b6-5d01c12bf0b1": None}

    @classmethod
    def building_decision_book_items_list(
        cls,
        data: ListOptions,
        token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        decision_book = BuildDecisionBook.filter_one(
            BuildDecisionBook.uu_id == data.build_decision_book_uu_id,
        ).data
        if not decision_book:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"No Decision Book is match with given UUID {data.build_decision_book_uu_id}",
            )
        if isinstance(token_dict, EmployeeTokenObject):
            Build.pre_query = Build.select_action(
                employee_id=token_dict.selected_company.employee_id,
                filter_expr=[
                    Build.uu_id == str(decision_book.uu_id),
                ],
            )
            reachable_building = Build.filter_all()
            if not reachable_building.data:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"No building is match with given Employee UUID {token_dict.selected_company.employee_uu_id}",
                )

            Companies.pre_query = Companies.select_action(
                duty_id_list=[
                    token_dict.selected_company.duty_id,
                    token_dict.selected_company.bulk_duties_id,
                ],
                filter_expr=[Companies.id == decision_book.resp_company_id],
            )
            reachable_companies = Companies.filter_all()
            if not reachable_companies.data:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"No company is match with given Employee UUID {token_dict.selected_company.employee_uu_id}",
                )
            BuildDecisionBookItems.filter_attr = BuildDecisionBookItems.FilterModel(
                **data.dump()
            )
            records = BuildDecisionBookItems.filter_all(
                BuildDecisionBookItems.build_decision_book_id == decision_book.id
            )
            return AlchemyJsonResponse(
                completed=True,
                message="DecisionBook are listed successfully",
                result=records,
            )
            # return JSONResponse(
            #     status_code=status.HTTP_200_OK,
            #     content=dict(
            #         total_count=records.count,
            #         count=len(records.data),
            #         message=f"Decision Book Items has found from given Decision Book UUID {data.build_decision_book_uu_id}",
            #         completed=True,
            #         data=[record.get_dict() for record in records.data],
            #     ),
            # )
        else:
            # BuildDecisionBookItems.pre_query = BuildDecisionBookItems.select_action(
            #     occupant_id=token_dict.occupant_list["occupant_id"]
            # )
            # BuildDecisionBookItems.filter_attr = list_options
            # records = BuildDecisionBookItems.filter_all(
            # )
            # return return_json_response_from_alchemy(response=records, pagination=list_options)
            return


class DecisionBookDecisionBookItemsCreateEventMethods(MethodToEvent):

    event_type = "CREATE"
    __event_keys__ = {
        "dce10509-0da5-46fb-af3c-a81d54d5481c": "building_decision_book_items_create",
    }
    __event_validation__ = {
        "dce10509-0da5-46fb-af3c-a81d54d5481c": InsertBuildDecisionBookItems
    }

    @classmethod
    def iterate_over_build_parts(
        cls,
        build_parts_list,
        payment_types,
        local_date,
        end_date,
        unit_price,
        unit_type,
        book_payment_dict,
        unit_price_is_fixed,
    ):
        start_date, payment_return_dict = local_date, {}
        for build_part_single in build_parts_list:
            local_date = start_date
            while local_date.is_between(start_date, end_date, "[]"):
                local_date = system_arrow.find_last_day_of_month(local_date)
                payment_amount = unit_price
                if not unit_price_is_fixed:
                    unit_amount = int(build_part_single.part_net_size)
                    payment_amount = abs(unit_price * float(unit_amount)) * -1
                    payment_amount = -1 * (
                        abs(payment_amount) + (50 - float(abs(payment_amount)) % 50)
                    )
                created_book_payment = BuildDecisionBookPayments.find_or_create(
                    build_parts_id=build_part_single.id,
                    build_parts_uu_id=str(build_part_single.uu_id),
                    payment_amount=payment_amount,
                    payment_types_id=payment_types.id,
                    payment_types_uu_id=str(payment_types.uu_id),
                    process_date=str(local_date),
                    process_date_m=int(local_date.month),
                    process_date_y=int(local_date.year),
                    period_time=f"{local_date.year}-{str(local_date.month).zfill(2)}",
                    **book_payment_dict,
                )
                created_book_payment.save_and_confirm()
                local_date = local_date.shift(days=2)
                part_key = str(build_part_single.due_part_key).upper()
                if part_key not in payment_return_dict:
                    payment_return_dict[part_key] = payment_amount
        return payment_return_dict

    @classmethod
    def create_payment_records_for_each_build_part(
        cls,
        data_info_type,
        build_id,
        unit_price,
        unit_type,
        decision_book,
        decision_book_item,
        unit_price_is_fixed,
        currency,
        debit_start_date: str = None,
        debit_end_date: str = None,
    ):
        build_parts_list = BuildParts.filter_all(
            BuildParts.human_livable == True,
            BuildParts.build_id == build_id,
        )
        print("data_info_type.key", data_info_type.key)
        book_payment_dict = dict(
            payment_plan_time_periods=str(data_info_type.key),
            build_decision_book_item_id=decision_book_item.id,
            build_decision_book_item_uu_id=str(decision_book_item.uu_id),
            currency=currency,
        )
        payment_types = ApiEnumDropdown.get_debit_search(search_debit="DT-D")
        if data_info_type.key == "BDT-D":
            local_date = system_arrow.get(
                system_arrow.get(decision_book.expiry_starts).date()
            )
            end_date = system_arrow.get(
                system_arrow.get(decision_book.expiry_ends).date()
            )
            payment_return_dict = cls.iterate_over_build_parts(
                build_parts_list=build_parts_list.data,
                payment_types=payment_types,
                local_date=local_date,
                end_date=end_date,
                unit_price=unit_price,
                unit_type=unit_type,
                unit_price_is_fixed=unit_price_is_fixed,
                book_payment_dict=book_payment_dict,
            )
            return payment_return_dict
        elif data_info_type.key == "BDT-A":
            local_date = system_arrow.get(system_arrow.get(debit_start_date).date())
            end_date = system_arrow.get(system_arrow.get(debit_end_date).date())
            payment_return_dict = cls.iterate_over_build_parts(
                build_parts_list=build_parts_list.data,
                payment_types=payment_types,
                local_date=local_date,
                end_date=end_date,
                unit_price=unit_price,
                unit_type=unit_type,
                unit_price_is_fixed=unit_price_is_fixed,
                book_payment_dict=book_payment_dict,
            )
            return payment_return_dict
        elif data_info_type.key == "BDT-R" or data_info_type.key == "BDT-L":
            local_date = system_arrow.get(system_arrow.get(debit_start_date).date())
            end_date = system_arrow.get(system_arrow.get(debit_end_date).date())
            decision_date = system_arrow.get(decision_book.expiry_starts).date()
            meeting_date = system_arrow.get(decision_book.meeting_date).date()

            already_book_projects = BuildDecisionBookProjects.filter_all(
                BuildDecisionBookProjects.build_decision_book_id == decision_book.id,
                BuildDecisionBookProjects.project_type
                == f"{decision_book.decision_type}_{data_info_type.key}",
                system=True,
            )
            management_room = BuildParts.filter_one(
                BuildParts.build_id == build_id, BuildParts.part_no == 0, system=True
            ).data
            occupant_man = OccupantTypes.filter_by_one(
                system=True, occupant_code="MT-VPR", occupant_category_type="MT"
            ).data
            manager_living_space = BuildLivingSpace.filter_one(
                BuildLivingSpace.build_parts_id == management_room.id,
                BuildLivingSpace.occupant_type == occupant_man.id,
            ).data
            if not manager_living_space:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail=f"{occupant_man.occupant_description} Living Space is not found. Check manager living space and try again",
                )
            already_book_project_count = already_book_projects.count + 1
            book_project_dict = dict(
                project_no=f"{data_info_type.key}_{decision_date.year}_{already_book_project_count}",
                project_name=f"{str(meeting_date)}_{decision_book.decision_type} Project {already_book_projects.count + 1}",
                project_start_date=str(local_date),
                project_stop_date=str(end_date),
                project_type=f"{decision_book.decision_type}_{data_info_type.key}",
                project_note=str(decision_book_item.item_comment),
                build_decision_book_id=decision_book.id,
                build_decision_book_uu_id=str(decision_book.uu_id),
                build_decision_book_item_id=decision_book_item.id,
                build_decision_book_item_uu_id=str(decision_book_item.uu_id),
                project_response_living_space_id=manager_living_space.id,
                project_response_living_space_uu_id=str(manager_living_space.uu_id),
            )
            book_project_created = BuildDecisionBookProjects.find_or_create(
                **book_project_dict
            )
            book_project_created.save_and_confirm()
            print("book_project_created", book_project_created)
            item_comment_at_database = decision_book_item.item_comment
            decision_book_item.update(
                item_comment=f"{book_project_created.project_no}_{book_project_created.project_name} "
                f"is assigned to {occupant_man.occupant_description} | {item_comment_at_database}"
            )
            decision_book_item.save_and_confirm()
            project_lead = OccupantTypes.filter_by_one(
                system=True, occupant_code="PRJ-LDR", occupant_category_type="PRJ"
            ).data
            build_new_president = OccupantTypes.filter_by_one(
                system=True, occupant_code="MT-VPR", occupant_category_type="MT"
            ).data

            new_president = BuildLivingSpace.filter_one(
                BuildLivingSpace.occupant_type == build_new_president.id,
                BuildLivingSpace.build_parts_id == management_room.id,
            ).data

            project_leader = BuildLivingSpace.filter_one(
                BuildLivingSpace.occupant_type == project_lead.id,
                BuildLivingSpace.build_parts_id == management_room.id,
                BuildLivingSpace.person_id == new_president.person_id,
            ).data
            if not project_leader:
                project_leader = BuildLivingSpace.find_or_create(
                    person_id=new_president.person_id,
                    person_uu_id=str(new_president.person_uu_id),
                    build_parts_id=management_room.id,
                    build_parts_uu_id=str(management_room.uu_id),
                    occupant_type=project_lead.id,
                    occupant_type_uu_id=str(project_lead.uu_id),
                )
                project_leader.save_and_confirm()
                related_service = Services.filter_by_one(
                    system=True,
                    related_responsibility=project_lead.occupant_code,
                ).data
                ServiceBindOccupantEventMethods.bind_services_occupant_system(
                    service_id=related_service.id,
                    build_living_space_id=project_leader.id,
                )
            project_person = BuildDecisionBookProjectPerson.find_or_create(
                build_decision_book_project_id=book_project_created.id,
                build_decision_book_project_uu_id=str(book_project_created.uu_id),
                living_space_id=project_leader.id,
                living_space_uu_id=str(project_leader.uu_id),
            )
            project_person.save_and_confirm()
            book_project_created.update(
                project_response_living_space_id=project_leader.id,
                project_response_living_space_uu_id=str(project_leader.uu_id),
            )
            book_project_created.save()
            return book_project_created
        elif data_info_type.key == "BDT-S":
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="BDT-S is not implemented yet. Check info type and try again",
            )
        elif data_info_type.key == "BDT-I":
            return
        else:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Payment Info Type is not valid. Check payment type and try again",
            )

    @classmethod
    def building_decision_book_items_create(
        cls,
        data: InsertBuildDecisionBookItems,
        token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
    ):

        if isinstance(token_dict, EmployeeTokenObject):
            raise HTTPException(
                status_code=status.HTTP_406_NOT_ACCEPTABLE,
                detail="No employee can reach this event. An notification is send to admin about event registration",
            )
        elif isinstance(token_dict, OccupantTokenObject):
            data_dict = data.dump()
            occupant_wrt = OccupantTypes.filter_by_one(
                system=True, occupant_code="MT-WRT", occupant_category_type="MT"
            ).data

            if token_dict.selected_occupant.occupant_type_id != occupant_wrt.id:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="Only WRITER can create decision book item. Check your occupant type and try again",
                )

            decision_book_person = BuildDecisionBookPerson.filter_one(
                BuildDecisionBookPerson.token == data.token,
            ).data
            decision_book = BuildDecisionBook.filter_one(
                BuildDecisionBook.id == decision_book_person.build_decision_book_id,
            ).data

            book_items = BuildDecisionBookItems.filter_all(
                BuildDecisionBookItems.build_decision_book_id == decision_book.id,
                system=True,
            )
            if int(book_items.count) < 3:
                BuildDecisionBookItems.check_meeting_is_valid_to_start_add_attendance(
                    decision_book=decision_book,
                    token_dict=token_dict,
                )

            book_items = BuildDecisionBookItems.filter_all(
                BuildDecisionBookItems.build_decision_book_id == decision_book.id,
                system=True,
            )

            data_dict["item_order"] = int(book_items.count) + 1
            data_dict["build_decision_book_id"] = decision_book.id
            data_dict["build_decision_book_uu_id"] = str(decision_book.uu_id)

            data_info_type = ApiEnumDropdown.filter_by_one(
                system=True,
                enum_class="BuildDuesTypes",
                key="BDT-I",
            ).data
            data_info_types = ApiEnumDropdown.due_type_search()
            for info_type in data_info_types:
                if str(info_type.uu_id) == data_dict["info_type_uu_id"]:
                    data_info_type = info_type
                    break

            row_is_debit = str(data_info_type.key).upper() in ["BDT-A", "BDT-D"]
            row_is_project = str(data_info_type.key).upper() in [
                "BDT-R",
                "BDT-L",
                "BDT-S",
            ]
            debit_dates_required = (
                not data_dict["debit_start_date"] or not data_dict["debit_end_date"]
            )
            if row_is_project and debit_dates_required:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="Debit Start Date and Debit End Date is required for this payment type. "
                    "Check debit start date and debit end date and try again",
                )

            data_dict["info_type_id"] = data_info_type.id
            data_dict["info_type_uu_id"] = str(data_info_type.uu_id)
            unit_price, unit_type = float(data_dict["unit_price"]), str(
                data_dict["unit_type"]
            )

            debit_start_date, debit_end_date = (
                data_dict["debit_start_date"],
                data_dict["debit_end_date"],
            )
            currency = data_dict["currency"]
            del (
                data_dict["token"],
                data_dict["unit_price"],
                data_dict["unit_type"],
                data_dict["unit_price_is_fixed"],
                data_dict["debit_start_date"],
                data_dict["debit_end_date"],
                data_dict["currency"],
            )

            new_decision_book_item = BuildDecisionBookItems.find_or_create(**data_dict)
            new_decision_book_item.save_and_confirm()
            print("new_decision_book_item", new_decision_book_item)
            if created_payment_records_dict := cls.create_payment_records_for_each_build_part(
                data_info_type=data_info_type,
                build_id=decision_book.build_id,
                unit_price=unit_price,
                unit_type=unit_type.upper(),
                decision_book=decision_book,
                decision_book_item=new_decision_book_item,
                unit_price_is_fixed=data.unit_price_is_fixed,
                debit_start_date=debit_start_date,
                debit_end_date=debit_end_date,
                currency=currency,
            ):
                if row_is_debit:
                    if data_info_type.key == "BDT-D":
                        item_comment = "Regular Payment Plan : "
                    else:
                        item_comment = "Additional Payment Plan : "
                    for key, value in dict(
                        sorted(
                            created_payment_records_dict.items(),
                            key=lambda x: x[1],
                            reverse=True,
                        )
                    ).items():
                        item_comment += f" {key} | {abs(float(value))} {currency}, "
                    item_comment = item_comment[:-2]
                    new_decision_book_item.update(item_comment=item_comment)
                    new_decision_book_item.update(is_payment_created=True)
                elif row_is_project:
                    project_no = str(created_payment_records_dict.project_no)
                    item_comment = (
                        f"{data.item_comment} | @ Project is created no : {project_no}."
                    )
                    new_decision_book_item.update(item_comment=item_comment)
                    new_decision_book_item.update(is_payment_created=True)
                new_decision_book_item.save_and_confirm()

            return JSONResponse(
                status_code=status.HTTP_200_OK,
                content=dict(
                    message=f"Decision Book Item has created for given Decision Book UUID {decision_book.uu_id}",
                    completed=True,
                    data=new_decision_book_item.get_dict(),
                ),
            )
        # raise HTTPException(
        #     status_code=status.HTTP_400_BAD_REQUEST,
        #     detail="Decision Book Item is not created. Check info type and info no and try again. Unique constraint index is implemented for info type and info no",
        # )


class DecisionBookDecisionBookItemsUpdateEventMethods(MethodToEvent):

    event_type = "UPDATE"
    __event_keys__ = {
        "f0fdfe1b-806b-4175-ad50-a1a165c0dfb7": "building_decision_book_items_update",
    }
    __event_validation__ = {"f0fdfe1b-806b-4175-ad50-a1a165c0dfb7": None}

    @classmethod
    def building_decision_book_items_update(
        cls,
        data: InsertBuildDecisionBookItems,
        token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        return


class DecisionBookDecisionBookItemsPatchEventMethods(MethodToEvent):

    event_type = "PATCH"
    __event_keys__ = {
        "42328809-b516-477b-82cc-2d6fadf28843": "building_decision_book_items_patch",
    }
    __event_validation__ = {"42328809-b516-477b-82cc-2d6fadf28843": None}

    @classmethod
    def building_decision_book_items_patch(
        cls,
        data: InsertBuildDecisionBookItems,
        token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        return


DecisionBookDecisionBookItemsListEventMethod = (
    DecisionBookDecisionBookItemsListEventMethods(
        action=ActionsSchema(endpoint="/build/decision_book/items/list")
    )
)
DecisionBookDecisionBookItemsCreateEventMethod = (
    DecisionBookDecisionBookItemsCreateEventMethods(
        action=ActionsSchema(endpoint="/build/decision_book/items/create")
    )
)
DecisionBookDecisionBookItemsUpdateEventMethod = (
    DecisionBookDecisionBookItemsUpdateEventMethods(
        action=ActionsSchema(endpoint="/build/decision_book/items/update")
    )
)
DecisionBookDecisionBookItemsPatchEventMethod = (
    DecisionBookDecisionBookItemsPatchEventMethods(
        action=ActionsSchema(endpoint="/build/decision_book/items/patch")
    )
)

# if data.info_no is None:
#     data_dict["info_no"] = data_dict["item_order"]
# elif data.occupant_types_uu_id:
#     data_dict["info_type"] = None
#     if occupant_type := OccupantTypes.find_one(
#         uu_id=data.occupant_types_uu_id
#     ):
#         if (
#             str(data.occupant_types_uu_id)
#             in OccupantTypes.get_manager_occupant_type()
#         ):
#             if BuildDecisionBookItems.find_one(
#                 build_decision_book_id=decision_book.id,
#                 occupant_type=occupant_type.id,
#             ):
#                 raise HTTPException(
#                     status_code=status.HTTP_400_BAD_REQUEST,
#                     detail="This Occupant Type is already exist for given Decision Book UUID. Check occupant type and try again",
#                 )
#         data_dict["occupant_type"] = occupant_type.id
#         data_dict["occupant_type_uu_id"] = str(occupant_type.uu_id)
