from sqlalchemy import (
    String,
    ForeignKey,
    Numeric,
    SmallInteger,
    Boolean,
    Integer,
    Index,
)
from sqlalchemy.orm import mapped_column, Mapped
from Controllers.Postgres.mixin import CrudCollection


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

    __tablename__ = "applications"
    __exclude__fields__ = []

    name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Application Name"
    )
    site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
    application_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="Application Code"
    )
    application_type: Mapped[str] = mapped_column(String, comment="Application Type")
    description: Mapped[str] = mapped_column(String, comment="Application Description")


class Events(CrudCollection):
    """
    Events class based on declarative_base and BaseMixin via session
    If Events2Occupants and Events2Employees are not found for user request, response 401 Unauthorized
    """

    __tablename__ = "events"
    __exclude__fields__ = []

    function_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="function code", unique=True
    )
    function_class: Mapped[str] = mapped_column(
        String, nullable=False, comment="class name"
    )

    # name: Mapped[str] = mapped_column(String, nullable=True)  # form or page title
    description: Mapped[str] = mapped_column(String, server_default="")
    property_description: Mapped[str] = mapped_column(String, server_default="")

    marketing_layer = mapped_column(SmallInteger, server_default="3")
    cost: Mapped[float] = mapped_column(Numeric(20, 2), server_default="0.00")
    unit_price: Mapped[float] = mapped_column(Numeric(20, 2), server_default="0.00")

    endpoint_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="Event Type"
    )
    endpoint_id: Mapped[int] = mapped_column(
        ForeignKey("endpoint_restriction.id"), nullable=True
    )
    endpoint_uu_id: Mapped[str] = mapped_column(
        String, nullable=True, comment="Endpoint UUID"
    )

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


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

    __tablename__ = "modules"
    __exclude__fields__ = []

    module_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Module Name"
    )
    module_description: Mapped[str] = mapped_column(String, server_default="")
    module_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="Module Code"
    )
    module_layer = mapped_column(Integer, nullable=False, comment="Module Layer")
    is_default_module = mapped_column(Boolean, server_default="0")

    def retrieve_services(self):
        services = Services.filter_all(Services.module_id == self.id).data
        if not services:
            self.raise_http_exception(
                status_code="HTTP_404_NOT_FOUND",
                error_case="RECORD_NOT_FOUND",
                message=f"No services found for this module : {str(self.uu_id)}",
                data={
                    "module_uu_id": str(self.uu_id),
                },
            )
        return services

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


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

    __tablename__ = "module_price"
    __exclude__fields__ = []

    campaign_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="Campaign Code"
    )
    module_id: Mapped[int] = mapped_column(ForeignKey("modules.id"), nullable=False)
    module_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Module UUID"
    )
    service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
    service_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Service UUID"
    )
    event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
    event_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Event UUID"
    )
    is_counted_percentage: Mapped[float] = mapped_column(
        Numeric(6, 2), server_default="0.00"
    )  # %22
    discounted_price: Mapped[float] = mapped_column(
        Numeric(20, 2), server_default="0.00"
    )  # Normal: 78.00 TL
    calculated_price: Mapped[float] = mapped_column(
        Numeric(20, 2), server_default="0.00"
    )  # sana düz 75.00 TL yapar

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


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

    __tablename__ = "services"
    __exclude__fields__ = []

    module_id: Mapped[int] = mapped_column(ForeignKey("modules.id"), nullable=False)
    module_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Module UUID"
    )
    service_name: Mapped[str] = mapped_column(
        String, nullable=False, comment="Service Name"
    )
    service_description: Mapped[str] = mapped_column(String, server_default="")
    service_code: Mapped[str] = mapped_column(
        String, nullable=True, comment="Service Code"
    )
    related_responsibility: Mapped[str] = mapped_column(String, server_default="")

    @classmethod
    def retrieve_service_via_occupant_code(cls, occupant_code):
        from Schemas import OccupantTypes

        with cls.new_session() as db_session:
            occupant_type = OccupantTypes.filter_by_one(
                system=True, occupant_code=occupant_code, db=db_session
            ).data
            if not occupant_type:
                cls.raise_http_exception(
                    status_code="HTTP_404_NOT_FOUND",
                    error_case="RECORD_NOT_FOUND",
                    message=f"No occupant type found for this code : {occupant_code}",
                    data={
                        "occupant_code": occupant_code,
                    },
                )
            return cls.filter_one(
                cls.related_responsibility == occupant_type.occupant_code, db=db_session
            ).data

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


