from schemas.base_imports import (
    CrudCollection,
    String,
    Integer,
    Boolean,
    ForeignKey,
    Index,
    TIMESTAMP,
    Numeric,
    SmallInteger,
    mapped_column,
    Mapped,
    BigInteger,
    Text,
)

class RelationshipEmployee2PostCode(CrudCollection):
    """
    Build2EmployeeRelationship class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "relationship_employee2postcode"
    __exclude__fields__ = []
    __include__fields__ = []

    company_id: Mapped[int] = mapped_column(
        ForeignKey("companies.id"), nullable=True
    )  # 1, 2, 3
    employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
    member_id: Mapped[int] = mapped_column(
        ForeignKey("address_postcode.id"), nullable=False
    )

    relationship_type: Mapped[str] = mapped_column(
        String, nullable=True, server_default="Employee"
    )  # Commercial
    show_only: Mapped[bool] = mapped_column(Boolean, server_default="0")

    __table_args__ = ({"comment": "Build2Employee Relationship Information"},)


class AddressPostcode(CrudCollection):
    """
    Postcode class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_postcode"
    __exclude__fields__ = []
    __access_by__ = []
    __many__table__ = RelationshipEmployee2PostCode

    street_id: Mapped[int] = mapped_column(ForeignKey("address_street.id"))
    street_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="Street UUID"
    )
    postcode: Mapped[str] = mapped_column(
        String(32), nullable=False, comment="Postcode"
    )

    __table_args__ = ({"comment": "Postcode Information"},)


