from typing import Union

from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse

from databases import (
    AddressPostcode,
    Addresses,
    RelationshipEmployee2PostCode,
    AddressStreet,
)

from api_validations.validations_request import (
    ListOptions,
    InsertAddress,
    UpdateAddress,
    InsertPostCode,
    UpdatePostCode,
    SearchAddress,
)
from api_validations.core_response import AlchemyJsonResponse
from api_events.events.abstract_class import MethodToEvent, ActionsSchema
from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject


class AddressListEventMethods(MethodToEvent):

    event_type = "SELECT"
    event_description = "List Address records"
    event_category = "Address"

    __event_keys__ = {
        "9c251d7d-da70-4d63-a72c-e69c26270442": "address_list_super_user",
        "52afe375-dd95-4f4b-aaa2-4ec61bc6de52": "address_list_employee",
    }

    @classmethod
    def address_list_super_user(
        cls,
        list_options: ListOptions,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        post_code_list = RelationshipEmployee2PostCode.filter_all(
            RelationshipEmployee2PostCode.company_id
            == token_dict.selected_company.company_id,
        ).data
        post_code_id_list = [post_code.member_id for post_code in post_code_list]
        if not post_code_id_list:
            raise HTTPException(
                status_code=404,
                detail="User has no post code registered. User can not list addresses.",
            )
        get_street_ids = [
            street_id[0]
            for street_id in AddressPostcode.select_only(
                AddressPostcode.id.in_(post_code_id_list),
                select_args=[AddressPostcode.street_id],
                order_by=AddressPostcode.street_id.desc(),
            ).data
        ]
        if not get_street_ids:
            raise HTTPException(
                status_code=404,
                detail="User has no street registered. User can not list addresses.",
            )
        Addresses.pre_query = Addresses.filter_all(
            Addresses.street_id.in_(get_street_ids),
        ).query
        Addresses.filter_attr = list_options
        records = Addresses.filter_all().data
        return AlchemyJsonResponse(
            completed=True,
            message="List Address records",
            result=records,
        )

    @classmethod
    def address_list_employee(
        cls,
        list_options: ListOptions,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        Addresses.filter_attr = list_options
        records = Addresses.list_via_employee(
            token_dict=token_dict,
        )
        return AlchemyJsonResponse(
            completed=True,
            message="List Address records",
            result=records,
        )


class AddressCreateEventMethods(MethodToEvent):

    event_type = "CREATE"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "ffdc445f-da10-4ce4-9531-d2bdb9a198ae": "create_address",
    }

    @classmethod
    def create_address(
        cls,
        data: InsertAddress,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        post_code = AddressPostcode.filter_one(
            AddressPostcode.uu_id == data.post_code_uu_id,
        ).data
        if not post_code:
            raise HTTPException(
                status_code=404,
                detail="Post code not found. User can not create address without post code.",
            )

        data_dict = data.excluded_dump()
        data_dict["street_id"] = post_code.street_id
        data_dict["street_uu_id"] = str(post_code.street_uu_id)
        del data_dict["post_code_uu_id"]
        address = Addresses.find_or_create(**data_dict)
        address.save()
        address.update(is_confirmed=True)
        address.save()
        return JSONResponse(
            content={
                "completed": True,
                "message": "Create Address record",
                "data": address.get_dict(),
            },
            status_code=200,
        )


class AddressSearchEventMethods(MethodToEvent):

    event_type = "SEARCH"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "e0ac1269-e9a7-4806-9962-219ac224b0d0": "search_address",
    }

    @classmethod
    def search_address(
        cls,
        data: SearchAddress,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        import databases.sql_models
        from time import perf_counter

        st = perf_counter()

        pre_query_first = AddressStreet.search_address_text(search_text=data.search)
        query, schemas, new_data_list = (
            pre_query_first.get("query"),
            pre_query_first.get("schema"),
            [],
        )
        filter_list = data.list_options.dump()
        filter_table = AddressStreet
        if filter_list.get("order_field") not in schemas:
            filter_list["order_field"] = "uu_id"
        else:
            filter_table = getattr(
                databases.sql_models, str(filter_list.get("order_field")).split(".")[0]
            )
            filter_list["order_field"] = str(filter_list.get("order_field")).split(".")[
                1
            ]

        order = (
            getattr(filter_table, filter_list.get("order_field")).desc()
            if str(filter_list.get("order_type"))[0] == "d"
            else getattr(filter_table, filter_list.get("order_field")).asc()
        )

        query = (
            query.order_by(order)
            .limit(int(filter_list.get("size")))
            .offset(int((filter_list.get("page")) - 1) * int(filter_list.get("size")))
            .populate_existing()
        )

        records = list(query.all())
        print(perf_counter() - st)

        for item in records:
            new_data_dict = {}
            for index, schema in enumerate(schemas):
                new_data_dict[schema] = str(item[index])
                if "uu_id" in str(item[index]):
                    new_data_dict[schema] = str(new_data_dict.get(schema))
            new_data_list.append(new_data_dict)

        return JSONResponse(
            content={
                "completed": True,
                "pagination": filter_list,
                "count": len(new_data_list),
                "data": new_data_list,
                "message": "Search Address records",
            },
            status_code=200,
        )


class AddressUpdateEventMethods(MethodToEvent):

    event_type = "UPDATE"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "1f9c3a9c-e5bd-4dcd-9b9a-3742d7e03a27": "update_address",
    }

    @classmethod
    def update_address(
        cls,
        address_uu_id: str,
        data: UpdateAddress,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        if isinstance(token_dict, EmployeeTokenObject):
            address = Addresses.filter_one(
                Addresses.uu_id == address_uu_id,
            ).data
            if not address:
                raise HTTPException(
                    status_code=404,
                    detail=f"Address not found. User can not update with given address uuid : {address_uu_id}",
                )

            data_dict = data.excluded_dump()
            updated_address = address.update(**data_dict)
            updated_address.save()
            return JSONResponse(
                content={
                    "completed": True,
                    "message": "Update Address record",
                    "data": updated_address.get_dict(),
                },
                status_code=200,
            )
        elif isinstance(token_dict, OccupantTokenObject):
            raise HTTPException(
                status_code=403,
                detail="Occupant can not update address.",
            )


class AddressPatchEventMethods(MethodToEvent):

    event_type = "PATCH"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "b0e55a7e-af81-468c-b46c-a6b3a6b68d5d": "patch_address",
    }

    @classmethod
    def patch_address(
        cls,
        address_uu_id: str,
        data: InsertAddress,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        address = Addresses.filter_one(
            Addresses.uu_id == address_uu_id,
        ).data
        post_code = RelationshipEmployee2PostCode.filter_one(
            RelationshipEmployee2PostCode.member_id == address.post_code_id,
        )
        if not post_code:
            raise HTTPException(
                status_code=404,
                detail="Post code not found. User can not patch address without post code.",
            )

        data_dict = data.excluded_dump()
        data_dict["post_code_id"] = post_code.id
        del data_dict["post_code_uu_id"]

        patched_address = address.patch(**data_dict)
        return JSONResponse(
            content={
                "completed": True,
                "message": "Patch Address record",
                "data": patched_address.get_dict(),
            },
            status_code=200,
        )


class AddressPostCodeCreateEventMethods(MethodToEvent):

    event_type = "CREATE"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "6f1406ac-577d-4f2c-8077-71fff2252c5f": "create_post_code_address",
    }

    @classmethod
    def create_post_code_address(
        cls,
        data: InsertPostCode,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        data_dump = data.excluded_dump()
        street = AddressStreet.filter_one(
            AddressStreet.uu_id == data.street_uu_id,
        ).data
        if not street:
            raise HTTPException(
                status_code=404,
                detail="Street not found. User can not create post code without street.",
            )
        data_dump["street_id"] = street.id
        data_dump["postcode"] = data.post_code
        del data_dump["post_code"]

        post_code = AddressPostcode.find_or_create(**data_dump)
        relation_table = AddressPostcode.__many__table__.find_or_create(
            member_id=post_code.id,
            employee_id=token_dict.selected_company.employee_id,
            company_id=token_dict.selected_company.company_id,
        )
        post_code.save()
        post_code.update(is_confirmed=True)
        post_code.save()
        relation_table.update(is_confirmed=True)
        relation_table.save()
        return JSONResponse(
            content={
                "completed": True,
                "message": "Create Post Code record",
                "data": post_code.get_dict(),
            },
            status_code=200,
        )


class AddressPostCodeUpdateEventMethods(MethodToEvent):

    event_type = "UPDATE"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "df18e489-a63c-477f-984c-aa52d30640ad": "update_post_code_address",
    }

    @classmethod
    def update_post_code_address(
        cls,
        post_code_uu_id: str,
        data: UpdatePostCode,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        if isinstance(token_dict, EmployeeTokenObject):
            AddressPostcode.pre_query = AddressPostcode.select_action(
                employee_id=token_dict.selected_company.employee_id,
            )
            post_code = AddressPostcode.filter_one(
                AddressPostcode.uu_id == post_code_uu_id,
            ).data
            if not post_code:
                raise HTTPException(
                    status_code=404,
                    detail="Street not found. User can not update post code without street.",
                )

            data_dict = data.excluded_dump()
            updated_post_code = post_code.update(**data_dict)
            updated_post_code.save()
            return JSONResponse(
                content={
                    "completed": True,
                    "message": "Update Post Code record",
                    "data": updated_post_code.get_dict(),
                },
                status_code=200,
            )
        elif isinstance(token_dict, OccupantTokenObject):
            raise HTTPException(
                status_code=403,
                detail="Occupant can not update post code.",
            )
        return JSONResponse(
            content={
                "completed": True,
                "message": "Update Post Code record",
                "data": {},
            },
            status_code=404,
        )


class AddressPostCodeListEventMethods(MethodToEvent):

    event_type = "SELECT"
    event_description = ""
    event_category = ""

    __event_keys__ = {
        "88d37b78-1ac4-4513-9d25-090ac3a24f31": "list_post_code_address",
    }

    @classmethod
    def list_post_code_address(
        cls,
        list_options: ListOptions,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ):
        post_code_list = AddressPostcode.__many__table__.filter_all(
            AddressPostcode.__many__table__.company_id
            == token_dict.selected_company.company_id,
        ).data
        if not post_code_list:
            raise HTTPException(
                status_code=404,
                detail="User has no post code registered or not yet any post code created.",
            )

        AddressPostcode.pre_query = AddressPostcode.filter_all(
            AddressPostcode.id.in_(
                [post_code.member_id for post_code in post_code_list]
            ),
        ).query
        AddressPostcode.filter_attr = list_options
        records = AddressPostcode.filter_all().data
        return AlchemyJsonResponse(
            completed=True,
            message="List Address records",
            result=records,
        )


AddressListEventMethod = AddressListEventMethods(
    action=ActionsSchema(endpoint="/address/list")
)
AddressCreateEventMethod = AddressCreateEventMethods(
    action=ActionsSchema(endpoint="/address/create")
)
AddressUpdateEventMethod = AddressUpdateEventMethods(
    action=ActionsSchema(endpoint="/address/update")
)
AddressPatchEventMethod = AddressPatchEventMethods(
    action=ActionsSchema(endpoint="/address/patch")
)
AddressPostCodeCreateEventMethod = AddressPostCodeCreateEventMethods(
    action=ActionsSchema(endpoint="/postcode/create")
)
AddressPostCodeUpdateEventMethod = AddressPostCodeUpdateEventMethods(
    action=ActionsSchema(endpoint="/postcode/update")
)
AddressPostCodeListEventMethod = AddressPostCodeListEventMethods(
    action=ActionsSchema(endpoint="/postcode/list")
)
AddressSearchEventMethod = AddressSearchEventMethods(
    action=ActionsSchema(endpoint="/address/search")
)