class Service2Events(CrudCollection):
    """
    Service2Actions class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "services2events"
    __exclude__fields__ = []

    service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
    service_uu_id = mapped_column(String, nullable=False, comment="Service UUID")
    event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
    event_uu_id = mapped_column(String, nullable=False, comment="Event UUID")

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


# class Service2Application(CrudCollection):
#     pass


class Event2OccupantExtra(CrudCollection):

    __tablename__ = "event2occupant_extra"
    __exclude__fields__ = []

    build_living_space_id: Mapped[int] = mapped_column(
        ForeignKey("build_living_space.id"), nullable=False
    )
    build_living_space_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Build Living Space UUID"
    )
    event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
    event_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Event UUID"
    )

    __table_args__ = (
        Index(
            "event2occupant_extra_bind_event_to_occupant",
            build_living_space_id,
            event_id,
            unique=True,
        ),
        {"comment": "Occupant2Event Information"},
    )


class Event2EmployeeExtra(CrudCollection):
    """
    Employee2Event class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "event2employee_extra"
    __exclude__fields__ = []

    employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
    employee_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Employee UUID"
    )

    event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
    event_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Event UUID"
    )

    __table_args__ = (
        Index(
            "event2employee_extra_employee_to_event",
            employee_id,
            event_id,
            unique=True,
        ),
        {"comment": "Employee to Event Information"},
    )