class Addresses(CrudCollection):
    """
    Address class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "addresses"
    __exclude__fields__ = []

    build_number: Mapped[str] = mapped_column(
        String(24), nullable=False, comment="Build Number"
    )
    door_number: Mapped[str] = mapped_column(
        String(24), nullable=True, comment="Door Number"
    )
    floor_number: Mapped[str] = mapped_column(
        String(24), nullable=True, comment="Floor Number"
    )

    comment_address: Mapped[str] = mapped_column(
        String, nullable=False, comment="Address"
    )
    letter_address: Mapped[str] = mapped_column(
        String, nullable=False, comment="Address"
    )
    short_letter_address: Mapped[str] = mapped_column(
        String, nullable=False, comment="Address"
    )

    latitude: Mapped[float] = mapped_column(Numeric(20, 12), server_default="0")
    longitude: Mapped[float] = mapped_column(Numeric(20, 12), server_default="0")

    street_id: Mapped[int] = mapped_column(
        ForeignKey("address_street.id"), nullable=False
    )
    street_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="Street UUID"
    )

    __table_args__ = (
        # Index("_address_ndx_00", country_code, b_state, city, district),
        {"comment": "Address Information"},
    )

    @classmethod
    def list_via_employee(cls, token_dict, filter_expr=None):
        with cls.new_session() as db_session:
            post_code_list = RelationshipEmployee2PostCode.filter_all(
                RelationshipEmployee2PostCode.employee_id
                == token_dict.selected_company.employee_id,
                db=db_session,
            ).data
            post_code_id_list = [post_code.member_id for post_code in post_code_list]
            if not post_code_id_list:
                raise ValueError(
                    "User has no post code registered. User can not list addresses."
                )
                # raise HTTPException(
                #     status_code=404,
                #     detail="User has no post code registered. User can not list addresses.",
                # )
            cls.pre_query = cls.filter_all(
                cls.post_code_id.in_(post_code_id_list), db=db_session
            ).query
            filter_cls = cls.filter_all(*filter_expr or [], db=db_session)
            cls.pre_query = None
            return filter_cls.data

    # buildings: Mapped["Build"] = relationship(
    #     "Build", back_populates="addresses", foreign_keys="Build.address_id"
    # )
    # site: Mapped["BuildSites"] = relationship(
    #     "BuildSites", back_populates="addresses", foreign_keys="BuildSites.address_id"
    # )
    # official_companies: Mapped["Companies"] = relationship(
    #     "Company",
    #     back_populates="official_address",
    #     foreign_keys="Company.official_address_id",
    # )

    # @classmethod
    # def create_action(cls, request, create_address: InsertAddress):
    #     from services.redis.auth_actions.token import parse_token_object_to_dict
    #
    #     token_dict = parse_token_object_to_dict(request=request)
    #     data_dict = create_address.model_dump()
    #     post_code = AddressPostcode.find_one(uu_id=create_address.post_code_uu_id)
    #     if not post_code:
    #         raise HTTPException(
    #             status_code=404,
    #             detail="Post code not found.",
    #         )
    #     if Employee2AddressRelationship.post_code_id.find_one(
    #         employee_id=token_dict.selected_company.employee_id,
    #         post_code_id=post_code.id,
    #     ):
    #         data_dict["post_code_id"] = post_code.id
    #         del data_dict["post_code_uu_id"]
    #         return cls.find_or_create(**create_address.model_dump())
    #     raise HTTPException(
    #         status_code=401,
    #         detail=f"User is not qualified to create address at this post code {post_code.postcode}",
    #     )

    # __table_args__ = (
    #     Index("_address_ndx_00", country_code, b_state, city, district),
    #     {"comment": "Address Information"},
    # )


class AddressGeographicLocations(CrudCollection):
    """
    Country class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_geographic_locations"
    __exclude__fields__ = []

    geo_table: Mapped[str] = mapped_column(
        String, nullable=False, comment="Address Table Name"
    )
    geo_id: Mapped[int] = mapped_column(
        Integer, nullable=False, comment="Address Table ID"
    )
    geo_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Geographic Location Name"
    )
    geo_latitude: Mapped[float] = mapped_column(
        Numeric(20, 6), server_default="0", comment="Geographic Location Name"
    )
    geo_longitude: Mapped[float] = mapped_column(
        Numeric(20, 6), server_default="0", comment="Geographic Location Latitude"
    )
    geo_altitude: Mapped[float] = mapped_column(
        Numeric(20, 6), server_default="0", comment="Geographic Location Longitude"
    )
    geo_description: Mapped[str] = mapped_column(
        Text, nullable=False, comment="Geographic Location Description"
    )
    geo_area_size: Mapped[float] = mapped_column(
        Numeric(20, 2),
        nullable=True,
        server_default="0",
        comment="Geographic Location Area Size",
    )
    geo_population: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Geographic Location Population"
    )
    # geo_geom_point = mapped_column(Geometry('POINT', srid=4326), nullable=True, comment="Geographic Location Points")
    # geo_geom_polygon = mapped_column(Geometry('POLYGON', srid=4326), nullable=True,
    # comment="Geographic Location Vector geographic information (polygon)")
    # geo_centroid = mapped_column( GEOMETRY(POINT, 4326), nullable=True,
    # comment="Geographic Location center of gravity of the region(points)")

    __table_args__ = (
        Index("_address_geographic_locations_ndx_00", geo_table, geo_id),
        Index("_address_geographic_locations_ndx_01", geo_latitude, geo_longitude),
        {"comment": "Geographic Location Information"},
    )


class AddressCountry(CrudCollection):
    """
    Country class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_country"
    __exclude__fields__ = []

    country_code: Mapped[str] = mapped_column(
        String(16), nullable=False, comment="Country Code"
    )
    country_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Country Name"
    )
    money_code: Mapped[str] = mapped_column(
        String(12), nullable=True, comment="Money Code"
    )
    language: Mapped[str] = mapped_column(
        String, nullable=True, comment="Language Code"
    )
    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )

    __table_args__ = (
        Index("_address_country_ndx_00", money_code),
        Index("_address_country_ndx_01", country_code, unique=True),
        {"comment": "Country Information"},
    )


class AddressState(CrudCollection):
    """
    State class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_state"
    __exclude__fields__ = []

    state_code: Mapped[str] = mapped_column(
        String(16), nullable=False, comment="State Code"
    )
    state_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="State Name"
    )
    licence_plate: Mapped[str] = mapped_column(
        String(24), nullable=True, comment="Sign Code"
    )
    phone_code: Mapped[str] = mapped_column(
        String(36), nullable=True, comment="Phone Code"
    )
    gov_code: Mapped[str] = mapped_column(
        String(128), nullable=True, comment="Government Code"
    )
    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )

    country_id: Mapped[int] = mapped_column(ForeignKey("address_country.id"))
    country_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="Country UUID"
    )

    __table_args__ = (
        Index(
            "_address_state_ndx_01",
            country_id,
            state_code,
            unique=True,
        ),
        {"comment": "State Information"},
    )


