from typing import Union, Dict, Any, List

from fastapi import status
from fastapi.responses import JSONResponse

from api_validations.validations_response import (
    ListAddressResponse,
    AddressPostCodeResponse,
)
from databases import (
    AddressDistrict,
    AddressCity,
    AddressCountry,
    AddressStreet,
    AddressNeighborhood,
    AddressPostcode,
    # AddressCountryCode,
    RelationshipEmployee2PostCode,
    Addresses,
)

from api_validations.validations_request import (
    ListOptions,
    InsertAddress,
    UpdateAddress,
    InsertPostCode,
    UpdatePostCode,
    SearchAddress,
)
from ApiServices.api_handlers 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",
    }
    __event_validation__ = {
        "9c251d7d-da70-4d63-a72c-e69c26270442": ListAddressResponse,
        "52afe375-dd95-4f4b-aaa2-4ec61bc6de52": ListAddressResponse,
    }

    @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
        Addresses.pre_query = Addresses.filter_all(
            Addresses.street_id.in_(get_street_ids),
        )
        records = Addresses.filter_all().data
        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",
    }
    __event_validation__ = {
        "ffdc445f-da10-4ce4-9531-d2bdb9a198ae": InsertAddress,
    }

    @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 AlchemyJsonResponse(
            completed=True,
            message="Address created successfully",
            result=address.get_dict(),
        )


class AddressSearchEventMethods(MethodToEvent):
    """Event methods for searching addresses.

    This class handles address search functionality including text search
    and filtering.
    """

    event_type = "SEARCH"
    event_description = "Search for addresses using text and filters"
    event_category = "Address"

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

    @classmethod
    def _build_order_clause(
        cls, filter_list: Dict[str, Any], schemas: List[str], filter_table: Any
    ) -> Any:
        """Build the ORDER BY clause for the query.

        Args:
            filter_list: Dictionary of filter options
            schemas: List of available schema fields
            filter_table: SQLAlchemy table to query

        Returns:
            SQLAlchemy order_by clause
        """
        # Default to ordering by UUID if field not in schema
        if filter_list.get("order_field") not in schemas:
            filter_list["order_field"] = "uu_id"
        else:
            # Extract table and field from order field
            table_name, field_name = str(filter_list.get("order_field")).split(".")
            filter_table = getattr(databases.sql_models, table_name)
            filter_list["order_field"] = field_name

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

    @classmethod
    def _format_record(cls, record: Any, schemas: List[str]) -> Dict[str, str]:
        """Format a database record into a dictionary.

        Args:
            record: Database record to format
            schemas: List of schema fields

        Returns:
            Formatted record dictionary
        """
        result = {}
        for index, schema in enumerate(schemas):
            value = str(record[index])
            # Special handling for UUID fields
            if "uu_id" in value:
                value = str(value)
            result[schema] = value
        return result

    @classmethod
    def search_address(
        cls,
        data: SearchAddress,
        token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
    ) -> JSONResponse:
        """Search for addresses using text search and filters.

        Args:
            data: Search parameters including text and filters
            token_dict: Authentication token

        Returns:
            JSON response with search results

        Raises:
            HTTPException: If search fails
        """
        try:
            # Start performance measurement
            start_time = perf_counter()

            # Get initial query
            search_result = AddressStreet.search_address_text(search_text=data.search)
            if not search_result:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="No addresses found matching search criteria",
                )

            query = search_result.get("query")
            schemas = search_result.get("schema")

            # Apply filters
            filter_list = data.list_options.dump()
            filter_table = AddressStreet

            # Build and apply order clause
            order = cls._build_order_clause(filter_list, schemas, filter_table)

            # Apply pagination
            page_size = int(filter_list.get("size"))
            offset = (int(filter_list.get("page")) - 1) * page_size

            # Execute query
            query = (
                query.order_by(order)
                .limit(page_size)
                .offset(offset)
                .populate_existing()
            )
            records = list(query.all())

            # Format results
            results = [cls._format_record(record, schemas) for record in records]

            # Log performance
            duration = perf_counter() - start_time
            print(f"Address search completed in {duration:.3f}s")

            return AlchemyJsonResponse(
                completed=True, message="Address search results", result=results
            )

        except HTTPException as e:
            # Re-raise HTTP exceptions
            raise e
        except Exception as e:
            # Log and wrap other errors
            print(f"Address search error: {str(e)}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="Failed to search addresses",
            ) from e


class AddressUpdateEventMethods(MethodToEvent):

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

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

    @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 AlchemyJsonResponse(
                completed=True,
                message="Address updated successfully",
                result=updated_address.get_dict(),
            )
        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",
    }
    __event_validation__ = {
        "b0e55a7e-af81-468c-b46c-a6b3a6b68d5d": None,
    }

    @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 AlchemyJsonResponse(
            completed=True,
            message="Address patched successfully",
            result=patched_address.get_dict(),
        )


class AddressPostCodeCreateEventMethods(MethodToEvent):

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

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

    @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 AlchemyJsonResponse(
            completed=True,
            message="Post code created successfully",
            result=post_code.get_dict(),
        )


class AddressPostCodeUpdateEventMethods(MethodToEvent):

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

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

    @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 AlchemyJsonResponse(
                completed=True,
                message="Post code updated successfully",
                result=updated_post_code.get_dict(),
            )
        elif isinstance(token_dict, OccupantTokenObject):
            raise HTTPException(
                status_code=403,
                detail="Occupant can not update post code.",
            )
        return AlchemyJsonResponse(
            completed=True, message="Update Post Code record", result={}
        )


class AddressPostCodeListEventMethods(MethodToEvent):

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

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

    @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.filter_attr = list_options
        post_codes = AddressPostcode.filter_all()
        return AlchemyJsonResponse(
            completed=True, message="List Post code records", result=post_codes
        )


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")
)