class Event2Employee(CrudCollection):
    """
    Employee2Event class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "event2employee"
    __exclude__fields__ = []

    employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
    employee_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Employee UUID"
    )
    event_service_id: Mapped[int] = mapped_column(
        ForeignKey("services.id"), nullable=False
    )
    event_service_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Event Cluster UUID"
    )

    __table_args__ = (
        Index(
            "event2employee_employee_to_event",
            employee_id,
            event_service_id,
            unique=True,
        ),
        {"comment": "Employee to Event Information"},
    )

    @classmethod
    def get_event_codes(cls, employee_id: int, db) -> dict[str : list[str]]:
        employee_events = cls.filter_all(
            cls.employee_id == employee_id,
            db=db,
        ).data
        active_event_ids = Service2Events.filter_all_system(
            Service2Events.service_id.in_(
                [event.event_service_id for event in employee_events]
            ),
            db=db,
        ).data
        active_events = Events.filter_all(
            Events.id.in_([event.event_id for event in active_event_ids]),
            db=db,
        ).data
        if extra_events := Event2EmployeeExtra.filter_all(
            Event2EmployeeExtra.employee_id == employee_id,
            db=db,
        ).data:
            events_extra = Events.filter_all(
                Events.id.in_([event.event_id for event in extra_events]),
                db=db,
            ).data
            active_events.extend(events_extra)
        events_dict = {}
        for event in active_events:
            if event.endpoint_code in events_dict:
                events_dict[event.endpoint_code].append(event.function_code)
            else:
                events_dict[event.endpoint_code] = [event.function_code]
        return events_dict


class Event2Occupant(CrudCollection):
    """
    Occupant2Event class based on declarative_base and BaseMixin via session
    """

    __tablename__ = "event2occupant"
    __exclude__fields__ = []

    build_living_space_id: Mapped[str] = mapped_column(
        ForeignKey("build_living_space.id"), nullable=False
    )
    build_living_space_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Build Living Space UUID"
    )
    event_service_id: Mapped[int] = mapped_column(
        ForeignKey("services.id"), nullable=False
    )
    event_service_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Event Cluster UUID"
    )
    # event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
    # event_uu_id = mapped_column(String, nullable=False, comment="Event UUID")

    __table_args__ = (
        Index(
            "event2occupant_bind_event_to_occupant",
            build_living_space_id,
            event_service_id,
            unique=True,
        ),
        {"comment": "Occupant2Event Information"},
    )

    @classmethod
    def get_event_codes(cls, build_living_space_id: int, db) -> dict[str : list[str]]:
        occupant_events = cls.filter_all(
            cls.build_living_space_id == build_living_space_id,
            db=db,
        ).data
        active_event_ids = Service2Events.filter_all_system(
            Service2Events.service_id.in_(
                [event.event_service_id for event in occupant_events]
            ),
            db=db,
        ).data
        active_events = Events.filter_all(
            Events.id.in_([event.event_id for event in active_event_ids]),
            db=db,
        ).data
        if extra_events := Event2OccupantExtra.filter_all(
            Event2OccupantExtra.build_living_space_id == build_living_space_id,
            db=db,
        ).data:
            events_extra = Events.filter_all(
                Events.id.in_([event.event_id for event in extra_events]),
                db=db,
            ).data
            active_events.extend(events_extra)
        events_dict = {}
        for event in active_events:
            if event.endpoint_code in events_dict:
                events_dict[event.endpoint_code].append(event.function_code)
            else:
                events_dict[event.endpoint_code] = [event.function_code]
        return events_dict


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

    __tablename__ = "application2employee"
    __exclude__fields__ = []

    employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
    employee_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Employee UUID"
    )

    application_id: Mapped[int] = mapped_column(ForeignKey("applications.id"))
    application_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Application UUID"
    )

    site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
    application_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="Application Code"
    )

    @classmethod
    def get_application_codes(cls, employee_id: int, db) -> dict[str, str]:
        print("employee_id", employee_id)
        employee_applications = cls.filter_all(
            Application2Employee.employee_id == employee_id,
            db=db,
        ).data
        applications_dict = {}
        print("employee_applications", employee_applications)
        for employee_application in employee_applications:
            if employee_application.site_url not in applications_dict:
                applications_dict[str(employee_application.site_url)] = str(
                    employee_application.application_code
                )
        return applications_dict

    __table_args__ = (
        Index(
            "application_to_employee",
            employee_id,
            site_url,
            application_id,
            unique=True,
        ),
        {"comment": "Application2Employee Information"},
    )


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

    __tablename__ = "application2occupant"
    __exclude__fields__ = []

    build_living_space_id: Mapped[int] = mapped_column(
        ForeignKey("build_living_space.id"), nullable=False
    )
    build_living_space_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Build Living Space UUID"
    )

    application_id: Mapped[int] = mapped_column(ForeignKey("applications.id"))
    application_uu_id: Mapped[str] = mapped_column(
        String, nullable=False, comment="Application UUID"
    )

    site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
    application_code: Mapped[str] = mapped_column(
        String, nullable=False, comment="Application Code"
    )

    @classmethod
    def get_application_codes(cls, build_living_space_id: int, db) -> dict[str, str]:
        occupant_applications = cls.filter_all(
            cls.build_living_space_id == build_living_space_id,
            db=db,
        ).data
        applications_dict = {}
        for occupant_application in occupant_applications:
            if occupant_application.site_url not in applications_dict:
                applications_dict[str(occupant_application.site_url)] = str(
                    occupant_application.application_code
                )
        return applications_dict

    __table_args__ = (
        Index(
            "application_to_occupant",
            build_living_space_id,
            site_url,
            application_id,
            unique=True,
        ),
        {"comment": "Application2Occupant Information"},
    )