class AddressCity(CrudCollection):
    """
    City class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_city"
    __exclude__fields__ = []

    city_code: Mapped[str] = mapped_column(
        String(24), nullable=False, comment="City Code"
    )
    city_name: Mapped[str] = mapped_column(String, nullable=False, comment="City Name")
    licence_plate: Mapped[str] = mapped_column(
        String(24), nullable=True, comment="Sign Code"
    )
    phone_code: Mapped[str] = mapped_column(
        String(36), nullable=True, comment="Phone Code"
    )
    gov_code: Mapped[str] = mapped_column(
        String(128), nullable=True, comment="Government Code"
    )
    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )

    state_id: Mapped[int] = mapped_column(ForeignKey("address_state.id"))
    state_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="State UUID"
    )

    __table_args__ = (
        Index(
            "_address_city_ndx_01",
            state_id,
            city_code,
            unique=True,
        ),
        {"comment": "City Information"},
    )


class AddressDistrict(CrudCollection):
    """
    District class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_district"
    __exclude__fields__ = []

    district_code: Mapped[str] = mapped_column(
        String(16), nullable=False, comment="District Code"
    )
    district_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="District Name"
    )
    phone_code: Mapped[str] = mapped_column(
        String(36), nullable=True, comment="Phone Code"
    )
    gov_code: Mapped[str] = mapped_column(
        String(128), nullable=True, comment="Government Code"
    )
    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )

    city_id: Mapped[int] = mapped_column(
        ForeignKey("address_city.id"), nullable=False, comment="City ID"
    )
    city_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="City UUID"
    )

    __table_args__ = (
        Index(
            "_address_district_ndx_01",
            city_id,
            district_code,
            unique=True,
        ),
        {"comment": "District Information"},
    )


class AddressLocality(CrudCollection):
    """
    Locality class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_locality"
    __exclude__fields__ = []

    locality_code: Mapped[str] = mapped_column(
        String(16), nullable=False, comment="Locality Code"
    )
    locality_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Locality Name"
    )
    type_code: Mapped[str] = mapped_column(String, nullable=True, comment="Type Name")
    type_description: Mapped[str] = mapped_column(
        String, nullable=True, comment="Type Name"
    )
    gov_code: Mapped[str] = mapped_column(
        String(128), nullable=True, comment="Government Code"
    )
    address_show: Mapped[bool] = mapped_column(Boolean, server_default="1")
    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )

    district_id: Mapped[int] = mapped_column(
        ForeignKey("address_district.id"), nullable=False, comment="District ID"
    )
    district_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="District UUID"
    )

    __table_args__ = (
        Index(
            "_address_locality_ndx_01",
            district_id,
            locality_code,
            unique=True,
        ),
        {"comment": "Locality Information"},
    )


class AddressNeighborhood(CrudCollection):
    """
    Neighborhood class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_neighborhood"
    __exclude__fields__ = []

    neighborhood_code: Mapped[str] = mapped_column(
        String(16), nullable=False, comment="Neighborhood Code"
    )
    neighborhood_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Neighborhood Name"
    )
    type_code: Mapped[str] = mapped_column(String, nullable=True, comment="Type Name")
    type_description: Mapped[str] = mapped_column(
        String, nullable=True, comment="Type Name"
    )
    gov_code: Mapped[str] = mapped_column(
        String(128), nullable=True, comment="Government Code"
    )
    address_show: Mapped[bool] = mapped_column(Boolean, server_default="1")
    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )

    district_id: Mapped[int] = mapped_column(
        ForeignKey("address_district.id"), nullable=True, comment="District ID"
    )
    district_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="District UUID"
    )
    locality_id: Mapped[int] = mapped_column(
        ForeignKey("address_locality.id"), nullable=True, comment="Locality ID"
    )
    locality_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="Locality UUID"
    )

    __table_args__ = (
        Index(
            "_address_neighborhood_ndx_01",
            locality_id,
            neighborhood_code,
            unique=True,
        ),
        {"comment": "Neighborhood Information"},
    )


class AddressStreet(CrudCollection):
    """
    Street class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "address_street"
    __exclude__fields__ = []

    street_code: Mapped[str] = mapped_column(
        String(16), nullable=False, comment="Street Code"
    )
    street_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Street Name"
    )
    type_code: Mapped[str] = mapped_column(String, nullable=True, comment="Type Name")
    type_description: Mapped[str] = mapped_column(
        String, nullable=True, comment="Type Name"
    )
    gov_code: Mapped[str] = mapped_column(
        String(128), nullable=True, comment="Government Code"
    )

    address_geographic_id: Mapped[int] = mapped_column(
        BigInteger, nullable=True, comment="Address Geographic Id"
    )
    neighborhood_id: Mapped[int] = mapped_column(
        ForeignKey("address_neighborhood.id"), nullable=False, comment="Neighborhood ID"
    )
    neighborhood_uu_id: Mapped[str] = mapped_column(
        String, server_default="", comment="Neighborhood UUID"
    )

    __table_args__ = (
        Index("_address_street_ndx_01", neighborhood_id, street_code, unique=True),
        {"comment": "Street Information"},
    )

    @classmethod
    def search_address_text(cls, search_text, token_dict=None):
        field_dict = {
            "AddressStreet.uu_id": cls.uu_id,
            "AddressCountry.uu_id": AddressCountry.uu_id,
            "AddressState.uu_id": AddressState.uu_id,
            "AddressCity.uu_id": AddressCity.uu_id,
            "AddressDistrict.uu_id": AddressDistrict.uu_id,
            "AddressLocality.uu_id": AddressLocality.uu_id,
            "AddressNeighborhood.uu_id": AddressNeighborhood.uu_id,
            "AddressCountry.country_name": AddressCountry.country_name,
            "AddressState.state_name": AddressState.state_name,
            "AddressCity.city_name": AddressCity.city_name,
            "AddressDistrict.district_name": AddressDistrict.district_name,
            "AddressLocality.locality_name": AddressLocality.locality_name,
            "AddressNeighborhood.neighborhood_name": AddressNeighborhood.neighborhood_name,
            "AddressStreet.street_name": cls.street_name,
        }
        joined_data = (
            cls.session.query(*list(field_dict.values()))
            .select_from(cls)
            .join(AddressNeighborhood, AddressNeighborhood.id == cls.neighborhood_id)
            .join(
                AddressLocality, AddressLocality.id == AddressNeighborhood.locality_id
            )
            .join(AddressDistrict, AddressDistrict.id == AddressLocality.district_id)
            .join(AddressCity, AddressCity.id == AddressDistrict.city_id)
            .join(AddressState, AddressState.id == AddressCity.state_id)
            .join(AddressCountry, AddressCountry.id == AddressState.country_id)
            .filter(
                or_(
                    AddressNeighborhood.neighborhood_name.ilike(
                        f"%{str(search_text).upper()}%"
                    ),
                    AddressLocality.locality_name.ilike(
                        f"%{str(search_text).upper()}%"
                    ),
                    AddressDistrict.district_name.ilike(
                        f"%{str(search_text).upper()}%"
                    ),
                    # AddressCity.city_name.ilike(f"%{str(search_text).upper()}%"),
                    # AddressState.state_name.ilike(f"%{str(search_text).upper()}%"),
                    # AddressCountry.country_name.ilike(f"%{str(search_text).upper()}%"),
                    cls.street_name.ilike(f"%{str(search_text).upper()}%"),
                ),
            )
        )
        # select([mytable.c.id]).where(
        #     func.to_tsvector('english', mytable.c.title) \
        #         .match('somestring', postgresql_regconfig='english')
        # )
        joined_statement = joined_data
        joined_data = joined_data.first()
        if not joined_data:
            raise ValueError(
                "No address found with the given search text.",
            )
            # raise HTTPException(
            #     status_code=404,
            #     detail="No address found with the given search text.",
            # )
        return dict(
            query=joined_statement,
            schema=list(field_dict.keys()),
        )
